Skip to content

Commit

Permalink
GDPR - add consent information to PBS cookie_sync request (#2530)
Browse files Browse the repository at this point in the history
* add consent information to cookie_sync request

* restructured unit tests and other minor changes
  • Loading branch information
jsnellbaker authored and mkendall07 committed May 15, 2018
1 parent 07e711c commit e610247
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 48 deletions.
62 changes: 42 additions & 20 deletions modules/prebidServerBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,36 +96,56 @@ function setS2sConfig(options) {
}

_s2sConfig = options;
if (options.syncEndpoint) {
queueSync(options.bidders);
}
}
getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig));

/**
* resets the _synced variable back to false, primiarily used for testing purposes
*/
export function resetSyncedStatus() {
_synced = false;
}

/**
* @param {Array} bidderCodes list of bidders to request user syncs for.
*/
function queueSync(bidderCodes) {
function queueSync(bidderCodes, gdprConsent) {
if (_synced) {
return;
}
_synced = true;
const payload = JSON.stringify({

const payload = {
uuid: utils.generateUUID(),
bidders: bidderCodes
});
ajax(_s2sConfig.syncEndpoint, (response) => {
try {
response = JSON.parse(response);
response.bidder_status.forEach(bidder => doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder));
} catch (e) {
utils.logError(e);
};

if (gdprConsent) {
// only populate gdpr field if we know CMP returned consent information (ie didn't timeout or have an error)
if (gdprConsent.consentString) {
payload.gdpr = (gdprConsent.gdprApplies) ? 1 : 0;
}
},
payload, {
contentType: 'text/plain',
withCredentials: true
});
// attempt to populate gdpr_consent if we know gdprApplies or it may apply
if (gdprConsent.gdprApplies !== false) {
payload.gdpr_consent = gdprConsent.consentString;
}
}
const jsonPayload = JSON.stringify(payload);

ajax(_s2sConfig.syncEndpoint,
(response) => {
try {
response = JSON.parse(response);
response.bidder_status.forEach(bidder => doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder));
} catch (e) {
utils.logError(e);
}
},
jsonPayload,
{
contentType: 'text/plain',
withCredentials: true
});
}

/**
Expand Down Expand Up @@ -348,9 +368,6 @@ const LEGACY_PROTOCOL = {
if (result.status === 'OK' || result.status === 'no_cookie') {
if (result.bidder_status) {
result.bidder_status.forEach(bidder => {
if (bidder.no_cookie) {
doBidderSync(bidder.usersync.type, bidder.usersync.url, bidder.bidder);
}
if (bidder.error) {
utils.logWarn(`Prebid Server returned error: '${bidder.error}' for ${bidder.bidder}`);
}
Expand Down Expand Up @@ -666,6 +683,11 @@ export function PrebidServer() {
.reduce(utils.flatten)
.filter(utils.uniques);

if (_s2sConfig && _s2sConfig.syncEndpoint) {
let consent = (Array.isArray(bidRequests) && bidRequests.length > 0) ? bidRequests[0].gdprConsent : undefined;
queueSync(_s2sConfig.bidders, consent);
}

const request = protocolAdapter().buildRequest(s2sBidRequest, bidRequests, adUnitsWithSizes);
const requestJson = JSON.stringify(request);

Expand Down
93 changes: 65 additions & 28 deletions test/spec/modules/prebidServerBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { PrebidServer as Adapter } from 'modules/prebidServerBidAdapter';
import { PrebidServer as Adapter, resetSyncedStatus } from 'modules/prebidServerBidAdapter';
import adapterManager from 'src/adaptermanager';
import * as utils from 'src/utils';
import cookie from 'src/cookie';
Expand Down Expand Up @@ -375,6 +375,7 @@ describe('S2S Adapter', () => {
requests = [];
xhr.onCreate = request => requests.push(request);
config.resetConfig();
resetSyncedStatus();
});

afterEach(() => xhr.restore());
Expand All @@ -392,11 +393,11 @@ describe('S2S Adapter', () => {
expect(requestBid.ad_units[0].bids[0].params.member).to.exist.and.to.be.a('string');
});

it('adds gdpr consent information to ortb2 request depending on module use', () => {
it('adds gdpr consent information to ortb2 request depending on presence of module', () => {
let ortb2Config = utils.deepClone(CONFIG);
ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'

let consentConfig = { consentManagement: { cmp: 'iab' }, s2sConfig: ortb2Config };
let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config };
config.setConfig(consentConfig);

let gdprBidRequest = utils.deepClone(BID_REQUESTS);
Expand Down Expand Up @@ -424,6 +425,67 @@ describe('S2S Adapter', () => {
$$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook);
});

it('check gdpr info gets added into cookie_sync request: have consent data', () => {
let cookieSyncConfig = utils.deepClone(CONFIG);
cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync';

let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig };
config.setConfig(consentConfig);

let gdprBidRequest = utils.deepClone(BID_REQUESTS);

gdprBidRequest[0].gdprConsent = {
consentString: 'abc123def',
gdprApplies: true
};

adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax);
let requestBid = JSON.parse(requests[0].requestBody);

expect(requestBid.gdpr).is.equal(1);
expect(requestBid.gdpr_consent).is.equal('abc123def');
});

it('check gdpr info gets added into cookie_sync request: have consent data but gdprApplies is false', () => {
let cookieSyncConfig = utils.deepClone(CONFIG);
cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync';

let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig };
config.setConfig(consentConfig);

let gdprBidRequest = utils.deepClone(BID_REQUESTS);
gdprBidRequest[0].gdprConsent = {
consentString: 'xyz789abcc',
gdprApplies: false
};

adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax);
let requestBid = JSON.parse(requests[0].requestBody);

expect(requestBid.gdpr).is.equal(0);
expect(requestBid.gdpr_consent).is.undefined;
});

it('checks gdpr info gets added to cookie_sync request: consent data unknown', () => {
let cookieSyncConfig = utils.deepClone(CONFIG);
cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync';

let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: cookieSyncConfig };
config.setConfig(consentConfig);

let gdprBidRequest = utils.deepClone(BID_REQUESTS);
gdprBidRequest[0].gdprConsent = {
consentString: undefined,
gdprApplies: undefined
};

adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax);
let requestBid = JSON.parse(requests[0].requestBody);

expect(requestBid.gdpr).is.undefined;
expect(requestBid.gdpr_consent).is.undefined;
});

it('sets invalid cacheMarkup value to 0', () => {
const s2sConfig = Object.assign({}, CONFIG, {
cacheMarkup: 999
Expand Down Expand Up @@ -794,31 +856,6 @@ describe('S2S Adapter', () => {
expect(bid_request_passed).to.have.property('adId', '123');
});

it('does cookie sync when no_cookie response', () => {
server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE));

config.setConfig({s2sConfig: CONFIG});
adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
server.respond();

sinon.assert.calledOnce(utils.triggerPixel);
sinon.assert.calledWith(utils.triggerPixel, 'https://pixel.rubiconproject.com/exchange/sync.php?p=prebid');
sinon.assert.calledOnce(utils.insertUserSyncIframe);
sinon.assert.calledWith(utils.insertUserSyncIframe, '//ads.pubmatic.com/AdServer/js/user_sync.html?predirect=https%3A%2F%2Fprebid.adnxs.com%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dpubmatic%26uid%3D');
});

it('logs error when no_cookie response is missing type or url', () => {
server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE_ERROR));

config.setConfig({s2sConfig: CONFIG});
adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax);
server.respond();

sinon.assert.notCalled(utils.triggerPixel);
sinon.assert.notCalled(utils.insertUserSyncIframe);
sinon.assert.calledTwice(utils.logError);
});

it('does not call cookieSet cookie sync when no_cookie response && not opted in', () => {
server.respondWith(JSON.stringify(RESPONSE_NO_PBS_COOKIE));

Expand Down

0 comments on commit e610247

Please sign in to comment.