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 `
")
+ lastBodyIndex := strings.LastIndex(ad, "", "", 1), "\n\n\n
\n\n\n\n
\n")
+}
+
+func removeWrapper(ad string) string {
+ bodyIndex := strings.Index(ad, "
")
+ if bodyIndex == -1 || lastBodyIndex == -1 {
+ return ""
+ }
+
+ str := strings.TrimSpace(strings.Replace(strings.Replace(ad[bodyIndex:lastBodyIndex], "
", "", 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",
+ 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",
+ 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>%Ry7oBsdXNzYn{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&|H36EsUCmOpS(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++