Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core & Multiple modules: activity controls #9802

Merged
merged 43 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ea44838
Core: allow restriction of cookies / localStorage through `bidderSett…
dgirardi Mar 13, 2023
d7c3f27
Add test cases
dgirardi Mar 13, 2023
05759cc
Remove gvlid param from storage manager logic
dgirardi Mar 29, 2023
7228769
Refactor every invocation of `getStorageManager`
dgirardi Mar 29, 2023
8ee280c
GVL ID registry
dgirardi Mar 29, 2023
0a58854
Refactor gdprEnforcement gvlid lookup
dgirardi Mar 30, 2023
c87a167
fix lint
dgirardi Mar 30, 2023
a157a64
Merge branch 'master' into no-gvlid-for-storage
dgirardi Mar 30, 2023
b0b9135
Remove empty file
dgirardi Mar 30, 2023
1795ab6
Undo https://github.com/prebid/Prebid.js/pull/9728 for realVu
dgirardi Mar 30, 2023
7fd468f
Merge branch 'master' into no-gvlid-for-storage
dgirardi Apr 4, 2023
995b42a
Fix typo
dgirardi Apr 4, 2023
72ec01b
Activity control rules
dgirardi Mar 28, 2023
7c4c15b
Rule un-registration
dgirardi Apr 4, 2023
0610dc2
fetchBids enforcement
dgirardi Apr 4, 2023
afa843a
fetchBids rule for gdpr
dgirardi Apr 4, 2023
52cbd40
enableAnalytics check
dgirardi Apr 4, 2023
93cce10
reportAnalytics TCF2 rule
dgirardi Apr 5, 2023
38f610a
Update logging condition for multiple GVL IDs
dgirardi Apr 5, 2023
167e44f
Change core to prebid
dgirardi Apr 5, 2023
a92d766
Refactor userID to use non-core storage manager when storing for subm…
dgirardi Apr 5, 2023
4a3b734
enrichEids check
dgirardi Apr 5, 2023
eeb3769
gdpr enforcement for enrichEids
dgirardi Apr 5, 2023
b58795a
syncUser activity check
dgirardi Apr 5, 2023
72b325c
gdpr enforcement for syncUser
dgirardi Apr 6, 2023
c9b4077
refactor gdprEnforcement
dgirardi Apr 6, 2023
f976ba7
storageManager activity checks
dgirardi Apr 6, 2023
886d091
gdpr enforcement for accessDevice
dgirardi Apr 6, 2023
103a6ca
move alias resolution logic to adapterManager
dgirardi Apr 6, 2023
806d78b
Refactor file structure to get around circular deps
dgirardi Apr 6, 2023
5dc478b
transmit(Eids/Ufpd/PreciseGeo) enforcement for bid adapters
dgirardi Apr 11, 2023
305f623
Object transformers and guards
dgirardi Apr 12, 2023
33d822a
transmit* and enrich* enforcement for RTD modules
dgirardi Apr 12, 2023
5d5338e
allowActivities configuration
dgirardi Apr 13, 2023
539db58
improve comments
dgirardi Apr 13, 2023
b78cf75
Merge branch 'master' into activity-controls
dgirardi Apr 13, 2023
64e0d4f
do not pass private activity params to pub-defined rules
dgirardi Apr 13, 2023
8ea2042
fix objectGuard edge case: null values
dgirardi Apr 13, 2023
86061c6
Merge branch 'master' into activity-controls
dgirardi May 16, 2023
71e9b5b
Merge branch 'master' into activity-controls
dgirardi May 23, 2023
6e0a850
move config logic into a module
dgirardi May 23, 2023
73f763a
dedupe log messages
dgirardi May 23, 2023
dfb113e
Merge branch 'master' into activity-controls
dgirardi May 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions libraries/objectGuard/objectGuard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {isData, objectTransformer} from '../../src/activities/redactor.js';
import {deepAccess, deepClone, deepEqual, deepSetValue} from '../../src/utils.js';

/**
* @typedef {Object} ObjectGuard
* @property {*} obj a view on the guarded object
* @property {function(): void} verify a function that checks for and rolls back disallowed changes to the guarded object
*/

/**
* Create a factory function for object guards using the given rules.
*
* An object guard is a pair {obj, verify} where:
* - `obj` is a view on the guarded object that applies "redact" rules (the same rules used in activites/redactor.js)
* - `verify` is a function that, when called, will check that the guarded object was not modified
* in a way that violates any "write protect" rules, and rolls back any offending changes.
*
* This is meant to provide sandboxed version of a privacy-sensitive object, where reads
* are filtered through redaction rules and writes are checked against write protect rules.
*
* @param {Array[TransformationRule]} rules
* @return {function(*, ...[*]): ObjectGuard}
*/
export function objectGuard(rules) {
const root = {};
const writeRules = [];

rules.forEach(rule => {
if (rule.wp) writeRules.push(rule);
if (!rule.get) return;
rule.paths.forEach(path => {
let node = root;
path.split('.').forEach(el => {
node.children = node.children || {};
node.children[el] = node.children[el] || {};
node = node.children[el];
})
node.rule = rule;
});
});

const wpTransformer = objectTransformer(writeRules);

function mkApplies(session, args) {
return function applies(rule) {
if (!session.hasOwnProperty(rule.name)) {
session[rule.name] = rule.applies(...args);
}
return session[rule.name];
}
}

function mkGuard(obj, tree, applies) {
return new Proxy(obj, {
get(target, prop, receiver) {
const val = Reflect.get(target, prop, receiver);
if (tree.hasOwnProperty(prop)) {
const {children, rule} = tree[prop];
if (children && val != null && typeof val === 'object') {
return mkGuard(val, children, applies);
} else if (rule && isData(val) && applies(rule)) {
return rule.get(val);
}
}
return val;
},
});
}

function mkVerify(transformResult) {
return function () {
transformResult.forEach(fn => fn());
}
}

return function guard(obj, ...args) {
const session = {};
return {
obj: mkGuard(obj, root.children || {}, mkApplies(session, args)),
verify: mkVerify(wpTransformer(session, obj, ...args))
}
};
}

/**
* @param {TransformationRuleDef} ruleDef
* @return {TransformationRule}
*/
export function writeProtectRule(ruleDef) {
return Object.assign({
wp: true,
run(root, path, object, property, applies) {
const origHasProp = object && object.hasOwnProperty(property);
const original = origHasProp ? object[property] : undefined;
const origCopy = origHasProp && original != null && typeof original === 'object' ? deepClone(original) : original;
return function () {
const object = path == null ? root : deepAccess(root, path);
const finalHasProp = object && isData(object[property]);
const finalValue = finalHasProp ? object[property] : undefined;
if (!origHasProp && finalHasProp && applies()) {
delete object[property];
} else if ((origHasProp !== finalHasProp || finalValue !== original || !deepEqual(finalValue, origCopy)) && applies()) {
deepSetValue(root, (path == null ? [] : [path]).concat(property).join('.'), origCopy);
}
}
}
}, ruleDef)
}
88 changes: 88 additions & 0 deletions libraries/objectGuard/ortbGuard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {isActivityAllowed} from '../../src/activities/rules.js';
import {ACTIVITY_ENRICH_EIDS, ACTIVITY_ENRICH_UFPD} from '../../src/activities/activities.js';
import {
appliesWhenActivityDenied,
ortb2TransmitRules,
ORTB_EIDS_PATHS,
ORTB_UFPD_PATHS
} from '../../src/activities/redactor.js';
import {objectGuard, writeProtectRule} from './objectGuard.js';
import {mergeDeep} from '../../src/utils.js';

function ortb2EnrichRules(isAllowed = isActivityAllowed) {
return [
{
name: ACTIVITY_ENRICH_EIDS,
paths: ORTB_EIDS_PATHS,
applies: appliesWhenActivityDenied(ACTIVITY_ENRICH_EIDS, isAllowed)
},
{
name: ACTIVITY_ENRICH_UFPD,
paths: ORTB_UFPD_PATHS,
applies: appliesWhenActivityDenied(ACTIVITY_ENRICH_UFPD, isAllowed)
}
].map(writeProtectRule)
}

export function ortb2GuardFactory(isAllowed = isActivityAllowed) {
return objectGuard(ortb2TransmitRules(isAllowed).concat(ortb2EnrichRules(isAllowed)));
}

/**
*
*
* @typedef {Function} ortb2Guard
* @param {{}} ortb2 ORTB object to guard
* @param {{}} params activity params to use for activity checks
* @returns {ObjectGuard}
*/

/*
* Get a guard for an ORTB object. Read access is restricted in the same way it'd be redacted (see activites/redactor.js);
* and writes are checked against the enrich* activites.
*
* @type ortb2Guard
*/
export const ortb2Guard = ortb2GuardFactory();

export function ortb2FragmentsGuardFactory(guardOrtb2 = ortb2Guard) {
return function guardOrtb2Fragments(fragments, params) {
fragments.global = fragments.global || {};
fragments.bidder = fragments.bidder || {};
const bidders = new Set(Object.keys(fragments.bidder));
const verifiers = [];

function makeGuard(ortb2) {
const guard = guardOrtb2(ortb2, params);
verifiers.push(guard.verify);
return guard.obj;
}

const obj = {
global: makeGuard(fragments.global),
bidder: Object.fromEntries(Object.entries(fragments.bidder).map(([bidder, ortb2]) => [bidder, makeGuard(ortb2)]))
};

return {
obj,
verify() {
Object.entries(obj.bidder)
.filter(([bidder]) => !bidders.has(bidder))
.forEach(([bidder, ortb2]) => {
const repl = {};
const guard = guardOrtb2(repl, params);
mergeDeep(guard.obj, ortb2);
guard.verify();
fragments.bidder[bidder] = repl;
})
verifiers.forEach(fn => fn());
}
}
}
}

/**
* Get a guard for an ortb2Fragments object.
* @type {function(*, *): ObjectGuard}
*/
export const guardOrtb2Fragments = ortb2FragmentsGuardFactory();
74 changes: 74 additions & 0 deletions modules/allowActivities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {config} from '../src/config.js';
import {registerActivityControl} from '../src/activities/rules.js';

const CFG_NAME = 'allowActivities';
const RULE_NAME = `${CFG_NAME} config`;
const DEFAULT_PRIORITY = 1;

export function updateRulesFromConfig(registerRule) {
const activeRuleHandles = new Map();
const defaultRuleHandles = new Map();
const rulesByActivity = new Map();

function clearAllRules() {
rulesByActivity.clear();
Array.from(activeRuleHandles.values())
.flatMap(ruleset => Array.from(ruleset.values()))
.forEach(fn => fn());
activeRuleHandles.clear();
Array.from(defaultRuleHandles.values()).forEach(fn => fn());
defaultRuleHandles.clear();
}

function cleanParams(params) {
// remove private parameters for publisher condition checks
return Object.fromEntries(Object.entries(params).filter(([k]) => !k.startsWith('_')))
}

function setupRule(activity, priority) {
if (!activeRuleHandles.has(activity)) {
activeRuleHandles.set(activity, new Map())
}
const handles = activeRuleHandles.get(activity);
if (!handles.has(priority)) {
handles.set(priority, registerRule(activity, RULE_NAME, function (params) {
for (const rule of rulesByActivity.get(activity).get(priority)) {
if (!rule.condition || rule.condition(cleanParams(params))) {
return {allow: rule.allow, reason: rule}
}
}
}, priority));
}
}

function setupDefaultRule(activity) {
if (!defaultRuleHandles.has(activity)) {
defaultRuleHandles.set(activity, registerRule(activity, RULE_NAME, function () {
return {allow: false, reason: 'activity denied by default'}
}, Number.POSITIVE_INFINITY))
}
}

config.getConfig(CFG_NAME, (cfg) => {
clearAllRules();
Object.entries(cfg[CFG_NAME]).forEach(([activity, activityCfg]) => {
if (activityCfg.default === false) {
setupDefaultRule(activity);
}
const rules = new Map();
rulesByActivity.set(activity, rules);

(activityCfg.rules || []).forEach(rule => {
const priority = rule.priority == null ? DEFAULT_PRIORITY : rule.priority;
if (!rules.has(priority)) {
rules.set(priority, [])
}
rules.get(priority).push(rule);
});

Array.from(rules.keys()).forEach(priority => setupRule(activity, priority));
});
})
}

updateRulesFromConfig(registerActivityControl);
Loading