diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video_consented_providers.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video_consented_providers.json new file mode 100644 index 00000000000..5b72f70cbfd --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video_consented_providers.json @@ -0,0 +1,145 @@ +{ + "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 + }, + "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", + "ext": { + "consent": "TESTCONSENT", + "consented_providers_settings": { + "consented_providers": [1608,765,492,1365,5678,1545,2563,1411] + } + } + }, + "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", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 0, + "h": 0 + }, + "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", + "ext": { + "consent": "TESTCONSENT" + } + }, + "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": "video" + } + ] + } + ] +} diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index 0747a409780..5bcca3d3e0e 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -16,6 +16,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/jsonutil" "github.com/prebid/prebid-server/util/maputil" ) @@ -108,6 +109,12 @@ func (this *FacebookAdapter) buildRequests(request *openrtb2.BidRequest) ([]*ada continue } + body, err = jsonutil.DropElement(body, "consented_providers_settings") + if err != nil { + errs = append(errs, err) + return reqs, errs + } + reqs = append(reqs, &adapters.RequestData{ Method: "POST", Uri: this.URI, diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go new file mode 100644 index 00000000000..963477de7aa --- /dev/null +++ b/util/jsonutil/jsonutil.go @@ -0,0 +1,57 @@ +package jsonutil + +import ( + "bytes" + "encoding/json" + "io" +) + +var comma = []byte(",")[0] + +func DropElement(extension []byte, elementName string) ([]byte, error) { + buf := bytes.NewBuffer(extension) + dec := json.NewDecoder(buf) + var startIndex int64 + var i interface{} + for { + token, err := dec.Token() + if err == io.EOF { + // io.EOF is a successful end + break + } + if err != nil { + return nil, err + } + + if token == elementName { + err := dec.Decode(&i) + if err != nil { + return nil, err + } + endIndex := dec.InputOffset() + + if dec.More() { + //if there were other elements before + if extension[startIndex] == comma { + startIndex++ + } + + for { + //structure has more elements, need to find index of comma + if extension[endIndex] == comma { + endIndex++ + break + } + endIndex++ + } + } + + extension = append(extension[:startIndex], extension[endIndex:]...) + break + } else { + startIndex = dec.InputOffset() + } + + } + return extension, nil +} diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go new file mode 100644 index 00000000000..0b6ec34c4ed --- /dev/null +++ b/util/jsonutil/jsonutil_test.go @@ -0,0 +1,122 @@ +package jsonutil + +import ( + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestDropElement(t *testing.T) { + + tests := []struct { + description string + input []byte + elementToRemove string + output []byte + errorExpected bool + errorContains string + }{ + { + description: "Drop Single Element After Another Element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`), + elementToRemove: "consented_providers", + output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Single Element Before Another Element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`), + elementToRemove: "consented_providers", + output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Single Element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`), + elementToRemove: "consented_providers", + output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Single Element string", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`), + elementToRemove: "consented_providers", + output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Parent Element Between Two Elements", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), + elementToRemove: "consented_providers_settings", + output: []byte(`{"consent": "TESTCONSENT","test": 123}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Parent Element Before Element", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), + elementToRemove: "consented_providers_settings", + output: []byte(`{"test": 123}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Parent Element After Element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToRemove: "consented_providers_settings", + output: []byte(`{"consent": "TESTCONSENT"}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Parent Element Only", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToRemove: "consented_providers_settings", + output: []byte(`{}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Element That Doesn't Exist", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToRemove: "test2", + output: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + errorExpected: false, + errorContains: "", + }, + //Errors + { + description: "Error Decode", + input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), + elementToRemove: "consented_providers", + output: []byte(``), + errorExpected: true, + errorContains: "looking for beginning of value", + }, + { + description: "Error Malformed", + input: []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`), + elementToRemove: "consented_providers", + output: []byte(``), + errorExpected: true, + errorContains: "invalid character", + }, + } + + for _, tt := range tests { + res, err := DropElement(tt.input, tt.elementToRemove) + + if tt.errorExpected { + assert.Error(t, err, "Error should not be nil") + assert.True(t, strings.Contains(err.Error(), tt.errorContains)) + } else { + assert.NoError(t, err, "Error should be nil") + assert.Equal(t, tt.output, res, "Result is incorrect") + } + + } +}