Skip to content

Commit

Permalink
Gdpr Enforcement module and sharedId/pubCommonId modules: vendor cons…
Browse files Browse the repository at this point in the history
…ent 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 <sholdun@magnite.com>
  • Loading branch information
goldun and sholdun authored Jun 2, 2022
1 parent f97f3df commit 5ece4bb
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 79 deletions.
24 changes: 15 additions & 9 deletions modules/gdprEnforcement.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ 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.
*/
Expand Down Expand Up @@ -123,9 +125,10 @@ 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) {
export function validateRules(rule, consentData, currentModule, gvlId, moduleType) {
const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id;

// return 'true' if vendor present in 'vendorExceptions'
Expand All @@ -138,12 +141,14 @@ export function validateRules(rule, consentData, currentModule, gvlId) {
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;
const vendorAllowed = rule.enforceVendor === false || vendorConsent === true || vendorlessModule === true;

/*
Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming
Expand All @@ -162,15 +167,16 @@ export function validateRules(rule, consentData, currentModule, gvlId) {
* @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, result) {
export function deviceAccessHook(fn, gvlid, moduleName, moduleType, result) {
result = Object.assign({}, {
hasEnforcementHook: true
});
if (!hasDeviceAccess()) {
logWarn('Device access is disabled by Publisher');
result.valid = false;
fn.call(this, gvlid, moduleName, result);
fn.call(this, gvlid, moduleName, moduleType, result);
} else {
const consentData = gdprDataHandler.getConsentData();
if (consentData && consentData.gdprApplies) {
Expand All @@ -183,24 +189,24 @@ export function deviceAccessHook(fn, gvlid, moduleName, result) {
gvlid = getGvlid(moduleName) || gvlid;
}
const curModule = moduleName || curBidder;
let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid);
let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid, moduleType);
if (isAllowed) {
result.valid = true;
fn.call(this, gvlid, moduleName, result);
fn.call(this, gvlid, moduleName, moduleType, result);
} else {
curModule && logWarn(`TCF2 denied device access for ${curModule}`);
result.valid = false;
storageBlocked.push(curModule);
fn.call(this, gvlid, moduleName, result);
fn.call(this, gvlid, moduleName, moduleType, result);
}
} else {
// The module doesn't enforce TCF1.1 strings
result.valid = true;
fn.call(this, gvlid, moduleName, result);
fn.call(this, gvlid, moduleName, moduleType, result);
}
} else {
result.valid = true;
fn.call(this, gvlid, moduleName, result);
fn.call(this, gvlid, moduleName, moduleType, result);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion modules/ixBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ 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/cygnus';
const SUPPORTED_AD_TYPES = [BANNER, VIDEO];
const BANNER_ENDPOINT_VERSION = 7.2;
Expand Down Expand Up @@ -1086,7 +1087,7 @@ function localStorageHandler(data) {
hasEnforcementHook: false,
valid: hasDeviceAccess()
};
validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => {
validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, MODULE_TYPE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => {
if (permissions.valid) {
storeErrorEventData(data);
}
Expand Down
3 changes: 2 additions & 1 deletion modules/pubCommonId.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import * as events from '../src/events.js';
import CONSTANTS from '../src/constants.json';
import { getStorageManager } from '../src/storageManager.js';

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

const ID_NAME = '_pubcid';
const OPTOUT_NAME = '_pubcid_optout';
Expand Down
16 changes: 5 additions & 11 deletions modules/sharedIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
* @requires module:modules/userId
*/

import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID } from '../src/utils.js';
import {buildUrl, generateUUID, hasDeviceAccess, logInfo, parseUrl, triggerPixel} 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 GVLID = 887;
export const storage = getStorageManager({gvlid: GVLID, moduleName: 'pubCommonId'});
const MODULE_TYPE = 'fpid-module';
export const storage = getStorageManager({moduleName: 'pubCommonId', moduleType: MODULE_TYPE});
const COOKIE = 'cookie';
const LOCAL_STORAGE = 'html5';
const OPTOUT_NAME = '_pubcid_optout';
Expand Down Expand Up @@ -74,11 +74,6 @@ export const sharedIdSystemSubmodule = {
*/
name: 'sharedId',
aliasName: 'pubCommonId',
/**
* Vendor id of prebid
* @type {Number}
*/
gvlid: GVLID,

/**
* decode the stored id value for passing to bid requests
Expand All @@ -93,8 +88,7 @@ export const sharedIdSystemSubmodule = {
return undefined;
}
logInfo(' Decoded value PubCommonId ' + value);
const idObj = {'pubcid': value};
return idObj;
return {'pubcid': value};
},
/**
* performs action to obtain id
Expand Down
9 changes: 5 additions & 4 deletions src/storageManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} =
let hookDetails = {
hasEnforcementHook: false
}
validateStorageEnforcement(gvlid, bidderCode || moduleName, hookDetails, function(result) {
validateStorageEnforcement(gvlid, bidderCode || moduleName, moduleType, hookDetails, function(result) {
if (result && result.hasEnforcementHook) {
value = cb(result);
} else {
Expand Down Expand Up @@ -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, hookDetails, callback) {
export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, moduleType, hookDetails, callback) {
callback(hookDetails);
}, 'validateStorageEnforcement');

Expand All @@ -322,12 +322,13 @@ 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} = {}) {
export function getStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}) {
if (arguments.length > 1 || (arguments.length > 0 && !isPlainObject(arguments[0]))) {
throw new Error('Invalid invocation for getStorageManager')
}
return newStorageManager({gvlid, moduleName, bidderCode});
return newStorageManager({gvlid, moduleName, bidderCode, moduleType});
}

export function resetData() {
Expand Down
Loading

0 comments on commit 5ece4bb

Please sign in to comment.