Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDPR - add consent information to PBS cookie_sync request #2530

Merged
merged 2 commits into from
May 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be awesome if this code could be reused for spec.getUserSyncs but it's ok for now.

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