From 3bca76deba3f285316866426184d6ebaf4e7cd19 Mon Sep 17 00:00:00 2001 From: Gorka Guridi Date: Tue, 2 Jul 2024 11:20:05 +0100 Subject: [PATCH] SAS-15935 Passing the consent to the script execution instead of handling it in prebid (#16) --- modules/azerionedgeRtdProvider.js | 91 ++--------- .../modules/azerionedgeRtdProvider_spec.js | 153 ++++-------------- 2 files changed, 43 insertions(+), 201 deletions(-) diff --git a/modules/azerionedgeRtdProvider.js b/modules/azerionedgeRtdProvider.js index 66a0548141a..852639972c2 100644 --- a/modules/azerionedgeRtdProvider.js +++ b/modules/azerionedgeRtdProvider.js @@ -20,7 +20,6 @@ const SUBREAL_TIME_MODULE = 'azerionedge'; export const STORAGE_KEY = 'ht-pa-v1-a'; const IMPROVEDIGITAL_GVLID = '253'; -const PURPOSES = ['1', '3', '5', '7', '9']; export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, @@ -45,14 +44,21 @@ function getScriptURL(config) { * Attach script tag to DOM * * @param {Object} config + * @param {Object} userConsent * * @return {void} */ -export function attachScript(config) { +export function attachScript(config, userConsent) { const script = getScriptURL(config); loadExternalScript(script, SUBREAL_TIME_MODULE, () => { if (typeof window.azerionPublisherAudiences === 'function') { - window.azerionPublisherAudiences(config.params?.process || {}); + const publisherConfig = config.params?.process || {}; + window.azerionPublisherAudiences({ + ...publisherConfig, + gdprApplies: userConsent?.gdpr?.gdprApplies, + gdprConsent: userConsent?.gdpr?.consentString, + uspConsent: userConsent?.usp, + }); } }); } @@ -109,79 +115,10 @@ export function setAudiencesToBidders(reqBidsConfigObj, config, audiences) { * @return {boolean} */ function init(config, userConsent) { - if (hasUserConsented(userConsent)) { - attachScript(config); - } + attachScript(config, userConsent); return true; } -/** - * List the vendors consented coming from userConsent object. - * - * @param {Object} userConsent - * - * @return {Array} - */ -function getVendorsConsented(userConsent) { - const consents = userConsent?.gdpr?.vendorData?.vendor?.consents || {}; - return Object.entries(consents).reduce((acc, [vendorId, consented]) => { - return consented ? [...acc, vendorId] : acc; - }, []); -} - -/** - * List the purposes consented coming from userConsent object. - * - * @param {Object} userConsent - * - * @return {Array} - */ -export function getPurposesConsented(userConsent) { - const consents = userConsent?.gdpr?.vendorData?.purpose?.consents || {}; - return Object.entries(consents).reduce((acc, [purposeId, consented]) => { - return consented ? [...acc, purposeId] : acc; - }, []); -} - -/** - * Checks if GDPR gives us access through the userConsent object. - * - * @param {Object} userConsent - * - * @return {boolean} - */ -export function hasGDPRAccess(userConsent) { - const gdprApplies = userConsent?.gdpr?.gdprApplies; - const isVendorAllowed = getVendorsConsented(userConsent).includes(IMPROVEDIGITAL_GVLID); - const arePurposesAllowed = PURPOSES.every((purpose) => getPurposesConsented(userConsent).includes(purpose)); - return !gdprApplies || (isVendorAllowed && arePurposesAllowed); -} - -/** - * Checks if USP gives us access through the userConsent object. - * - * @param {Object} userConsent - * - * @return {boolean} - */ -export function hasUSPAccess(userConsent) { - const uspProvided = userConsent?.usp; - const hasProvidedUserNotice = uspProvided?.[1] !== 'N'; - const hasNotOptedOut = uspProvided?.[2] !== 'Y'; - return !uspProvided || (hasProvidedUserNotice && hasNotOptedOut); -} - -/** - * Checks if GDPR/USP gives us access through the userConsent object. - * - * @param {Object} userConsent - * - * @return {boolean} - */ -export function hasUserConsented(userConsent) { - return hasGDPRAccess(userConsent) && hasUSPAccess(userConsent); -} - /** * Real-time user audiences retrieval * @@ -198,11 +135,9 @@ export function getBidRequestData( config, userConsent ) { - if (hasUserConsented(userConsent)) { - const audiences = getAudiences(); - if (audiences.length > 0) { - setAudiencesToBidders(reqBidsConfigObj, config, audiences); - } + const audiences = getAudiences(); + if (audiences.length > 0) { + setAudiencesToBidders(reqBidsConfigObj, config, audiences); } callback(); } diff --git a/test/spec/modules/azerionedgeRtdProvider_spec.js b/test/spec/modules/azerionedgeRtdProvider_spec.js index 6b89d6264b9..0eef82a2512 100644 --- a/test/spec/modules/azerionedgeRtdProvider_spec.js +++ b/test/spec/modules/azerionedgeRtdProvider_spec.js @@ -13,9 +13,12 @@ describe('Azerion Edge RTD submodule', function () { const bidders = ['appnexus', 'improvedigital']; const process = { key: 'value' }; const dataProvider = { name: 'azerionedge', waitForIt: true }; - const tcfGDPRNotApplicable = { gdprApplies: false }; - const uspNotProvided = { usp: undefined }; - const ignoreConsent = {gdpr: tcfGDPRNotApplicable, usp: uspNotProvided}; + const userConsent = {gdpr: {gdprApplies: 'gdpr-applies', consentString: 'consent-string'}, usp: 'usp'}; + + const resetAll = () => { + window.azerionPublisherAudiences.resetHistory(); + loadExternalScript.resetHistory(); + } let reqBidsConfigObj; let storageStub; @@ -36,13 +39,11 @@ describe('Azerion Edge RTD submodule', function () { let returned; beforeEach(function () { - returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, ignoreConsent); + returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, userConsent); }); it('should have the correct gvlid', () => { - expect(azerionedgeRTD.azerionedgeSubmodule.gvlid).to.equal( - IMPROVEDIGITAL_GVLID - ); + expect(azerionedgeRTD.azerionedgeSubmodule.gvlid).to.equal(IMPROVEDIGITAL_GVLID); }); it('should return true', function () { @@ -58,18 +59,21 @@ describe('Azerion Edge RTD submodule', function () { expect(loadExternalScript.args[0][0]).to.deep.equal(expected); }); - it('should call azerionPublisherAudiencesStub with empty configuration', function () { - expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal({}); + [ + ['gdprApplies', userConsent.gdpr.gdprApplies], + ['gdprConsent', userConsent.gdpr.consentString], + ['uspConsent', userConsent.usp], + ].forEach(([key, value]) => { + it(`should call azerionPublisherAudiencesStub with ${key}:${value}`, function () { + expect(window.azerionPublisherAudiences.args[0][0]).to.include({[key]: value}); + }); }); describe('with key', function () { beforeEach(function () { - window.azerionPublisherAudiences.resetHistory(); - loadExternalScript.resetHistory(); - returned = azerionedgeRTD.azerionedgeSubmodule.init({ - ...dataProvider, - params: { key }, - }, ignoreConsent); + resetAll(); + const config = { ...dataProvider, params: { key } }; + returned = azerionedgeRTD.azerionedgeSubmodule.init(config, userConsent); }); it('should return true', function () { @@ -84,120 +88,23 @@ describe('Azerion Edge RTD submodule', function () { describe('with process configuration', function () { beforeEach(function () { - window.azerionPublisherAudiences.resetHistory(); - loadExternalScript.resetHistory(); - returned = azerionedgeRTD.azerionedgeSubmodule.init({ - ...dataProvider, - params: { process }, - }, ignoreConsent); + resetAll(); + const config = { ...dataProvider, params: { process } }; + returned = azerionedgeRTD.azerionedgeSubmodule.init(config, userConsent); }); it('should return true', function () { expect(returned).to.equal(true); }); - it('should call azerionPublisherAudiencesStub with process configuration', function () { - expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal( - process - ); - }); - }); - }); - - describe('GDPR access', () => { - const vendorConsented = { '253': true } - const purposesConsented = {'1': true, '3': true, '5': true, '7': true, '9': true}; - const partialPurposesConsented = {'1': true, '3': true, '5': true, '7': true}; - const tcfConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented }, purpose: { consents: purposesConsented } } }; - const tcfVendorNotConsented = { gdprApplies: true, vendorData: { purpose: {consents: purposesConsented} } }; - const tcfPurposesNotConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented } } }; - const tcfPartialPurposesNotConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented }, purpose: { consents: partialPurposesConsented } } }; - - [ - ['not applicable', tcfGDPRNotApplicable, true], - ['tcf consented', tcfConsented, true], - ['tcf vendor not consented', tcfVendorNotConsented, false], - ['tcf purposes not consented', tcfPurposesNotConsented, false], - ['tcp partial purposes not consented', tcfPartialPurposesNotConsented, false], - ].forEach(([info, gdpr, expected]) => { - it(`for ${info} should return ${expected}`, () => { - expect(azerionedgeRTD.hasGDPRAccess({gdpr})).to.equal(expected); - }); - - it(`for ${info} should load=${expected} the external script`, () => { - azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, {gdpr, usp: uspNotProvided}); - expect(loadExternalScript.called).to.equal(expected); - }); - - describe('for bid request data', function () { - let callbackStub; - - beforeEach(function () { - callbackStub = sinon.mock(); - azerionedgeRTD.azerionedgeSubmodule.getBidRequestData(reqBidsConfigObj, callbackStub, dataProvider, {gdpr, usp: uspNotProvided}); - }); - - it(`does call=${expected} the local storage looking for audiences`, function () { - expect(storageStub.called).to.equal(expected); - }); - - it('calls callback always', function () { - expect(callbackStub.called).to.be.true; - }); - }); - }); - }); - - describe('USP acccess', () => { - const uspMalformed = -1; - const uspNotApplicable = '1---'; - const uspUserNotifiedOptedOut = '1YY-'; - const uspUserNotifiedNotOptedOut = '1YN-'; - const uspUserNotifiedUnknownOptedOut = '1Y--'; - const uspUserNotNotifiedOptedOut = '1NY-'; - const uspUserNotNotifiedNotOptedOut = '1NN-'; - const uspUserNotNotifiedUnknownOptedOut = '1N--'; - const uspUserUnknownNotifiedOptedOut = '1-Y-'; - const uspUserUnknownNotifiedNotOptedOut = '1-N-'; - const uspUserUnknownNotifiedUnknownOptedOut = '1---'; - - [ - ['malformed', uspMalformed, true], - ['not applicable', uspNotApplicable, true], - ['not provided', uspNotProvided, true], - ['user notified and opted out', uspUserNotifiedOptedOut, false], - ['user notified and not opted out', uspUserNotifiedNotOptedOut, true], - ['user notified and unknown opted out', uspUserNotifiedUnknownOptedOut, true], - ['user not notified and opted out', uspUserNotNotifiedOptedOut, false], - ['user not notified and not opted out', uspUserNotNotifiedNotOptedOut, false], - ['user not notified and unknown opted out', uspUserNotNotifiedUnknownOptedOut, false], - ['user unknown notified and opted out', uspUserUnknownNotifiedOptedOut, false], - ['user unknown notified and not opted out', uspUserUnknownNotifiedNotOptedOut, true], - ['user unknown notified and unknown opted out', uspUserUnknownNotifiedUnknownOptedOut, true], - ].forEach(([info, usp, expected]) => { - it(`for ${info} should return ${expected}`, () => { - expect(azerionedgeRTD.hasUSPAccess({usp})).to.equal(expected); - }); - - it(`for ${info} should load=${expected} the external script`, () => { - azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, {gdpr: tcfGDPRNotApplicable, usp}); - expect(loadExternalScript.called).to.equal(expected); - }); - - describe('for bid request data', function () { - let callbackStub; - - beforeEach(function () { - callbackStub = sinon.mock(); - azerionedgeRTD.azerionedgeSubmodule.getBidRequestData(reqBidsConfigObj, callbackStub, dataProvider, {gdpr: tcfGDPRNotApplicable, usp}); - }); - - it(`does call=${expected} the local storage looking for audiences`, function () { - expect(storageStub.called).to.equal(expected); - }); - - it('calls callback always', function () { - expect(callbackStub.called).to.be.true; + [ + ['gdprApplies', userConsent.gdpr.gdprApplies], + ['gdprConsent', userConsent.gdpr.consentString], + ['uspConsent', userConsent.usp], + ...Object.entries(process), + ].forEach(([key, value]) => { + it(`should call azerionPublisherAudiencesStub with ${key}:${value}`, function () { + expect(window.azerionPublisherAudiences.args[0][0]).to.include({[key]: value}); }); }); });