-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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({ | ||
|
||
let payload = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll change it to const to be consistent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. If you have a choice, always use |
||
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 | ||
}); | ||
} | ||
|
||
/** | ||
|
@@ -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}`); | ||
} | ||
|
@@ -666,6 +683,10 @@ export function PrebidServer() { | |
.reduce(utils.flatten) | ||
.filter(utils.uniques); | ||
|
||
if (_s2sConfig && _s2sConfig.syncEndpoint) { | ||
queueSync(_s2sConfig.bidders, bidRequests[0].gdprConsent); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per this check, it should always at least have 1 element: But I can add another check in this code to be sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Either way. My only two concerns are "does it work now?" and "is it likely that the code changes to break it in the future without anyone realizing?" Looks like the answer to (1) is "yes." (2) usually depends on how thorough the docs and unit tests are. Your call either way. From my experience, I wouldn't trust the |
||
} | ||
|
||
const request = protocolAdapter().buildRequest(s2sBidRequest, bidRequests, adUnitsWithSizes); | ||
const requestJson = JSON.stringify(request); | ||
|
||
|
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'; | ||
|
@@ -392,11 +392,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); | ||
|
@@ -424,6 +424,58 @@ describe('S2S Adapter', () => { | |
$$PREBID_GLOBAL$$.requestBids.removeHook(requestBidsHook); | ||
}); | ||
|
||
it('check gdpr info gets added into cookie_sync request', () => { | ||
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); | ||
|
||
// check scenario if gdprApplies and we got consent information CMP fine | ||
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'); | ||
|
||
// check scenario if gdprApplies is false | ||
resetSyncedStatus(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests shouldn't have side-effects on the global state. It's also dangerous to put this mid-test because it won't execute if one of the earlier assertions fail. This test should be split into smaller ones, and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True on the above; I wanted to limit the scenarios where I was using this function to not affect other tests by having that function run for every test. But it doesn't seem like it's breaking the other tests when I put it there now. I'll make the changes here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's fine... if other tests fail because the global state gets reset, then they're the broken ones. Tests should be initializing their own state, rather than relying on other tests to do it for them. |
||
requests = []; | ||
|
||
gdprBidRequest[0].gdprConsent = { | ||
consentString: 'xyz789abcc', | ||
gdprApplies: false | ||
}; | ||
|
||
adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); | ||
requestBid = JSON.parse(requests[0].requestBody); | ||
|
||
expect(requestBid.gdpr).is.equal(0); | ||
expect(requestBid.gdpr_consent).is.undefined; | ||
|
||
// check scenario if we didn't consent information from CMP appropriately (ie timeout) | ||
resetSyncedStatus(); | ||
requests = []; | ||
|
||
gdprBidRequest[0].gdprConsent = { | ||
consentString: undefined, | ||
gdprApplies: undefined | ||
}; | ||
|
||
adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); | ||
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 | ||
|
@@ -794,31 +846,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)); | ||
|
||
|
There was a problem hiding this comment.
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.