From 9407cef44ff17bcace11411cf72efc23b182decc Mon Sep 17 00:00:00 2001 From: "le.labat" Date: Mon, 24 Apr 2023 17:39:27 +0200 Subject: [PATCH] Criteo Id Module: ensure all kind of privacy strings are sent to backend Also adding missing gdpr applies flag --- modules/criteoIdSystem.js | 41 ++++++--- test/spec/modules/criteoIdSystem_spec.js | 108 +++++++++++++++++++++-- 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index e379fde0c5f..b48612203f8 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -9,12 +9,13 @@ import { timestamp, parseUrl, triggerPixel, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { submodule } from '../src/hook.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; const gvlid = 91; const bidderCode = 'criteo'; -export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: bidderCode}); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: bidderCode }); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; @@ -77,17 +78,33 @@ function getCriteoDataFromAllStorages() { } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { - const url = 'https://gum.criteo.com/sid/json?origin=prebid' + +function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent) { + let url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + `${dnaBundle ? '&info=' + encodeURIComponent(dnaBundle) : ''}` + - `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` + `${isLocalStorageWritable ? '&lsw=1' : ''}`; + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString) { + url = url + `&us_privacy=${encodeURIComponent(usPrivacyString)}`; + } + + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent) { + url = url + `${gdprConsent.consentString ? '&gdprString=' + encodeURIComponent(gdprConsent.consentString) : ''}`; + url = url + `&gdpr=${gdprConsent.gdprApplies === true ? 1 : 0}`; + } + + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + url = url + `${gppConsent.gppString ? '&gpp=' + encodeURIComponent(gppConsent.gppString) : ''}`; + url = url + `${gppConsent.applicableSections ? '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections) : ''}`; + } + return url; } @@ -116,7 +133,7 @@ function callSyncPixel(domain, pixel) { } } -function callCriteoUserSync(parsedCriteoData, gdprString, callback) { +function callCriteoUserSync(parsedCriteoData, callback) { const cw = storage.cookiesAreEnabled(); const lsw = storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().page); @@ -131,8 +148,7 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) { parsedCriteoData.dnaBundle, cw, lsw, - isPublishertagPresent, - gdprString + isPublishertagPresent ); const callbacks = { @@ -191,13 +207,10 @@ export const criteoIdSubmodule = { * @param {ConsentData} [consentData] * @returns {{id: {criteoId: string} | undefined}}} */ - getId(config, consentData) { - const hasGdprData = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; - const gdprConsentString = hasGdprData ? consentData.consentString : undefined; - + getId() { let localData = getCriteoDataFromAllStorages(); - const result = (callback) => callCriteoUserSync(localData, gdprConsentString, callback); + const result = (callback) => callCriteoUserSync(localData, callback); return { id: localData.bidId ? { criteoId: localData.bidId } : undefined, diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 508a22e8baa..aaf63873d93 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -1,5 +1,6 @@ import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; import * as utils from 'src/utils.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; import { server } from '../../mocks/xhr'; const pastDateString = new Date(0).toString() @@ -17,6 +18,9 @@ describe('CriteoId module', function () { let timeStampStub; let parseUrlStub; let triggerPixelStub; + let gdprConsentDataStub; + let uspConsentDataStub; + let gppConsentDataStub; beforeEach(function (done) { getCookieStub = sinon.stub(storage, 'getCookie'); @@ -27,6 +31,9 @@ describe('CriteoId module', function () { timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); parseUrlStub = sinon.stub(utils, 'parseUrl').returns({ protocol: 'https', hostname: 'testdev.com' }) triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); done(); }); @@ -39,6 +46,9 @@ describe('CriteoId module', function () { timeStampStub.restore(); triggerPixelStub.restore(); parseUrlStub.restore(); + gdprConsentDataStub.restore(); + uspConsentDataStub.restore(); + gppConsentDataStub.restore(); }); const storageTestCases = [ @@ -136,11 +146,11 @@ describe('CriteoId module', function () { })); const gdprConsentTestCases = [ - { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expected: 'expectedConsentString' }, - { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expected: undefined }, - { consentData: { gdprApplies: true, consentString: undefined }, expected: undefined }, - { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expected: undefined }, - { consentData: undefined, expected: undefined } + { consentData: { gdprApplies: true, consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '1' }, + { consentData: { gdprApplies: false, consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '0' }, + { consentData: { gdprApplies: true, consentString: undefined }, expectedGdprConsent: undefined, expectedGdpr: '1' }, + { consentData: { gdprApplies: 'oui', consentString: 'expectedConsentString' }, expectedGdprConsent: 'expectedConsentString', expectedGdpr: '0' }, + { consentData: undefined, expectedGdprConsent: undefined, expectedGdpr: undefined } ]; it('should call sync pixels if request by backend', function () { @@ -225,14 +235,94 @@ describe('CriteoId module', function () { gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () { let callBackSpy = sinon.spy(); - let result = criteoIdSubmodule.getId(undefined, testCase.consentData); + + gdprConsentDataStub.returns(testCase.consentData); + + let result = criteoIdSubmodule.getId(undefined); + result.callback(callBackSpy); + + let request = server.requests[0]; + + if (testCase.expectedGdprConsent) { + expect(request.url).to.have.string(`gdprString=${testCase.expectedGdprConsent}`); + } else { + expect(request.url).to.not.have.string('gdprString='); + } + + if (testCase.expectedGdpr) { + expect(request.url).to.have.string(`gdpr=${testCase.expectedGdpr}`); + } else { + expect(request.url).to.not.have.string('gdpr='); + } + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; + })); + + [undefined, 'abc'].forEach(usPrivacy => it('should call user sync url with the us privacy string', function () { + let callBackSpy = sinon.spy(); + + uspConsentDataStub.returns(usPrivacy); + + let result = criteoIdSubmodule.getId(undefined); result.callback(callBackSpy); let request = server.requests[0]; - if (testCase.expected) { - expect(request.url).to.have.string(`gdprString=${testCase.expected}`); + + if (usPrivacy) { + expect(request.url).to.have.string(`us_privacy=${usPrivacy}`); + } else { + expect(request.url).to.not.have.string('us_privacy='); + } + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({}) + ); + + expect(callBackSpy.calledOnce).to.be.true; + })); + + [ + { + consentData: { + gppString: 'abc', + applicableSections: [1] + }, + expectedGpp: 'abc', + expectedGppSid: '1' + }, + { + consentData: undefined, + expectedGpp: undefined, + expectedGppSid: undefined + } + ].forEach(testCase => it('should call user sync url with the gpp string', function () { + let callBackSpy = sinon.spy(); + + gppConsentDataStub.returns(testCase.consentData); + + let result = criteoIdSubmodule.getId(undefined); + result.callback(callBackSpy); + + let request = server.requests[0]; + + if (testCase.expectedGpp) { + expect(request.url).to.have.string(`gpp=${testCase.expectedGpp}`); + } else { + expect(request.url).to.not.have.string('gpp='); + } + + if (testCase.expectedGppSid) { + expect(request.url).to.have.string(`gpp_sid=${testCase.expectedGppSid}`); } else { - expect(request.url).to.not.have.string('gdprString'); + expect(request.url).to.not.have.string('gpp_sid='); } request.respond(