From 33d015d0785a4eb15b1c772e733ce9c6b47e619e Mon Sep 17 00:00:00 2001 From: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> Date: Tue, 28 Mar 2023 06:42:40 -0600 Subject: [PATCH] Concert Bid Adapter: enable support for GPP consent and remove user sync (#9700) * collect EIDs for bid request * add ad slot positioning to payload * RPO-2012: Update local storage name-spacing for c_uid (#8) * Updates c_uid namespacing to be more specific for concert * fixes unit tests * remove console.log * RPO-2012: Add check for shared id (#9) * Adds check for sharedId * Updates cookie name * remove trailing comma * [RPO-3152] Enable Support for GPP Consent (#12) * Adds gpp consent integration to concert bid adapter * Update tests to check for gpp consent string param * removes user sync endpoint and tests * updates comment * cleans up consentAllowsPpid function * comment fix * rename variables for clarity * fixes conditional logic for consent allows function (#13) --------- Co-authored-by: antoin Co-authored-by: Antoin --- modules/concertBidAdapter.js | 65 ++++++-------- test/spec/modules/concertBidAdapter_spec.js | 93 +++------------------ 2 files changed, 36 insertions(+), 122 deletions(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index dcea60d5231..176729dd607 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -5,7 +5,6 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const BIDDER_CODE = 'concert'; const CONCERT_ENDPOINT = 'https://bids.concert.io'; -const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html'; export const spec = { code: BIDDER_CODE, @@ -47,10 +46,18 @@ export const spec = { optedOut: hasOptedOutOfPersonalization(), adapterVersion: '1.1.1', uspConsent: bidderRequest.uspConsent, - gdprConsent: bidderRequest.gdprConsent + gdprConsent: bidderRequest.gdprConsent, + gppConsent: bidderRequest.gppConsent, } }; + if (!payload.meta.gppConsent && bidderRequest.ortb2?.regs?.gpp) { + payload.meta.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } + payload.slots = validBidRequests.map(bidRequest => { collectEid(eids, bidRequest); const adUnitElement = document.getElementById(bidRequest.adUnitCode) @@ -124,38 +131,6 @@ export const spec = { return bidResponses; }, - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @param {gdprConsent} object GDPR consent object. - * @param {uspConsent} string US Privacy String. - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = []; - if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) { - let params = []; - - if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { - params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`); - } - if (gdprConsent && (typeof gdprConsent.consentString === 'string')) { - params.push(`gdpr_consent=${gdprConsent.consentString}`); - } - if (uspConsent && (typeof uspConsent === 'string')) { - params.push(`usp_consent=${uspConsent}`); - } - - syncs.push({ - type: 'iframe', - url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '') - }); - } - return syncs; - }, - /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data @@ -229,16 +204,24 @@ function hasOptedOutOfPersonalization() { * @param {BidderRequest} bidderRequest Object which contains any data consent signals */ function consentAllowsPpid(bidderRequest) { - /* NOTE: We can't easily test GDPR consent, without the - * `consent-string` npm module; so will have to rely on that - * happening on the bid-server. */ - const uspConsent = !(bidderRequest?.uspConsent === 'string' && + let uspConsentAllows = true; + + // if a us privacy string was provided, but they explicitly opted out + if ( + typeof bidderRequest?.uspConsent === 'string' && bidderRequest?.uspConsent[0] === '1' && - bidderRequest?.uspConsent[2].toUpperCase() === 'Y'); + bidderRequest?.uspConsent[2].toUpperCase() === 'Y' // user has opted-out + ) { + uspConsentAllows = false; + } - const gdprConsent = bidderRequest?.gdprConsent && hasPurpose1Consent(bidderRequest?.gdprConsent); + /* + * True if the gdprConsent is null-y; or GDPR does not apply; or if purpose 1 consent was given. + * Much more nuanced GDPR requirements are tested on the bid server using the @iabtcf/core npm module; + */ + const gdprConsentAllows = hasPurpose1Consent(bidderRequest?.gdprConsent); - return (uspConsent || gdprConsent); + return (uspConsentAllows && gdprConsentAllows); } function collectEid(eids, bid) { diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 00626472ecb..d5e7140a9f7 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -57,8 +57,9 @@ describe('ConcertAdapter', function () { refererInfo: { page: 'https://www.google.com' }, - uspConsent: '1YYY', - gdprConsent: {} + uspConsent: '1YN-', + gdprConsent: {}, + gppConsent: {} }; bidResponse = { @@ -111,7 +112,7 @@ describe('ConcertAdapter', function () { expect(payload).to.have.property('meta'); expect(payload).to.have.property('slots'); - const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent']; + const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent']; const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; metaRequiredFields.forEach(function(field) { @@ -138,6 +139,14 @@ describe('ConcertAdapter', function () { expect(payload.meta.uid).to.not.equal(false); }); + it('should not generate uid if USP consent disallows', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { ...bidRequest, uspConsent: '1YY' }); + const payload = JSON.parse(request.data); + + expect(payload.meta.uid).to.equal(false); + }); + it('should use sharedid if it exists', function() { storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, { @@ -213,82 +222,4 @@ describe('ConcertAdapter', function () { expect(bids).to.have.lengthOf(0); }); }); - - describe('spec.getUserSyncs', function() { - it('should not register syncs when iframe is not enabled', function() { - const opts = { - iframeEnabled: false - } - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should not register syncs when the user has opted out', function() { - const opts = { - iframeEnabled: true - }; - storage.setDataInLocalStorage('c_nap', 'true'); - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync).to.have.lengthOf(0); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: true - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=1'); - }); - - it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_applies=0'); - }); - - it('should set gdpr consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - }); - - it('should set ccpa consent param with the user\'s choices on consent', function() { - const opts = { - iframeEnabled: true - }; - storage.removeDataFromLocalStorage('c_nap'); - - bidRequest.gdprConsent = { - gdprApplies: false, - uspConsent: '1YYY' - }; - - const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); - expect(sync[0].url).to.have.string('usp_consent=1YY'); - }); - }); });