Skip to content

Commit

Permalink
Concert Bid Adapter: enable support for GPP consent and remove user s…
Browse files Browse the repository at this point in the history
…ync (prebid#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 <antoin.campbell@voxmedia.com>
Co-authored-by: Antoin <antoinfive@gmail.com>
  • Loading branch information
3 people authored Mar 28, 2023
1 parent faac597 commit 33d015d
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 122 deletions.
65 changes: 24 additions & 41 deletions modules/concertBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
93 changes: 12 additions & 81 deletions test/spec/modules/concertBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ describe('ConcertAdapter', function () {
refererInfo: {
page: 'https://www.google.com'
},
uspConsent: '1YYY',
gdprConsent: {}
uspConsent: '1YN-',
gdprConsent: {},
gppConsent: {}
};

bidResponse = {
Expand Down Expand Up @@ -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) {
Expand All @@ -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, {
Expand Down Expand Up @@ -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');
});
});
});

0 comments on commit 33d015d

Please sign in to comment.