Skip to content

Commit 5ece4bb

Browse files
goldunsholdun
andauthored
Gdpr Enforcement module and sharedId/pubCommonId modules: vendor consent should not be enforced for first-party-id modules (prebid#8448)
* Fixed issue with gdprEnforcement module and sharedId/pubCommonId modules: vendor consent should not be enforced for first-party-id modules * addressed review comments * addressed review comments * added test to ensure device access is not allowed for vendorless modules in case purpose 1 consent isn't given * fixed issue with missing moduleType param Co-authored-by: Serhii Holdun <[email protected]>
1 parent f97f3df commit 5ece4bb

File tree

6 files changed

+129
-79
lines changed

6 files changed

+129
-79
lines changed

modules/gdprEnforcement.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const TCF2 = {
1818
'purpose7': { id: 7, name: 'measurement' }
1919
}
2020

21+
const VENDORLESS_MODULE_TYPES = ['fpid-module'];
22+
2123
/*
2224
These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher.
2325
*/
@@ -123,9 +125,10 @@ function getGvlidForAnalyticsAdapter(code) {
123125
* @param {Object} consentData - gdpr consent data
124126
* @param {string=} currentModule - Bidder code of the current module
125127
* @param {number=} gvlId - GVL ID for the module
128+
* @param {string=} moduleType module type
126129
* @returns {boolean}
127130
*/
128-
export function validateRules(rule, consentData, currentModule, gvlId) {
131+
export function validateRules(rule, consentData, currentModule, gvlId, moduleType) {
129132
const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id;
130133

131134
// return 'true' if vendor present in 'vendorExceptions'
@@ -138,12 +141,14 @@ export function validateRules(rule, consentData, currentModule, gvlId) {
138141
const vendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`);
139142
const liTransparency = deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`);
140143

144+
const vendorlessModule = includes(VENDORLESS_MODULE_TYPES, moduleType);
145+
141146
/*
142147
Since vendor exceptions have already been handled, the purpose as a whole is allowed if it's not being enforced
143148
or the user has consented. Similar with vendors.
144149
*/
145150
const purposeAllowed = rule.enforcePurpose === false || purposeConsent === true;
146-
const vendorAllowed = rule.enforceVendor === false || vendorConsent === true;
151+
const vendorAllowed = rule.enforceVendor === false || vendorConsent === true || vendorlessModule === true;
147152

148153
/*
149154
Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming
@@ -162,15 +167,16 @@ export function validateRules(rule, consentData, currentModule, gvlId) {
162167
* @param {Function} fn reference to original function (used by hook logic)
163168
* @param {Number=} gvlid gvlid of the module
164169
* @param {string=} moduleName name of the module
170+
* @param {string=} moduleType module type
165171
*/
166-
export function deviceAccessHook(fn, gvlid, moduleName, result) {
172+
export function deviceAccessHook(fn, gvlid, moduleName, moduleType, result) {
167173
result = Object.assign({}, {
168174
hasEnforcementHook: true
169175
});
170176
if (!hasDeviceAccess()) {
171177
logWarn('Device access is disabled by Publisher');
172178
result.valid = false;
173-
fn.call(this, gvlid, moduleName, result);
179+
fn.call(this, gvlid, moduleName, moduleType, result);
174180
} else {
175181
const consentData = gdprDataHandler.getConsentData();
176182
if (consentData && consentData.gdprApplies) {
@@ -183,24 +189,24 @@ export function deviceAccessHook(fn, gvlid, moduleName, result) {
183189
gvlid = getGvlid(moduleName) || gvlid;
184190
}
185191
const curModule = moduleName || curBidder;
186-
let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid);
192+
let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid, moduleType);
187193
if (isAllowed) {
188194
result.valid = true;
189-
fn.call(this, gvlid, moduleName, result);
195+
fn.call(this, gvlid, moduleName, moduleType, result);
190196
} else {
191197
curModule && logWarn(`TCF2 denied device access for ${curModule}`);
192198
result.valid = false;
193199
storageBlocked.push(curModule);
194-
fn.call(this, gvlid, moduleName, result);
200+
fn.call(this, gvlid, moduleName, moduleType, result);
195201
}
196202
} else {
197203
// The module doesn't enforce TCF1.1 strings
198204
result.valid = true;
199-
fn.call(this, gvlid, moduleName, result);
205+
fn.call(this, gvlid, moduleName, moduleType, result);
200206
}
201207
} else {
202208
result.valid = true;
203-
fn.call(this, gvlid, moduleName, result);
209+
fn.call(this, gvlid, moduleName, moduleType, result);
204210
}
205211
}
206212
}

modules/ixBidAdapter.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {Renderer} from '../src/Renderer.js';
3030
const BIDDER_CODE = 'ix';
3131
const ALIAS_BIDDER_CODE = 'roundel';
3232
const GLOBAL_VENDOR_ID = 10;
33+
const MODULE_TYPE = 'bid-adapter';
3334
const SECURE_BID_URL = 'https://htlb.casalemedia.com/cygnus';
3435
const SUPPORTED_AD_TYPES = [BANNER, VIDEO];
3536
const BANNER_ENDPOINT_VERSION = 7.2;
@@ -1086,7 +1087,7 @@ function localStorageHandler(data) {
10861087
hasEnforcementHook: false,
10871088
valid: hasDeviceAccess()
10881089
};
1089-
validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => {
1090+
validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, MODULE_TYPE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => {
10901091
if (permissions.valid) {
10911092
storeErrorEventData(data);
10921093
}

modules/pubCommonId.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import * as events from '../src/events.js';
99
import CONSTANTS from '../src/constants.json';
1010
import { getStorageManager } from '../src/storageManager.js';
1111

12-
const storage = getStorageManager();
12+
const MODULE_TYPE = 'fpid-module';
13+
const storage = getStorageManager({moduleType: MODULE_TYPE});
1314

1415
const ID_NAME = '_pubcid';
1516
const OPTOUT_NAME = '_pubcid_optout';

modules/sharedIdSystem.js

+5-11
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
* @requires module:modules/userId
66
*/
77

8-
import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID } from '../src/utils.js';
8+
import {buildUrl, generateUUID, hasDeviceAccess, logInfo, parseUrl, triggerPixel} from '../src/utils.js';
99
import {submodule} from '../src/hook.js';
10-
import { coppaDataHandler } from '../src/adapterManager.js';
10+
import {coppaDataHandler} from '../src/adapterManager.js';
1111
import {getStorageManager} from '../src/storageManager.js';
1212

13-
const GVLID = 887;
14-
export const storage = getStorageManager({gvlid: GVLID, moduleName: 'pubCommonId'});
13+
const MODULE_TYPE = 'fpid-module';
14+
export const storage = getStorageManager({moduleName: 'pubCommonId', moduleType: MODULE_TYPE});
1515
const COOKIE = 'cookie';
1616
const LOCAL_STORAGE = 'html5';
1717
const OPTOUT_NAME = '_pubcid_optout';
@@ -74,11 +74,6 @@ export const sharedIdSystemSubmodule = {
7474
*/
7575
name: 'sharedId',
7676
aliasName: 'pubCommonId',
77-
/**
78-
* Vendor id of prebid
79-
* @type {Number}
80-
*/
81-
gvlid: GVLID,
8277

8378
/**
8479
* decode the stored id value for passing to bid requests
@@ -93,8 +88,7 @@ export const sharedIdSystemSubmodule = {
9388
return undefined;
9489
}
9590
logInfo(' Decoded value PubCommonId ' + value);
96-
const idObj = {'pubcid': value};
97-
return idObj;
91+
return {'pubcid': value};
9892
},
9993
/**
10094
* performs action to obtain id

src/storageManager.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} =
4848
let hookDetails = {
4949
hasEnforcementHook: false
5050
}
51-
validateStorageEnforcement(gvlid, bidderCode || moduleName, hookDetails, function(result) {
51+
validateStorageEnforcement(gvlid, bidderCode || moduleName, moduleType, hookDetails, function(result) {
5252
if (result && result.hasEnforcementHook) {
5353
value = cb(result);
5454
} else {
@@ -303,7 +303,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} =
303303
/**
304304
* This hook validates the storage enforcement if gdprEnforcement module is included
305305
*/
306-
export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, hookDetails, callback) {
306+
export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, moduleType, hookDetails, callback) {
307307
callback(hookDetails);
308308
}, 'validateStorageEnforcement');
309309

@@ -322,12 +322,13 @@ export function getCoreStorageManager(moduleName) {
322322
* @param {Number=} gvlid? Vendor id - required for proper GDPR integration
323323
* @param {string=} bidderCode? - required for bid adapters
324324
* @param {string=} moduleName? module name
325+
* @param {string=} moduleType? module type
325326
*/
326-
export function getStorageManager({gvlid, moduleName, bidderCode} = {}) {
327+
export function getStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}) {
327328
if (arguments.length > 1 || (arguments.length > 0 && !isPlainObject(arguments[0]))) {
328329
throw new Error('Invalid invocation for getStorageManager')
329330
}
330-
return newStorageManager({gvlid, moduleName, bidderCode});
331+
return newStorageManager({gvlid, moduleName, bidderCode, moduleType});
331332
}
332333

333334
export function resetData() {

0 commit comments

Comments
 (0)