Skip to content

Commit 33d015d

Browse files
Concert Bid Adapter: enable support for GPP consent and remove user sync (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 <[email protected]> Co-authored-by: Antoin <[email protected]>
1 parent faac597 commit 33d015d

File tree

2 files changed

+36
-122
lines changed

2 files changed

+36
-122
lines changed

modules/concertBidAdapter.js

+24-41
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js';
55

66
const BIDDER_CODE = 'concert';
77
const CONCERT_ENDPOINT = 'https://bids.concert.io';
8-
const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html';
98

109
export const spec = {
1110
code: BIDDER_CODE,
@@ -47,10 +46,18 @@ export const spec = {
4746
optedOut: hasOptedOutOfPersonalization(),
4847
adapterVersion: '1.1.1',
4948
uspConsent: bidderRequest.uspConsent,
50-
gdprConsent: bidderRequest.gdprConsent
49+
gdprConsent: bidderRequest.gdprConsent,
50+
gppConsent: bidderRequest.gppConsent,
5151
}
5252
};
5353

54+
if (!payload.meta.gppConsent && bidderRequest.ortb2?.regs?.gpp) {
55+
payload.meta.gppConsent = {
56+
gppString: bidderRequest.ortb2.regs.gpp,
57+
applicableSections: bidderRequest.ortb2.regs.gpp_sid
58+
}
59+
}
60+
5461
payload.slots = validBidRequests.map(bidRequest => {
5562
collectEid(eids, bidRequest);
5663
const adUnitElement = document.getElementById(bidRequest.adUnitCode)
@@ -124,38 +131,6 @@ export const spec = {
124131
return bidResponses;
125132
},
126133

127-
/**
128-
* Register the user sync pixels which should be dropped after the auction.
129-
*
130-
* @param {SyncOptions} syncOptions Which user syncs are allowed?
131-
* @param {ServerResponse[]} serverResponses List of server's responses.
132-
* @param {gdprConsent} object GDPR consent object.
133-
* @param {uspConsent} string US Privacy String.
134-
* @return {UserSync[]} The user syncs which should be dropped.
135-
*/
136-
getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
137-
const syncs = [];
138-
if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) {
139-
let params = [];
140-
141-
if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) {
142-
params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`);
143-
}
144-
if (gdprConsent && (typeof gdprConsent.consentString === 'string')) {
145-
params.push(`gdpr_consent=${gdprConsent.consentString}`);
146-
}
147-
if (uspConsent && (typeof uspConsent === 'string')) {
148-
params.push(`usp_consent=${uspConsent}`);
149-
}
150-
151-
syncs.push({
152-
type: 'iframe',
153-
url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '')
154-
});
155-
}
156-
return syncs;
157-
},
158-
159134
/**
160135
* Register bidder specific code, which will execute if bidder timed out after an auction
161136
* @param {data} Containing timeout specific data
@@ -229,16 +204,24 @@ function hasOptedOutOfPersonalization() {
229204
* @param {BidderRequest} bidderRequest Object which contains any data consent signals
230205
*/
231206
function consentAllowsPpid(bidderRequest) {
232-
/* NOTE: We can't easily test GDPR consent, without the
233-
* `consent-string` npm module; so will have to rely on that
234-
* happening on the bid-server. */
235-
const uspConsent = !(bidderRequest?.uspConsent === 'string' &&
207+
let uspConsentAllows = true;
208+
209+
// if a us privacy string was provided, but they explicitly opted out
210+
if (
211+
typeof bidderRequest?.uspConsent === 'string' &&
236212
bidderRequest?.uspConsent[0] === '1' &&
237-
bidderRequest?.uspConsent[2].toUpperCase() === 'Y');
213+
bidderRequest?.uspConsent[2].toUpperCase() === 'Y' // user has opted-out
214+
) {
215+
uspConsentAllows = false;
216+
}
238217

239-
const gdprConsent = bidderRequest?.gdprConsent && hasPurpose1Consent(bidderRequest?.gdprConsent);
218+
/*
219+
* True if the gdprConsent is null-y; or GDPR does not apply; or if purpose 1 consent was given.
220+
* Much more nuanced GDPR requirements are tested on the bid server using the @iabtcf/core npm module;
221+
*/
222+
const gdprConsentAllows = hasPurpose1Consent(bidderRequest?.gdprConsent);
240223

241-
return (uspConsent || gdprConsent);
224+
return (uspConsentAllows && gdprConsentAllows);
242225
}
243226

244227
function collectEid(eids, bid) {

test/spec/modules/concertBidAdapter_spec.js

+12-81
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ describe('ConcertAdapter', function () {
5757
refererInfo: {
5858
page: 'https://www.google.com'
5959
},
60-
uspConsent: '1YYY',
61-
gdprConsent: {}
60+
uspConsent: '1YN-',
61+
gdprConsent: {},
62+
gppConsent: {}
6263
};
6364

6465
bidResponse = {
@@ -111,7 +112,7 @@ describe('ConcertAdapter', function () {
111112
expect(payload).to.have.property('meta');
112113
expect(payload).to.have.property('slots');
113114

114-
const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent'];
115+
const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent'];
115116
const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType'];
116117

117118
metaRequiredFields.forEach(function(field) {
@@ -138,6 +139,14 @@ describe('ConcertAdapter', function () {
138139
expect(payload.meta.uid).to.not.equal(false);
139140
});
140141

142+
it('should not generate uid if USP consent disallows', function() {
143+
storage.removeDataFromLocalStorage('c_nap');
144+
const request = spec.buildRequests(bidRequests, { ...bidRequest, uspConsent: '1YY' });
145+
const payload = JSON.parse(request.data);
146+
147+
expect(payload.meta.uid).to.equal(false);
148+
});
149+
141150
it('should use sharedid if it exists', function() {
142151
storage.removeDataFromLocalStorage('c_nap');
143152
const request = spec.buildRequests(bidRequests, {
@@ -213,82 +222,4 @@ describe('ConcertAdapter', function () {
213222
expect(bids).to.have.lengthOf(0);
214223
});
215224
});
216-
217-
describe('spec.getUserSyncs', function() {
218-
it('should not register syncs when iframe is not enabled', function() {
219-
const opts = {
220-
iframeEnabled: false
221-
}
222-
const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
223-
expect(sync).to.have.lengthOf(0);
224-
});
225-
226-
it('should not register syncs when the user has opted out', function() {
227-
const opts = {
228-
iframeEnabled: true
229-
};
230-
storage.setDataInLocalStorage('c_nap', 'true');
231-
232-
const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
233-
expect(sync).to.have.lengthOf(0);
234-
});
235-
236-
it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() {
237-
const opts = {
238-
iframeEnabled: true
239-
};
240-
storage.removeDataFromLocalStorage('c_nap');
241-
242-
bidRequest.gdprConsent = {
243-
gdprApplies: true
244-
};
245-
246-
const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
247-
expect(sync[0].url).to.have.string('gdpr_applies=1');
248-
});
249-
250-
it('should set gdprApplies flag to 1 if the user is in area where GDPR applies', function() {
251-
const opts = {
252-
iframeEnabled: true
253-
};
254-
storage.removeDataFromLocalStorage('c_nap');
255-
256-
bidRequest.gdprConsent = {
257-
gdprApplies: false
258-
};
259-
260-
const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
261-
expect(sync[0].url).to.have.string('gdpr_applies=0');
262-
});
263-
264-
it('should set gdpr consent param with the user\'s choices on consent', function() {
265-
const opts = {
266-
iframeEnabled: true
267-
};
268-
storage.removeDataFromLocalStorage('c_nap');
269-
270-
bidRequest.gdprConsent = {
271-
gdprApplies: false,
272-
consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='
273-
};
274-
275-
const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
276-
expect(sync[0].url).to.have.string('gdpr_consent=BOJ/P2HOJ/P2HABABMAAAAAZ+A==');
277-
});
278-
279-
it('should set ccpa consent param with the user\'s choices on consent', function() {
280-
const opts = {
281-
iframeEnabled: true
282-
};
283-
storage.removeDataFromLocalStorage('c_nap');
284-
285-
bidRequest.gdprConsent = {
286-
gdprApplies: false,
287-
uspConsent: '1YYY'
288-
};
289-
290-
const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent);
291-
expect(sync[0].url).to.have.string('usp_consent=1YY');
292-
});
293-
});
294225
});

0 commit comments

Comments
 (0)