diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 4bac4b0cf74..fa7418a90ab 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -27,15 +27,16 @@ import { ACTIVITY_ENRICH_EIDS, ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS, - ACTIVITY_SYNC_USER + ACTIVITY_SYNC_USER, ACTIVITY_TRANSMIT_UFPD } from '../src/activities/activities.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; const TCF2 = { - 'purpose1': {id: 1, name: 'storage'}, - 'purpose2': {id: 2, name: 'basicAds'}, - 'purpose7': {id: 7, name: 'measurement'} + purpose1: {id: 1, name: 'storage'}, + purpose2: {id: 2, name: 'basicAds'}, + purpose4: {id: 4, name: 'personalizedAds'}, + purpose7: {id: 7, name: 'measurement'}, }; /* @@ -55,6 +56,7 @@ const DEFAULT_RULES = [{ export let purpose1Rule; export let purpose2Rule; +export let purpose4Rule; export let purpose7Rule; export let enforcementRules; @@ -62,6 +64,7 @@ export let enforcementRules; const storageBlocked = new Set(); const biddersBlocked = new Set(); const analyticsBlocked = new Set(); +const ufpdBlocked = new Set(); let hooksAdded = false; let strictStorageEnforcement = false; @@ -232,6 +235,8 @@ export const fetchBidsRule = ((rule) => { export const reportAnalyticsRule = gdprRule(7, () => purpose7Rule, analyticsBlocked, (params) => getGvlidFromAnalyticsAdapter(params[ACTIVITY_PARAM_COMPONENT_NAME], params[ACTIVITY_PARAM_ANL_CONFIG])); +export const transmitUfpdRule = gdprRule(4, () => purpose4Rule, ufpdBlocked); + /** * Compiles the TCF2.0 enforcement results into an object, which is emitted as an event payload to "tcf2Enforcement" event. */ @@ -243,27 +248,20 @@ function emitTCF2FinalResults() { const tcf2FinalResults = { storageBlocked: formatSet(storageBlocked), biddersBlocked: formatSet(biddersBlocked), - analyticsBlocked: formatSet(analyticsBlocked) + analyticsBlocked: formatSet(analyticsBlocked), + ufpdBlocked: formatSet(ufpdBlocked), }; events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); - [storageBlocked, biddersBlocked, analyticsBlocked].forEach(el => el.clear()); + [storageBlocked, biddersBlocked, analyticsBlocked, ufpdBlocked].forEach(el => el.clear()); } events.on(CONSTANTS.EVENTS.AUCTION_END, emitTCF2FinalResults); -/* - Set of callback functions used to detect presence of a TCF rule, passed as the second argument to find(). -*/ -const hasPurpose1 = (rule) => { - return rule.purpose === TCF2.purpose1.name; -}; -const hasPurpose2 = (rule) => { - return rule.purpose === TCF2.purpose2.name; -}; -const hasPurpose7 = (rule) => { - return rule.purpose === TCF2.purpose7.name; -}; +function hasPurpose(purposeNo) { + const pname = TCF2[`purpose${purposeNo}`].name; + return (rule) => rule.purpose === pname; +} /** * A configuration function that initializes some module variables, as well as adds hooks @@ -279,9 +277,10 @@ export function setEnforcementConfig(config) { } strictStorageEnforcement = !!deepAccess(config, STRICT_STORAGE_ENFORCEMENT); - purpose1Rule = find(enforcementRules, hasPurpose1); - purpose2Rule = find(enforcementRules, hasPurpose2); - purpose7Rule = find(enforcementRules, hasPurpose7); + purpose1Rule = find(enforcementRules, hasPurpose(1)); + purpose2Rule = find(enforcementRules, hasPurpose(2)); + purpose4Rule = find(enforcementRules, hasPurpose(4)) + purpose7Rule = find(enforcementRules, hasPurpose(7)); if (!purpose1Rule) { purpose1Rule = DEFAULT_RULES[0]; @@ -301,6 +300,9 @@ export function setEnforcementConfig(config) { if (purpose2Rule) { RULE_HANDLES.push(registerActivityControl(ACTIVITY_FETCH_BIDS, RULE_NAME, fetchBidsRule)); } + if (purpose4Rule) { + RULE_HANDLES.push(registerActivityControl(ACTIVITY_TRANSMIT_UFPD, RULE_NAME, transmitUfpdRule)); + } if (purpose7Rule) { RULE_HANDLES.push(registerActivityControl(ACTIVITY_REPORT_ANALYTICS, RULE_NAME, reportAnalyticsRule)); } diff --git a/src/activities/redactor.js b/src/activities/redactor.js index d50df72648c..5942ee17152 100644 --- a/src/activities/redactor.js +++ b/src/activities/redactor.js @@ -8,7 +8,7 @@ import { ACTIVITY_TRANSMIT_UFPD } from './activities.js'; -export const ORTB_UFPD_PATHS = ['user.data', 'user.ext.data']; +export const ORTB_UFPD_PATHS = ['user.data', 'user.ext.data', 'user.yob', 'user.gender', 'user.keywords', 'user.kwarray']; export const ORTB_EIDS_PATHS = ['user.eids', 'user.ext.eids']; export const ORTB_GEO_PATHS = ['user.geo.lat', 'user.geo.lon', 'device.geo.lat', 'device.geo.lon']; diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 1585b8346ba..f571abcccea 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -11,7 +11,7 @@ import { reportAnalyticsRule, setEnforcementConfig, STRICT_STORAGE_ENFORCEMENT, - syncUserRule, + syncUserRule, transmitUfpdRule, validateRules } from 'modules/gdprEnforcement.js'; import {config} from 'src/config.js'; @@ -462,6 +462,46 @@ describe('gdpr enforcement', function () { }); }); + describe('transmitUfpdRule', () => { + it('should allow when purpose 3 consent is given', () => { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'personalizedAds', + enforcePurpose: true, + enforceVendor: true, + }] + } + }); + Object.assign(gvlids, { + mockBidder: 123 + }); + const consent = setupConsentData(); + consent.vendorData.purpose.consents[4] = true; + consent.vendorData.vendor.consents[123] = true; + expectAllow(true, transmitUfpdRule(activityParams(MODULE_TYPE_BIDDER, 'mockBidder'))); + }); + + it('should return deny when purpose 4 consent is withheld', () => { + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'personalizedAds', + enforcePurpose: true, + enforceVendor: true, + }] + } + }); + Object.assign(gvlids, { + mockBidder: 123 + }); + const consent = setupConsentData(); + consent.vendorData.purpose.consents[4] = true; + consent.vendorData.vendor.consents[123] = false; + expectAllow(false, transmitUfpdRule(activityParams(MODULE_TYPE_BIDDER, 'mockBidder'))) + }); + }); + describe('validateRules', function () { const createGdprRule = (purposeName = 'storage', enforcePurpose = true, enforceVendor = true, vendorExceptions = [], softVendorExceptions = []) => ({ purpose: purposeName, @@ -599,7 +639,8 @@ describe('gdpr enforcement', function () { Object.entries({ 'storage': 1, 'basicAds': 2, - 'measurement': 7 + 'measurement': 7, + 'personalizedAds': 4, }).forEach(([purpose, purposeNo]) => { describe(`for purpose ${purpose}`, () => { const rule = createGdprRule(purpose);