From eb3c345039f376c72b9772ff6332256c07d6ee75 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 6 Jul 2022 10:23:04 -0700 Subject: [PATCH] Revert "Gdpr Enforcement module and sharedId/pubCommonId modules: vendor consent should not be enforced for first-party-id modules (#8448)" (#8648) This reverts commit 5ece4bb0eeea95f09f0560cf6882af8491b30620. --- modules/gdprEnforcement.js | 22 ++-- modules/ixBidAdapter.js | 3 +- modules/pubCommonId.js | 3 +- modules/sharedIdSystem.js | 10 +- src/storageManager.js | 9 +- test/spec/modules/gdprEnforcement_spec.js | 153 ++++++++-------------- 6 files changed, 72 insertions(+), 128 deletions(-) diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index c29d216509f..9e300e1de97 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -18,8 +18,6 @@ const TCF2 = { 'purpose7': { id: 7, name: 'measurement' } } -const VENDORLESS_MODULE_TYPES = ['fpid-module']; - /* These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher. */ @@ -125,10 +123,9 @@ function getGvlidForAnalyticsAdapter(code) { * @param {Object} consentData - gdpr consent data * @param {string=} currentModule - Bidder code of the current module * @param {number=} gvlId - GVL ID for the module - * @param {string=} moduleType module type * @returns {boolean} */ -export function validateRules(rule, consentData, currentModule, gvlId, moduleType) { +export function validateRules(rule, consentData, currentModule, gvlId) { const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id; // return 'true' if vendor present in 'vendorExceptions' @@ -141,14 +138,12 @@ export function validateRules(rule, consentData, currentModule, gvlId, moduleTyp const vendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); const liTransparency = deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`); - const vendorlessModule = includes(VENDORLESS_MODULE_TYPES, moduleType); - /* Since vendor exceptions have already been handled, the purpose as a whole is allowed if it's not being enforced or the user has consented. Similar with vendors. */ const purposeAllowed = rule.enforcePurpose === false || purposeConsent === true; - const vendorAllowed = rule.enforceVendor === false || vendorConsent === true || vendorlessModule === true; + const vendorAllowed = rule.enforceVendor === false || vendorConsent === true; /* Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming @@ -167,16 +162,15 @@ export function validateRules(rule, consentData, currentModule, gvlId, moduleTyp * @param {Function} fn reference to original function (used by hook logic) * @param {Number=} gvlid gvlid of the module * @param {string=} moduleName name of the module - * @param {string=} moduleType module type */ -export function deviceAccessHook(fn, gvlid, moduleName, moduleType, result) { +export function deviceAccessHook(fn, gvlid, moduleName, result) { result = Object.assign({}, { hasEnforcementHook: true }); if (!hasDeviceAccess()) { logWarn('Device access is disabled by Publisher'); result.valid = false; - fn.call(this, gvlid, moduleName, moduleType, result); + fn.call(this, gvlid, moduleName, result); } else { const consentData = gdprDataHandler.getConsentData(); if (consentData && consentData.gdprApplies) { @@ -188,19 +182,19 @@ export function deviceAccessHook(fn, gvlid, moduleName, moduleType, result) { gvlid = getGvlid(moduleName) || gvlid; } const curModule = moduleName || curBidder; - let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid, moduleType); + let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid); if (isAllowed) { result.valid = true; - fn.call(this, gvlid, moduleName, moduleType, result); + fn.call(this, gvlid, moduleName, result); } else { curModule && logWarn(`TCF2 denied device access for ${curModule}`); result.valid = false; storageBlocked.push(curModule); - fn.call(this, gvlid, moduleName, moduleType, result); + fn.call(this, gvlid, moduleName, result); } } else { result.valid = true; - fn.call(this, gvlid, moduleName, moduleType, result); + fn.call(this, gvlid, moduleName, result); } } } diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index dc16a4c3843..3076dbb6436 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -30,7 +30,6 @@ import {Renderer} from '../src/Renderer.js'; const BIDDER_CODE = 'ix'; const ALIAS_BIDDER_CODE = 'roundel'; const GLOBAL_VENDOR_ID = 10; -const MODULE_TYPE = 'bid-adapter'; const SECURE_BID_URL = 'https://htlb.casalemedia.com/openrtb/pbjs'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]; const BANNER_ENDPOINT_VERSION = 7.2; @@ -1425,7 +1424,7 @@ function localStorageHandler(data) { hasEnforcementHook: false, valid: hasDeviceAccess() }; - validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, MODULE_TYPE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => { + validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => { if (permissions.valid) { storeErrorEventData(data); } diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index e88cf7b9e7d..e3fd21e7260 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -9,8 +9,7 @@ import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; -const MODULE_TYPE = 'fpid-module'; -const storage = getStorageManager({moduleType: MODULE_TYPE}); +const storage = getStorageManager({moduleName: 'pubCommonId'}); const ID_NAME = '_pubcid'; const OPTOUT_NAME = '_pubcid_optout'; diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 8ed5699ff22..99a34ae17f4 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -5,13 +5,12 @@ * @requires module:modules/userId */ -import {buildUrl, generateUUID, hasDeviceAccess, logInfo, parseUrl, triggerPixel} from '../src/utils.js'; +import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID } from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import {coppaDataHandler} from '../src/adapterManager.js'; +import { coppaDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; -const MODULE_TYPE = 'fpid-module'; -export const storage = getStorageManager({moduleName: 'pubCommonId', moduleType: MODULE_TYPE}); +export const storage = getStorageManager({moduleName: 'pubCommonId'}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; @@ -88,7 +87,8 @@ export const sharedIdSystemSubmodule = { return undefined; } logInfo(' Decoded value PubCommonId ' + value); - return {'pubcid': value}; + const idObj = {'pubcid': value}; + return idObj; }, /** * performs action to obtain id diff --git a/src/storageManager.js b/src/storageManager.js index c194ea49109..e703e0774d3 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -48,7 +48,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = let hookDetails = { hasEnforcementHook: false } - validateStorageEnforcement(gvlid, bidderCode || moduleName, moduleType, hookDetails, function(result) { + validateStorageEnforcement(gvlid, bidderCode || moduleName, hookDetails, function(result) { if (result && result.hasEnforcementHook) { value = cb(result); } else { @@ -303,7 +303,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = /** * This hook validates the storage enforcement if gdprEnforcement module is included */ -export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, moduleType, hookDetails, callback) { +export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, hookDetails, callback) { callback(hookDetails); }, 'validateStorageEnforcement'); @@ -322,13 +322,12 @@ export function getCoreStorageManager(moduleName) { * @param {Number=} gvlid? Vendor id - required for proper GDPR integration * @param {string=} bidderCode? - required for bid adapters * @param {string=} moduleName? module name - * @param {string=} moduleType? module type */ -export function getStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}) { +export function getStorageManager({gvlid, moduleName, bidderCode} = {}) { if (arguments.length > 1 || (arguments.length > 0 && !isPlainObject(arguments[0]))) { throw new Error('Invalid invocation for getStorageManager') } - return newStorageManager({gvlid, moduleName, bidderCode, moduleType}); + return newStorageManager({gvlid, moduleName, bidderCode}); } export function resetData() { diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index e74b4e82263..a78f5ac948e 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,21 +1,21 @@ import { deviceAccessHook, - enableAnalyticsHook, - enforcementRules, - getGvlid, - internal, + setEnforcementConfig, + userSyncHook, + userIdHook, makeBidRequestsHook, + validateRules, + enforcementRules, purpose1Rule, purpose2Rule, - setEnforcementConfig, - userIdHook, - userSyncHook, - validateRules + enableAnalyticsHook, + getGvlid, + internal } from 'modules/gdprEnforcement.js'; -import {config} from 'src/config.js'; -import adapterManager, {gdprDataHandler} from 'src/adapterManager.js'; +import { config } from 'src/config.js'; +import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import {validateStorageEnforcement} from 'src/storageManager.js'; +import { validateStorageEnforcement } from 'src/storageManager.js'; import * as events from 'src/events.js'; describe('gdpr enforcement', function () { @@ -98,9 +98,9 @@ describe('gdpr enforcement', function () { }; after(function () { - validateStorageEnforcement.getHooks({hook: deviceAccessHook}).remove(); + validateStorageEnforcement.getHooks({ hook: deviceAccessHook }).remove(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); - adapterManager.makeBidRequests.getHooks({hook: makeBidRequestsHook}).remove(); + adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); }) describe('deviceAccessHook', function () { @@ -149,7 +149,7 @@ describe('gdpr enforcement', function () { hasEnforcementHook: true, valid: false } - sinon.assert.calledWith(nextFnSpy, undefined, undefined, undefined, result); + sinon.assert.calledWith(nextFnSpy, undefined, undefined, result); }); it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { @@ -199,53 +199,6 @@ describe('gdpr enforcement', function () { expect(logWarnSpy.callCount).to.equal(1); }); - it('should allow device access when enforce vendor but module is vendorless ', function () { - adapterManagerStub.withArgs('pubCommonId').returns(getBidderSpec(1)); - setEnforcementConfig({ - gdpr: { - rules: [{ - purpose: 'storage', - enforcePurpose: true, - enforceVendor: true, - }] - } - }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.gdprApplies = true; - consentData.apiVersion = 2; - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'pubCommonId', 'fpid-module'); - expect(logWarnSpy.callCount).to.equal(0); - }); - - it('should not allow device access if enforce vendor, module is vendorless, but there is no consent for purpose 1', function () { - adapterManagerStub.withArgs('pubCommonId').returns(getBidderSpec(1)); - setEnforcementConfig({ - gdpr: { - rules: [{ - purpose: 'storage', - enforcePurpose: true, - enforceVendor: true, - }] - } - }); - - let consentData = {} - // set consent for purpose 1 to false - const newConsentData = utils.deepClone(staticConfig); - newConsentData.consentData.getTCData.purpose.consents['1'] = false; - consentData.vendorData = newConsentData.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; - - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'pubCommonId', 'fpid-module'); - expect(logWarnSpy.callCount).to.equal(1); - }); - it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function () { adapterManagerStub.withArgs('appnexus').returns(getBidderSpec(1)); setEnforcementConfig({ @@ -270,10 +223,10 @@ describe('gdpr enforcement', function () { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', undefined, result); + sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); }); - it('should use gvlMapping set by publisher', function () { + it('should use gvlMapping set by publisher', function() { config.setConfig({ 'gvlMapping': { 'appnexus': 4 @@ -301,11 +254,11 @@ describe('gdpr enforcement', function () { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', undefined, result); + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); config.resetConfig(); }); - it('should use gvl id of alias and not of parent', function () { + it('should use gvl id of alias and not of parent', function() { let curBidderStub = sinon.stub(config, 'getCurrentBidder'); curBidderStub.returns('appnexus-alias'); adapterManager.aliasBidAdapter('appnexus', 'appnexus-alias'); @@ -336,7 +289,7 @@ describe('gdpr enforcement', function () { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', undefined, result); + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); config.resetConfig(); curBidderStub.restore(); }); @@ -517,7 +470,7 @@ describe('gdpr enforcement', function () { const args = nextFnSpy.getCalls()[0].args; expect(args[1].hasValidated).to.be.true; expect(nextFnSpy.calledOnce).to.equal(true); - sinon.assert.calledWith(nextFnSpy, submodules, {...consentData, hasValidated: true}); + sinon.assert.calledWith(nextFnSpy, submodules, { ...consentData, hasValidated: true }); }); it('should allow userId module if gdpr not in scope', function () { @@ -571,7 +524,7 @@ describe('gdpr enforcement', function () { name: 'sampleUserId' } }] - sinon.assert.calledWith(nextFnSpy, expectedSubmodules, {...consentData, hasValidated: true}); + sinon.assert.calledWith(nextFnSpy, expectedSubmodules, { ...consentData, hasValidated: true }); }); }); @@ -630,17 +583,17 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); adapterManagerStub.withArgs('bidder_1').returns({ getSpec: function () { - return {'gvlid': 4} + return { 'gvlid': 4 } } }); adapterManagerStub.withArgs('bidder_2').returns({ getSpec: function () { - return {'gvlid': 5} + return { 'gvlid': 5 } } }); adapterManagerStub.withArgs('bidder_3').returns({ getSpec: function () { - return {'gvlid': undefined} + return { 'gvlid': undefined } } }); makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); @@ -651,20 +604,20 @@ describe('gdpr enforcement', function () { code: 'ad-unit-1', mediaTypes: {}, bids: [ - sinon.match({bidder: 'bidder_1'}), - sinon.match({bidder: 'bidder_2'}) + sinon.match({ bidder: 'bidder_1' }), + sinon.match({ bidder: 'bidder_2' }) ] }, { code: 'ad-unit-2', mediaTypes: {}, bids: [ - sinon.match({bidder: 'bidder_2'}), - sinon.match({bidder: 'bidder_3'}) // should be allowed even though it's doesn't have a gvlId because liTransparency is established. + sinon.match({ bidder: 'bidder_2' }), + sinon.match({ bidder: 'bidder_3' }) // should be allowed even though it's doesn't have a gvlId because liTransparency is established. ] }], []); }); - it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function () { + it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function() { setEnforcementConfig({ gdpr: { rules: [{ @@ -688,17 +641,17 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); adapterManagerStub.withArgs('bidder_1').returns({ getSpec: function () { - return {'gvlid': 4} + return { 'gvlid': 4 } } }); adapterManagerStub.withArgs('bidder_2').returns({ getSpec: function () { - return {'gvlid': 5} + return { 'gvlid': 5 } } }); adapterManagerStub.withArgs('bidder_3').returns({ getSpec: function () { - return {'gvlid': undefined} + return { 'gvlid': undefined } } }); @@ -710,13 +663,13 @@ describe('gdpr enforcement', function () { code: 'ad-unit-1', mediaTypes: {}, bids: [ - sinon.match({bidder: 'bidder_1'}), // 'bidder_2' is not present because it doesn't have vendorConsent + sinon.match({ bidder: 'bidder_1' }), // 'bidder_2' is not present because it doesn't have vendorConsent ] }, { code: 'ad-unit-2', mediaTypes: {}, bids: [ - sinon.match({bidder: 'bidder_3'}), // 'bidder_3' is allowed despite gvlId being undefined because it's part of vendorExceptions + sinon.match({ bidder: 'bidder_3' }), // 'bidder_3' is allowed despite gvlId being undefined because it's part of vendorExceptions ] }], []); @@ -774,12 +727,12 @@ describe('gdpr enforcement', function () { nextFnSpy = sandbox.spy(); }); - afterEach(function () { + afterEach(function() { config.resetConfig(); sandbox.restore(); }); - it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function () { + it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function() { setEnforcementConfig({ gdpr: { rules: [{ @@ -797,9 +750,9 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('analyticsAdapter_A').returns({gvlid: 3}); - adapterManagerStub.withArgs('analyticsAdapter_B').returns({gvlid: 5}); - adapterManagerStub.withArgs('analyticsAdapter_C').returns({gvlid: 1}); + adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); + adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); + adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); @@ -1072,7 +1025,7 @@ describe('gdpr enforcement', function () { expect(purpose2Rule).to.deep.equal(purpose2RuleDefinedInConfig); }); - it('should use the "rules" defined in config if a definition found', function () { + it('should use the "rules" defined in config if a definition found', function() { const rules = [{ purpose: 'storage', enforcePurpose: false, @@ -1082,23 +1035,23 @@ describe('gdpr enforcement', function () { enforcePurpose: false, enforceVendor: false }] - setEnforcementConfig({gdpr: {rules}}); + setEnforcementConfig({gdpr: { rules }}); expect(enforcementRules).to.deep.equal(rules); }); }); - describe('TCF2FinalResults', function () { + describe('TCF2FinalResults', function() { let sandbox; - beforeEach(function () { + beforeEach(function() { sandbox = sinon.createSandbox(); sandbox.spy(events, 'emit'); }); - afterEach(function () { + afterEach(function() { config.resetConfig(); sandbox.restore(); }); - it('should emit TCF2 enforcement data on auction end', function () { + it('should emit TCF2 enforcement data on auction end', function() { const rules = [{ purpose: 'storage', enforcePurpose: false, @@ -1108,7 +1061,7 @@ describe('gdpr enforcement', function () { enforcePurpose: false, enforceVendor: false }] - setEnforcementConfig({gdpr: {rules}}); + setEnforcementConfig({gdpr: { rules }}); events.emit('auctionEnd', {}) @@ -1117,28 +1070,28 @@ describe('gdpr enforcement', function () { }) }); - describe('getGvlid', function () { + describe('getGvlid', function() { let sandbox; let getGvlidForBidAdapterStub; let getGvlidForUserIdModuleStub; let getGvlidForAnalyticsAdapterStub; - beforeEach(function () { + beforeEach(function() { sandbox = sinon.createSandbox(); getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); }); - afterEach(function () { + afterEach(function() { sandbox.restore(); config.resetConfig(); }); - it('should return "null" if called without passing any argument', function () { + it('should return "null" if called without passing any argument', function() { const gvlid = getGvlid(); expect(gvlid).to.equal(null); }); - it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function () { + it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); @@ -1147,7 +1100,7 @@ describe('gdpr enforcement', function () { expect(gvlid).to.equal(null); }); - it('should return the GVL ID from gvlMapping if it is defined in setConfig', function () { + it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { config.setConfig({ gvlMapping: { moduleA: 1 @@ -1161,7 +1114,7 @@ describe('gdpr enforcement', function () { expect(gvlid).to.equal(1); }); - it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function () { + it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7);