From 41856f371e5d12982cbba7707fbfefecd70749ee Mon Sep 17 00:00:00 2001 From: Aadesh Date: Thu, 14 Nov 2019 10:05:09 -0500 Subject: [PATCH] facebook adapter refactor (#1064) --- .../audienceNetworktest/exemplary/banner.json | 141 +++++ .../exemplary/interstitial.json | 143 +++++ .../exemplary/native-1.1.json | 135 +++++ .../audienceNetworktest/exemplary/video.json | 145 +++++ .../supplemental/invalid-banner-height.json | 44 ++ .../supplemental/multi-imp.json | 259 +++++++++ .../supplemental/required-buyeruid.json | 47 ++ .../required-param-placementId.json | 47 ++ .../required-param-publisherId.json | 47 ++ .../supplemental/split-placementId.json | 126 +++++ adapters/audienceNetwork/facebook.go | 528 +++++++++++------- adapters/audienceNetwork/facebook_test.go | 518 +---------------- adapters/bidder.go | 13 + config/config.go | 6 +- exchange/adapter_map.go | 7 +- openrtb_ext/imp_facebook.go | 6 + router/router.go | 10 +- 17 files changed, 1507 insertions(+), 715 deletions(-) create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/video.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json create mode 100644 openrtb_ext/imp_facebook.go diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json new file mode 100644 index 00000000000..d0a85aa6692 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner.json @@ -0,0 +1,141 @@ +{ + "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" + ], + "Auth-Token": [ + "test-app-id|test-app-secret" + ], + "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", + "banner": { + "w": -1, + "h": 250 + }, + "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": 200, + "body": { + "id": "test-req-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 new file mode 100644 index 00000000000..d31714ec44d --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json @@ -0,0 +1,143 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 1, + "h": 1 + } + ], + "w": 1, + "h": 1 + }, + "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 + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Auth-Token": [ + "test-app-id|test-app-secret" + ], + "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", + "banner": { + "w": 0, + "h": 0 + }, + "instl": 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": 200, + "body": { + "id": "test-req-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/native-1.1.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json new file mode 100644 index 00000000000..2223422cc8b --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json @@ -0,0 +1,135 @@ +{ + "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" + ], + "Auth-Token": [ + "test-app-id|test-app-secret" + ], + "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": 200, + "body": { + "id": "test-req-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": "native" + } + ] + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json new file mode 100644 index 00000000000..070bcabf1ce --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.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" + } + } + } + ], + "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" + ], + "Auth-Token": [ + "test-app-id|test-app-secret" + ], + "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", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 0, + "h": 0 + }, + "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": 200, + "body": { + "id": "test-req-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/audienceNetworktest/supplemental/invalid-banner-height.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json new file mode 100644 index 00000000000..fa9fd9132b8 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json @@ -0,0 +1,44 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "1", + "banner": { + "format": [ + { + "w": 640, + "h": 480 + } + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "placementId": "plmt1", + "publisherId": "pub1" + } + } + } + ], + "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 #1: only banner heights 50 and 250 are supported", + "comparison": "literal" + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json new file mode 100644 index 00000000000..4fd1eb33d23 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json @@ -0,0 +1,259 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherid": "pub1", + "placementid": "plmt1" + } + } + }, + { + "id": "test-imp-2", + "banner": { + "format": [ + { + "w": 200, + "h": 50 + } + ], + "w": 250, + "h": 50 + }, + "ext": { + "bidder": { + "publisherid": "pub2", + "placementid": "plmt2" + } + } + } + ], + "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" + ], + "Auth-Token": [ + "test-app-id|test-app-secret" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-1", + "banner": { + "w": -1, + "h": 250 + }, + "tagid": "pub1_plmt1" + } + ], + "ext": { + "appnexus": { + "hb_source": 5 + }, + "prebid": {} + }, + "site": { + "domain": "prebid.org", + "page": "prebid.org", + "publisher": { + "id": "pub1" + } + }, + "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": 200, + "body": { + "id": "test-req-id", + "seatbid": [ + { + "bid": [ + { + "id": "bid-1", + "impid": "test-imp-1", + "price": 1.000000, + "adm": "{\"type\":\"ID\",\"bid_id\":\"bid-1\",\"placement_id\":\"pub1_plmt1\",\"resolved_placement_id\":\"pub1_plmt1\",\"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=plmt1&auction=123&impression=123&request=123478&bid=bid-1&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=plmt1&auction=123&impression=123&request=123478&bid=bid-1&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=plmt1&auction=123&impression=123&request=123478&bid=bid-1&clearing_price=${AUCTION_PRICE}" + } + ] + } + ], + "bidid": "654", + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Auth-Token": [ + "test-app-id|test-app-secret" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-2", + "banner": { + "w": -1, + "h": 50 + }, + "tagid": "pub2_plmt2" + } + ], + "ext": { + "appnexus": { + "hb_source": 5 + }, + "prebid": {} + }, + "site": { + "domain": "prebid.org", + "page": "prebid.org", + "publisher": { + "id": "pub2" + } + }, + "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": 200, + "body": { + "id": "test-req-id", + "seatbid": [ + { + "bid": [ + { + "id": "bid-2", + "impid": "test-imp-2", + "price": 1.000000, + "adm": "{\"type\":\"ID\",\"bid_id\":\"bid-2\",\"placement_id\":\"pub2_plmt2\",\"resolved_placement_id\":\"pub2_plmt2\",\"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=plmt2&auction=123&impression=123&request=123478&bid=bid-2&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=plmt2&auction=123&impression=123&request=123478&bid=bid-2&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=plmt2&auction=123&impression=123&request=123478&bid=bid-2&clearing_price=${AUCTION_PRICE}" + } + ] + } + ], + "bidid": "654", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid-1", + "impid": "test-imp-1", + "price": 1, + "adm": "{\"type\":\"ID\",\"bid_id\":\"bid-1\",\"placement_id\":\"pub1_plmt1\",\"resolved_placement_id\":\"pub1_plmt1\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "adid": "bid-1", + "crid": "bid-1", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=plmt1&auction=123&impression=123&request=123478&bid=bid-1&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=plmt1&auction=123&impression=123&request=123478&bid=bid-1&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=plmt1&auction=123&impression=123&request=123478&bid=bid-1&clearing_price=${AUCTION_PRICE}" + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid-2", + "impid": "test-imp-2", + "price": 1, + "adm": "{\"type\":\"ID\",\"bid_id\":\"bid-2\",\"placement_id\":\"pub2_plmt2\",\"resolved_placement_id\":\"pub2_plmt2\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "adid": "bid-2", + "crid": "bid-2", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=plmt2&auction=123&impression=123&request=123478&bid=bid-2&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=plmt2&auction=123&impression=123&request=123478&bid=bid-2&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=plmt2&auction=123&impression=123&request=123478&bid=bid-2&clearing_price=${AUCTION_PRICE}" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json new file mode 100644 index 00000000000..964dcb48b48 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "plmt1", + "publisherid": "pub1" + } + } + } + ], + "site": { + "domain": "prebid.org", + "page": "prebid.org" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516" + }, + "tmax": 500 + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing bidder token in 'user.buyeruid'", + "comparison": "literal" + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json new file mode 100644 index 00000000000..a9c3c23d298 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "publisherid": "123" + } + } + } + ], + "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": "Missing placementId param", + "comparison": "literal" + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json new file mode 100644 index 00000000000..c50f3d36378 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementId": "123" + } + } + } + ], + "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": "Missing publisherId param", + "comparison": "literal" + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json new file mode 100644 index 00000000000..b99834ab1df --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "placementid": "123_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", + "body": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "w": -1, + "h": 250 + }, + "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": 200, + "body": { + "id": "test-req-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=abc&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=abc&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=abc&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=abc&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=abc&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=abc&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index e71941e95e6..f8c993ba15b 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -1,34 +1,38 @@ package audienceNetwork import ( - "bytes" - "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" - "io/ioutil" - "math/rand" "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/pbs" - "golang.org/x/net/context/ctxhttp" + "github.com/prebid/prebid-server/openrtb_ext" ) type FacebookAdapter struct { http *adapters.HTTPAdapter URI string nonSecureUri string - platformJSON json.RawMessage + platformID string + appID string + appSecret string } -var supportedHeight = map[uint64]bool{ +type facebookAdMarkup struct { + BidID string `json:"bid_id"` +} + +var supportedBannerHeights = map[uint64]bool{ 50: true, - 90: true, 250: true, } @@ -37,267 +41,405 @@ func (a *FacebookAdapter) Name() string { return "audienceNetwork" } -// Facebook likes to parallelize to minimize latency -func (a *FacebookAdapter) SplitAdUnits() bool { - return true -} - func (a *FacebookAdapter) SkipNoCookies() bool { return false } -type facebookParams struct { - PlacementId string `json:"placementId"` +type facebookReqExt struct { + PlatformID string `json:"platformid"` + AuthID string `json:"authentication_id"` } -func coinFlip() bool { - return rand.Intn(2) != 0 -} - -func (a *FacebookAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (result adapters.CallOneResult, err error) { - url := a.URI - if coinFlip() { - //50% of traffic to non-secure endpoint - url = a.nonSecureUri +func (this *FacebookAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{ + Message: "No impressions provided", + }} } - httpReq, _ := http.NewRequest("POST", url, &reqJSON) - httpReq.Header.Add("Content-Type", "application/json") - httpReq.Header.Add("Accept", "application/json") - - anResp, e := ctxhttp.Do(ctx, a.http.Client, httpReq) - if e != nil { - err = e - return + + if request.User == nil || request.User.BuyerUID == "" { + return nil, []error{&errortypes.BadInput{ + Message: "Missing bidder token in 'user.buyeruid'", + }} } - result.StatusCode = anResp.StatusCode + return this.buildRequests(request) +} - defer anResp.Body.Close() - body, _ := ioutil.ReadAll(anResp.Body) - result.ResponseBody = string(body) +func (this *FacebookAdapter) buildRequests(request *openrtb.BidRequest) ([]*adapters.RequestData, []error) { + // Documentation suggests bid request splitting by impression so that each + // request only represents a single impression + reqs := make([]*adapters.RequestData, 0, len(request.Imp)) + headers := http.Header{} + var errs []error + + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("Auth-Token", this.appID+"|"+this.appSecret) + headers.Add("X-Fb-Pool-Routing-Token", request.User.BuyerUID) + + for _, imp := range request.Imp { + // Make a copy of the request so that we don't change the original request which + // is shared across multiple threads + fbreq := *request + fbreq.Imp = []openrtb.Imp{imp} + + if err := this.modifyRequest(&fbreq); err != nil { + errs = append(errs, err) + continue + } - if anResp.StatusCode == http.StatusBadRequest { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, result.ResponseBody), + body, err := json.Marshal(&fbreq) + if err != nil { + errs = append(errs, err) + continue + } + + body, err = modifyImpCustom(body, &fbreq.Imp[0]) + if err != nil { + errs = append(errs, err) + continue } - return - } - if anResp.StatusCode == http.StatusNoContent { - return + reqs = append(reqs, &adapters.RequestData{ + Method: "POST", + Uri: this.URI, + Body: body, + Headers: headers, + }) } - if anResp.StatusCode != http.StatusOK { - err = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", anResp.StatusCode, result.ResponseBody), - } - return + return reqs, errs +} + +// The authentication ID is a sha256 hmac hash encoded as a hex string, based on +// the app secret and the ID of the bid request +func (this *FacebookAdapter) makeAuthID(req *openrtb.BidRequest) string { + h := hmac.New(sha256.New, []byte(this.appSecret)) + h.Write([]byte(req.ID)) + + return hex.EncodeToString(h.Sum(nil)) +} + +func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { + if len(out.Imp) != 1 { + panic("each bid request to facebook should only have a single impression") } - var bidResp openrtb.BidResponse - err = json.Unmarshal(body, &bidResp) + imp := &out.Imp[0] + plmtId, pubId, err := this.extractPlacementAndPublisher(imp) if err != nil { - err = &errortypes.BadServerResponse{ - Message: err.Error(), - } - return + return err } - if len(bidResp.SeatBid) == 0 { - return + + reqExt := facebookReqExt{ + PlatformID: this.platformID, + AuthID: this.makeAuthID(out), + } + + if out.Ext, err = json.Marshal(reqExt); err != nil { + return err } - if len(bidResp.SeatBid[0].Bid) == 0 { - return + + imp.TagID = pubId + "_" + plmtId + imp.Ext = nil + + if out.App != nil { + 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 } - bid := bidResp.SeatBid[0].Bid[0] - result.Bid = &pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - CreativeMediaType: "banner", // hard code this, because that's all facebook supports now, can potentially update it dynamically from "template" field in the "adm" + if err = this.modifyImp(imp); err != nil { + return err } - return + + return nil } -func (a *FacebookAdapter) MakeOpenRtbBidRequest(req *pbs.PBSRequest, bidder *pbs.PBSBidder, placementId string, mtype pbs.MediaType, pubId string, unitInd int) (openrtb.BidRequest, error) { - // this method creates imps for all ad units for the bidder with a single media type - fbReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), []pbs.MediaType{mtype}) +func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { + impType, ok := resolveImpType(out) + if !ok { + return &errortypes.BadInput{ + Message: fmt.Sprintf("imp #%s with invalid type", out.ID), + } + } - if err != nil { - return openrtb.BidRequest{}, err + if out.Instl == 1 && impType != openrtb_ext.BidTypeBanner { + return &errortypes.BadInput{ + Message: fmt.Sprintf("imp #%s: interstitial imps are only supported for banner", out.ID), + } } - fbReq.Ext = a.platformJSON + switch impType { + case openrtb_ext.BidTypeBanner: + if out.Instl == 1 { + out.Banner.W = openrtb.Uint64Ptr(0) + out.Banner.H = openrtb.Uint64Ptr(0) + out.Banner.Format = nil + + return nil + } + + if _, ok := supportedBannerHeights[*out.Banner.H]; !ok { + return &errortypes.BadInput{ + Message: fmt.Sprintf("imp #%s: only banner heights 50 and 250 are supported", out.ID), + } + } + + /* This will get overwritten post-serialization */ + out.Banner.W = openrtb.Uint64Ptr(0) + out.Banner.Format = nil + break + } - if fbReq.Imp != nil && len(fbReq.Imp) > 0 { - // only returns 1 imp for requested ad unit - fbReq.Imp = fbReq.Imp[unitInd : unitInd+1] + return nil +} - if fbReq.Site != nil { - siteCopy := *fbReq.Site - siteCopy.Publisher = &openrtb.Publisher{ID: pubId} - fbReq.Site = &siteCopy +func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb.Imp) (string, string, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(out.Ext, &bidderExt); err != nil { + return "", "", &errortypes.BadInput{ + Message: err.Error(), } - if fbReq.App != nil { - appCopy := *fbReq.App - appCopy.Publisher = &openrtb.Publisher{ID: pubId} - fbReq.App = &appCopy + } + + var fbExt openrtb_ext.ExtImpFacebook + if err := json.Unmarshal(bidderExt.Bidder, &fbExt); err != nil { + return "", "", &errortypes.BadInput{ + Message: err.Error(), } - fbReq.Imp[0].TagID = placementId + } - // if instl = 1 sent in, pass size (0,0) to facebook - if fbReq.Imp[0].Instl == 1 && fbReq.Imp[0].Banner != nil { - fbReq.Imp[0].Banner.W = openrtb.Uint64Ptr(0) - fbReq.Imp[0].Banner.H = openrtb.Uint64Ptr(0) + if fbExt.PlacementId == "" { + return "", "", &errortypes.BadInput{ + Message: "Missing placementId param", } - // if instl = 0 and type is banner, do not send non supported size - if fbReq.Imp[0].Instl == 0 && fbReq.Imp[0].Banner != nil { - if !supportedHeight[*fbReq.Imp[0].Banner.H] { - return fbReq, &errortypes.BadInput{ - Message: "Facebook do not support banner height other than 50, 90 and 250", - } - } - // do not send legacy 320x50 size to facebook, instead use 0x50 - if *fbReq.Imp[0].Banner.W == 320 && *fbReq.Imp[0].Banner.H == 50 { - fbReq.Imp[0].Banner.W = openrtb.Uint64Ptr(0) + } + + 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, "_") + if len(toks) == 1 { + if publisherId == "" { + return "", "", &errortypes.BadInput{ + Message: "Missing publisherId param", } } - return fbReq, nil + + return placementId, publisherId, nil + } else if len(toks) == 2 { + publisherId = toks[0] + placementId = toks[1] } else { - return fbReq, &errortypes.BadInput{ - Message: "No supported impressions", + return "", "", &errortypes.BadInput{ + Message: fmt.Sprintf("Invalid placementId param '%s' and publisherId param '%s'", placementId, publisherId), } } + + return placementId, publisherId, nil } -func (a *FacebookAdapter) GenerateRequestsForFacebook(req *pbs.PBSRequest, bidder *pbs.PBSBidder) ([]*openrtb.BidRequest, error) { - requests := make([]*openrtb.BidRequest, len(bidder.AdUnits)*2) // potentially we can for eachadUnit have 2 imps - BANNER and VIDEO - reqIndex := 0 - for i, unit := range bidder.AdUnits { - var params facebookParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, err +// 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) { + impType, ok := resolveImpType(imp) + if ok == false { + panic("processing an invalid impression") + } + + var err error + + 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 + // post-serialization + + // The above does not apply to interstitial impressions + if imp.Instl == 1 { + break } - if params.PlacementId == "" { - return nil, &errortypes.BadInput{ - Message: "Missing placementId param", - } + + json, err = jsonparser.Set(json, []byte("-1"), "imp", "[0]", "banner", "w") + if err != nil { + return json, err } - s := strings.Split(params.PlacementId, "_") - if len(s) != 2 { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid placementId param '%s'", params.PlacementId), - } + + 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 } - pubId := s[0] - // BANNER - fbReqB, err := a.MakeOpenRtbBidRequest(req, bidder, params.PlacementId, pbs.MEDIA_TYPE_BANNER, pubId, i) - if err == nil { - requests[reqIndex] = &fbReqB - reqIndex = reqIndex + 1 + json, err = jsonparser.Set(json, []byte("0"), "imp", "[0]", "video", "h") + if err != nil { + return json, err } - // VIDEO - fbReqV, err := a.MakeOpenRtbBidRequest(req, bidder, params.PlacementId, pbs.MEDIA_TYPE_VIDEO, pubId, i) - if err == nil { - requests[reqIndex] = &fbReqV - reqIndex = reqIndex + 1 + 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 + } + + // 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 } - return requests[:reqIndex], nil -} -func (a *FacebookAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - ortbRequests, e := a.GenerateRequestsForFacebook(req, bidder) + return json, nil +} - if e != nil { - return nil, e +func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode != http.StatusOK { + msg := response.Headers.Get("x-fb-an-errors") + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Unexpected status code %d with error message '%s'", response.StatusCode, msg), + }} } - requests := make([]bytes.Buffer, len(ortbRequests)) - for i, ortbRequest := range ortbRequests { - e = json.NewEncoder(&requests[i]).Encode(ortbRequest) - if e != nil { - return nil, e - } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} } - ch := make(chan adapters.CallOneResult) - - for i := range requests { - go func(bidder *pbs.PBSBidder, reqJSON bytes.Buffer) { - result, err := a.callOne(ctx, reqJSON) - result.Error = err - if result.Bid != nil { - result.Bid.BidderCode = bidder.BidderCode - unit := bidder.LookupAdUnit(result.Bid.AdUnitCode) - if unit != nil { - result.Bid.Width = unit.Sizes[0].W - result.Bid.Height = unit.Sizes[0].H - } - result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode) - if result.Bid.BidID == "" { - result.Error = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", result.Bid.AdUnitCode), - } - result.Bid = nil - } + out := adapters.NewBidderResponseWithBidsCapacity(4) + var errs []error + + for _, seatbid := range bidResp.SeatBid { + for _, bid := range seatbid.Bid { + if bid.AdM == "" { + errs = append(errs, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bid %s missing 'adm'", bid.ID), + }) + continue } - ch <- result - }(bidder, requests[i]) - } - var err error + var obj facebookAdMarkup + if err := json.Unmarshal([]byte(bid.AdM), &obj); err != nil { + errs = append(errs, &errortypes.BadServerResponse{ + Message: err.Error(), + }) + continue + } - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(requests); i++ { - result := <-ch - if result.Bid != nil { - bids = append(bids, result.Bid) - } - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: requests[i].String(), - StatusCode: result.StatusCode, - ResponseBody: result.ResponseBody, + if obj.BidID == "" { + errs = append(errs, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("bid %s missing 'bid_id' in 'adm'", bid.ID), + }) + continue } - bidder.Debug = append(bidder.Debug, debug) + + bid.AdID = obj.BidID + bid.CrID = obj.BidID + + out.Bids = append(out.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: resolveBidType(&bid, request), + }) } - if result.Error != nil { - err = result.Error + } + + return out, errs +} + +func resolveBidType(bid *openrtb.Bid, req *openrtb.BidRequest) openrtb_ext.BidType { + for _, imp := range req.Imp { + if bid.ImpID == imp.ID { + if typ, ok := resolveImpType(&imp); ok { + return typ + } + + panic("Processing an invalid impression; cannot resolve impression type") } } - if len(bids) == 0 { - return nil, err + panic(fmt.Sprintf("Invalid bid imp ID %s does not match any imp IDs from the original bid request", bid.ImpID)) +} + +func resolveImpType(imp *openrtb.Imp) (openrtb_ext.BidType, bool) { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, true + } + + if imp.Video != nil { + return openrtb_ext.BidTypeVideo, true + } + + if imp.Audio != nil { + return openrtb_ext.BidTypeAudio, true } - return bids, nil + + if imp.Native != nil { + return openrtb_ext.BidTypeNative, true + } + + return openrtb_ext.BidTypeBanner, false } -func NewAdapterFromFacebook(config *adapters.HTTPAdapterConfig, partnerID string) adapters.Adapter { - if partnerID == "" { +func NewFacebookBidder(client *http.Client, platformID string, appID 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.MisconfiguredAdapter{ - TheName: "audienceNetwork", - Err: 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."), + return &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."), + } + } + + if appID == "" { + glog.Errorf("No facebook app ID specified. Calls to the Audience Network will fail. Did you set adapters.facebook.app_id in the app config?") + return &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."), + } + } + + if appSecret == "" { + glog.Errorf("No facebook app secret specified. Calls to the Audience Network will fail. Did you set adapters.facebook.app_secret in the app config?") + return &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."), } } - return NewFacebookAdapter(config, partnerID) -} -func NewFacebookAdapter(config *adapters.HTTPAdapterConfig, partnerID string) *FacebookAdapter { - a := adapters.NewHTTPAdapter(config) + a := &adapters.HTTPAdapter{Client: client} return &FacebookAdapter{ http: a, URI: "https://an.facebook.com/placementbid.ortb", //for AB test nonSecureUri: "http://an.facebook.com/placementbid.ortb", - platformJSON: json.RawMessage(fmt.Sprintf("{\"platformid\": %s}", partnerID)), + platformID: platformID, + appID: appID, + appSecret: appSecret, } } diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go index a28787e1aa4..2f685234e40 100644 --- a/adapters/audienceNetwork/facebook_test.go +++ b/adapters/audienceNetwork/facebook_test.go @@ -1,24 +1,10 @@ package audienceNetwork import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" "testing" "time" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "fmt" - - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/adapters/adapterstest" ) type tagInfo struct { @@ -34,6 +20,8 @@ type tagInfo struct { type bidInfo struct { partnerID int + appID string + appSecret string domain string page string publisherID string @@ -49,502 +37,6 @@ type FacebookExt struct { PlatformID int `json:"platformid"` } -func DummyFacebookServer(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var breq openrtb.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if string(breq.Ext) == "" { - http.Error(w, "No Ext field provided", http.StatusInternalServerError) - return - } - var fext FacebookExt - err = json.Unmarshal(breq.Ext, &fext) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if fext.PlatformID != fbdata.partnerID { - http.Error(w, fmt.Sprintf("Platform ID '%d' doesn't match '%d", fext.PlatformID, fbdata.partnerID), http.StatusInternalServerError) - return - } - if breq.Site == nil { - http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) - return - } - if breq.Site.Domain != fbdata.domain { - http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, fbdata.domain), http.StatusInternalServerError) - return - } - if breq.Site.Page != fbdata.page { - http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, fbdata.page), http.StatusInternalServerError) - return - } - if breq.Device.UA != fbdata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s", breq.Device.UA, fbdata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != fbdata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s", breq.Device.IP, fbdata.deviceIP), http.StatusInternalServerError) - return - } - if breq.User.BuyerUID != fbdata.buyerUID { - http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s", breq.User.BuyerUID, fbdata.buyerUID), http.StatusInternalServerError) - return - } - if len(breq.Imp) != 1 { - http.Error(w, fmt.Sprintf("Wrong number of imp objects sent: %d", len(breq.Imp)), http.StatusInternalServerError) - return - } - var bid *openrtb.Bid - for _, tag := range fbdata.tags { - if breq.Imp[0].Banner == nil { - http.Error(w, fmt.Sprintf("No banner object sent"), http.StatusInternalServerError) - return - } - if breq.Imp[0].Instl == 0 { - supportedHeight := map[uint64]bool{ - 50: true, - 90: true, - 250: true, - } - if !supportedHeight[*breq.Imp[0].Banner.H] { - http.Error(w, fmt.Sprintf("Height '%d' not supported", breq.Imp[0].Banner.H), http.StatusBadRequest) - return - } - } else if breq.Imp[0].Instl == 1 { - if *breq.Imp[0].Banner.H != 0 || *breq.Imp[0].Banner.W != 0 { - http.Error(w, fmt.Sprintf("Width and height should be 0, 0 for instl type"), http.StatusBadRequest) - return - } - } else { - http.Error(w, fmt.Sprintf("Invalid Instl sent"), http.StatusBadRequest) - return - } - - if breq.Imp[0].TagID == tag.placementID { - bid = &openrtb.Bid{ - ID: "random-id", - ImpID: breq.Imp[0].ID, - Price: tag.bid, - AdM: tag.content, - } - if tag.delay > 0 { - <-time.After(tag.delay) - } - } - } - if bid == nil { - http.Error(w, fmt.Sprintf("Placement ID '%s' not found", breq.Imp[0].TagID), http.StatusInternalServerError) - return - } - - resp := openrtb.BidResponse{ - ID: "a-random-id", - BidID: "another-random-id", - Cur: "USD", - SeatBid: []openrtb.SeatBid{ - { - Seat: "FBAN", - Bid: []openrtb.Bid{*bid}, - }, - }, - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) -} - -func GenerateBidRequestForTestData(fbdata bidInfo, url string) (*pbs.PBSRequest, error) { - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, len(fbdata.tags)), - } - for i, tag := range fbdata.tags { - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - MediaTypes: []string{"BANNER"}, // todo set this in fbdata so we can test video setup - Sizes: []openrtb.Format{ - { - W: tag.W, - H: tag.H, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "audienceNetwork", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(fmt.Sprintf("{\"placementId\": \"%s\"}", tag.placementID)), - }, - }, - Instl: tag.Instl, - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - return nil, err - } - - req := httptest.NewRequest("POST", url, body) - req.Header.Add("Referer", fbdata.page) - req.Header.Add("User-Agent", fbdata.deviceUA) - req.Header.Add("X-Real-IP", fbdata.deviceIP) - - pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{}) - pc.TrySync("audienceNetwork", fbdata.buyerUID) - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - hcc := config.HostCookie{} - - pbReq, err := pbs.ParsePBSRequest(req, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcc) - return pbReq, err -} - -func TestFacebookBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyFacebookServer)) - defer server.Close() - - fbdata = bidInfo{ - partnerID: 12345678, - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - publisherID: "987654321", - tags: make([]tagInfo, 2), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "need-an-actual-fb-id", - } - fbdata.tags[0] = tagInfo{ - code: "first-tag", - placementID: fmt.Sprintf("%s_999998888", fbdata.publisherID), - bid: 1.67, - W: 300, - H: 250, - } - fbdata.tags[1] = tagInfo{ - code: "second-tag", - placementID: fmt.Sprintf("%s_66775544", fbdata.publisherID), - bid: 3.22, - W: 300, - H: 250, - } - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewFacebookAdapter(&conf, fmt.Sprintf("%d", fbdata.partnerID)) - an.URI = server.URL - an.nonSecureUri = server.URL - - pbReq, err := GenerateBidRequestForTestData(fbdata, server.URL) - - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(pbReq.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - } - if pbReq.Bidders[0].BidderCode != "audienceNetwork" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - - ctx := context.Background() - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Received %d bids instead of 2", len(bids)) - } - for _, bid := range bids { - matched := false - for _, tag := range fbdata.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.BidderCode != "audienceNetwork" { - t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) - } - if bid.Price != tag.bid { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - } - if bid.Width != tag.W || bid.Height != tag.H { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, tag.W, tag.H) - } - if bid.Adm != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - } - } - } - if !matched { - t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - } - - // same test but with one request timing out - fbdata.tags[0].delay = 20 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond) - defer cancel() - - bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - // only get an error if everything fails - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 1 { - t.Fatalf("Received %d bids instead of 1", len(bids)) - } - if bids[0].AdUnitCode != fbdata.tags[1].code { - t.Fatalf("Didn't receive bid from non-timed out request") - } - if bids[0].Price != fbdata.tags[1].bid { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bids[0].Price, fbdata.tags[1].bid) - } -} - -func TestFacebookInterstitialResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyFacebookServer)) - defer server.Close() - - fbdata = bidInfo{ - partnerID: 12345678, - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - publisherID: "987654321", - tags: make([]tagInfo, 1), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "need-an-actual-fb-id", - } - fbdata.tags[0] = tagInfo{ - code: "first-tag", - placementID: fmt.Sprintf("%s_999998888", fbdata.publisherID), - bid: 1.67, - W: 300, - H: 250, - } - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewFacebookAdapter(&conf, fmt.Sprintf("%d", fbdata.partnerID)) - an.URI = server.URL - an.nonSecureUri = server.URL - - pbReq, err := GenerateBidRequestForTestData(fbdata, server.URL) - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(pbReq.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - } - if pbReq.Bidders[0].BidderCode != "audienceNetwork" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - - ctx := context.Background() - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - for _, bid := range bids { - matched := false - for _, tag := range fbdata.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.BidderCode != "audienceNetwork" { - t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) - } - if bid.Price != tag.bid { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - } - if bid.Width != tag.W || bid.Height != tag.H { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, tag.W, tag.H) - } - if bid.Adm != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - } - } - } - if !matched { - t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - } -} - -func TestFacebookBannerRequestWithSupportedSizes(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyFacebookServer)) - defer server.Close() - - fbdata = bidInfo{ - partnerID: 12345678, - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - publisherID: "987654321", - tags: make([]tagInfo, 3), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "need-an-actual-fb-id", - } - fbdata.tags[0] = tagInfo{ - code: "first-tag", - placementID: fmt.Sprintf("%s_999998888", fbdata.publisherID), - bid: 1.67, - W: 300, - H: 250, - } - fbdata.tags[1] = tagInfo{ - code: "second-tag", - placementID: fmt.Sprintf("%s_948884228", fbdata.publisherID), - bid: 3.24, - W: 320, - H: 50, - } - fbdata.tags[2] = tagInfo{ - code: "third-tag", - placementID: fmt.Sprintf("%s_122213422", fbdata.publisherID), - bid: 1.51, - W: 720, - H: 90, - } - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewFacebookAdapter(&conf, fmt.Sprintf("%d", fbdata.partnerID)) - an.URI = server.URL - an.nonSecureUri = server.URL - - pbReq, err := GenerateBidRequestForTestData(fbdata, server.URL) - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(pbReq.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - } - if pbReq.Bidders[0].BidderCode != "audienceNetwork" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - - ctx := context.Background() - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - for _, bid := range bids { - matched := false - for _, tag := range fbdata.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.BidderCode != "audienceNetwork" { - t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) - } - if bid.Price != tag.bid { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - } - if bid.Width != tag.W || bid.Height != tag.H { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, tag.W, tag.H) - } - if bid.Adm != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - } - } - } - if !matched { - t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - } -} - -func TestGenerateRequestsForFacebook(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyFacebookServer)) - defer server.Close() - // todo: only test for banner now, should add video setup to test that it generates 2 imps per ad unit when PBSAdUnit supports video params - fbdata = bidInfo{ - partnerID: 12345678, - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - publisherID: "987654321", - tags: make([]tagInfo, 3), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "need-an-actual-fb-id", - } - fbdata.tags[0] = tagInfo{ - code: "first-tag", - placementID: fmt.Sprintf("%s_999998888", fbdata.publisherID), - bid: 1.67, - W: 300, - H: 250, - Instl: 1, - } - fbdata.tags[1] = tagInfo{ - code: "second-tag", - placementID: fmt.Sprintf("%s_948884228", fbdata.publisherID), - bid: 3.24, - W: 320, - H: 50, - Instl: 0, - } - fbdata.tags[2] = tagInfo{ - code: "third-tag", - placementID: fmt.Sprintf("%s_122213422", fbdata.publisherID), - bid: 1.51, - W: 720, - H: 200, - Instl: 0, - } - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewFacebookAdapter(&conf, fmt.Sprintf("%d", fbdata.partnerID)) - an.URI = server.URL - an.nonSecureUri = server.URL - - pbReq, err := GenerateBidRequestForTestData(fbdata, server.URL) - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(pbReq.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - } - if pbReq.Bidders[0].BidderCode != "audienceNetwork" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - openrtbRequests, err := an.GenerateRequestsForFacebook(pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatalf("Generating openrtb requests failed: %v", err) - } - if len(openrtbRequests) != 2 { - t.Fatalf("Should only generate 2 openrtb request") - } - if len(openrtbRequests[0].Imp) != 1 { - t.Fatalf("Should only generate 1 imp per ad unit") - } - if len(openrtbRequests[1].Imp) != 1 { - t.Fatalf("Should only generate 1 imp per ad unit") - } - if *openrtbRequests[0].Imp[0].Banner.W != 0 || *openrtbRequests[0].Imp[0].Banner.H != 0 { - t.Fatalf("Should be generating 0x0 for interstitial type") - } - - if *openrtbRequests[1].Imp[0].Banner.W != 0 { - t.Fatalf("Should be passing width 0 for size 320x50") - } - +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "audienceNetworktest", NewFacebookBidder(nil, "test-platform-id", "test-app-id", "test-app-secret")) } diff --git a/adapters/bidder.go b/adapters/bidder.go index 5a2b2378183..9d3ffb75414 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) } +type MisconfiguredBidder struct { + Name string + Error error +} + +func (this *MisconfiguredBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) { + return nil, []error{this.Error} +} + +func (this *MisconfiguredBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *RequestData, response *ResponseData) (*BidderResponse, []error) { + return nil, []error{this.Error} +} + func BadInput(msg string) *errortypes.BadInput { return &errortypes.BadInput{ Message: msg, diff --git a/config/config.go b/config/config.go index e2a8170bb32..7fce942c125 100644 --- a/config/config.go +++ b/config/config.go @@ -221,7 +221,6 @@ type Adapter struct { // // For more info on templates, see: https://golang.org/pkg/text/template/ UserSyncURL string `mapstructure:"usersync_url"` - PlatformID string `mapstructure:"platform_id"` // needed for Facebook XAPI struct { Username string `mapstructure:"username"` Password string `mapstructure:"password"` @@ -229,6 +228,11 @@ type Adapter struct { } `mapstructure:"xapi"` // needed for Rubicon Disabled bool `mapstructure:"disabled"` ExtraAdapterInfo string `mapstructure:"extra_info"` + + // needed for Facebook + PlatformID string `mapstructure:"platform_id"` + AppID string `mapstructure:"app_id"` + AppSecret string `mapstructure:"app_secret"` } // validateAdapterEndpoint makes sure that an adapter has a valid endpoint diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index a1806ef7f32..a2a87fae673 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -104,13 +104,16 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), openrtb_ext.BidderTappx: tappx.NewTappxBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderTappx))].Endpoint), openrtb_ext.BidderVerizonMedia: verizonmedia.NewVerizonMediaBidder(client, cfg.Adapters[string(openrtb_ext.BidderVerizonMedia)].Endpoint), + openrtb_ext.BidderFacebook: audienceNetwork.NewFacebookBidder( + client, + cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID, + cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].AppID, + cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].AppSecret), } legacyBidders := map[openrtb_ext.BidderName]adapters.Adapter{ // TODO #267: Upgrade the Conversant adapter openrtb_ext.BidderConversant: conversant.NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderConversant)].Endpoint), - // TODO #211: Upgrade the Facebook adapter - openrtb_ext.BidderFacebook: audienceNetwork.NewAdapterFromFacebook(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID), // TODO #212: Upgrade the Index adapter openrtb_ext.BidderIx: ix.NewIxAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint), // TODO #213: Upgrade the Lifestreet adapter diff --git a/openrtb_ext/imp_facebook.go b/openrtb_ext/imp_facebook.go new file mode 100644 index 00000000000..a5abf557967 --- /dev/null +++ b/openrtb_ext/imp_facebook.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpFacebook struct { + PlacementId string `json:"placementId"` + PublisherId string `json:"publisherId"` +} diff --git a/router/router.go b/router/router.go index 53434981b10..06a842c6107 100644 --- a/router/router.go +++ b/router/router.go @@ -15,7 +15,6 @@ import ( "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/audienceNetwork" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/lifestreet" @@ -153,11 +152,10 @@ func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { "pulsepoint": pulsepoint.NewPulsePointAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPulsepoint)].Endpoint), "rubicon": rubicon.NewRubiconAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Username, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Password, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), - "audienceNetwork": audienceNetwork.NewAdapterFromFacebook(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID), - "lifestreet": lifestreet.NewLifestreetAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderLifestreet)].Endpoint), - "conversant": conversant.NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderConversant)].Endpoint), - "adform": adform.NewAdformAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), - "sovrn": sovrn.NewSovrnAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), + "lifestreet": lifestreet.NewLifestreetAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderLifestreet)].Endpoint), + "conversant": conversant.NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderConversant)].Endpoint), + "adform": adform.NewAdformAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), + "sovrn": sovrn.NewSovrnAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), } }