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

Prebid 8: introduce new transmitTid activity control #10034

Merged
merged 2 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 2 additions & 11 deletions libraries/objectGuard/objectGuard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isData, objectTransformer} from '../../src/activities/redactor.js';
import {isData, objectTransformer, sessionedApplies} from '../../src/activities/redactor.js';
import {deepAccess, deepClone, deepEqual, deepSetValue} from '../../src/utils.js';

/**
Expand Down Expand Up @@ -41,15 +41,6 @@ export function objectGuard(rules) {

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) {
Expand All @@ -76,7 +67,7 @@ export function objectGuard(rules) {
return function guard(obj, ...args) {
const session = {};
return {
obj: mkGuard(obj, root.children || {}, mkApplies(session, args)),
obj: mkGuard(obj, root.children || {}, sessionedApplies(session, ...args)),
verify: mkVerify(wpTransformer(session, obj, ...args))
}
};
Expand Down
5 changes: 5 additions & 0 deletions src/activities/activities.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ export const ACTIVITY_TRANSMIT_UFPD = 'transmitUfpd';
* transmitPreciseGeo: some component wants access to (and send along) geolocation info
*/
export const ACTIVITY_TRANSMIT_PRECISE_GEO = 'transmitPreciseGeo';

/**
* transmit TID: some component wants access ot (and send along) transaction IDs
*/
export const ACTIVITY_TRANSMIT_TID = 'transmitTid';
44 changes: 36 additions & 8 deletions src/activities/redactor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import {deepAccess} from '../utils.js';
import {isActivityAllowed} from './rules.js';
import {ACTIVITY_TRANSMIT_EIDS, ACTIVITY_TRANSMIT_PRECISE_GEO, ACTIVITY_TRANSMIT_UFPD} from './activities.js';
import {config} from '../config.js';
import {isActivityAllowed, registerActivityControl} from './rules.js';
import {
ACTIVITY_TRANSMIT_EIDS,
ACTIVITY_TRANSMIT_PRECISE_GEO,
ACTIVITY_TRANSMIT_TID,
ACTIVITY_TRANSMIT_UFPD
} from './activities.js';

export const ORTB_UFPD_PATHS = ['user.data', 'user.ext.data'];
export const ORTB_EIDS_PATHS = ['user.eids', 'user.ext.eids'];
Expand Down Expand Up @@ -74,23 +80,28 @@ export function objectTransformer(rules) {
})
return function applyTransform(session, obj, ...args) {
const result = [];
const applies = sessionedApplies(session, ...args);
rules.forEach(rule => {
if (session[rule.name] === false) return;
for (const [head, tail] of rule.paths) {
const parent = head == null ? obj : deepAccess(obj, head);
result.push(rule.run(obj, head, parent, tail, () => {
if (!session.hasOwnProperty(rule.name)) {
session[rule.name] = !!rule.applies(...args);
}
return session[rule.name]
}))
result.push(rule.run(obj, head, parent, tail, applies.bind(null, rule)));
if (session[rule.name] === false) return;
}
})
return result.filter(el => el != null);
}
}

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

export function isData(val) {
return val != null && (typeof val !== 'object' || Object.keys(val).length > 0)
}
Expand All @@ -107,6 +118,11 @@ function bidRequestTransmitRules(isAllowed = isActivityAllowed) {
name: ACTIVITY_TRANSMIT_EIDS,
paths: ['userId', 'userIdAsEids'],
applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_EIDS, isAllowed),
},
{
name: ACTIVITY_TRANSMIT_TID,
paths: ['ortb2Imp.ext.tid'],
applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_TID, isAllowed)
}
].map(redactRule)
}
Expand All @@ -130,6 +146,11 @@ export function ortb2TransmitRules(isAllowed = isActivityAllowed) {
get(val) {
return Math.round((val + Number.EPSILON) * 100) / 100;
}
},
{
name: ACTIVITY_TRANSMIT_TID,
paths: ['source.tid'],
applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_TID, isAllowed),
}
].map(redactRule);
}
Expand All @@ -155,3 +176,10 @@ export function redactorFactory(isAllowed = isActivityAllowed) {
* that can redact disallowed data from ORTB2 and/or bid request objects.
*/
export const redactor = redactorFactory();

// by default, TIDs are off since version 8
registerActivityControl(ACTIVITY_TRANSMIT_TID, 'enableTIDs config', () => {
if (!config.getConfig('enableTIDs')) {
return {allow: false, reason: 'TIDs are disabled'}
}
});
43 changes: 40 additions & 3 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
isArray,
isPlainObject,
logError,
logWarn,
logWarn, memoize,
parseQueryStringParameters,
parseSizesInput,
uniques
Expand All @@ -22,6 +22,10 @@ import {hook} from '../hook.js';
import {auctionManager} from '../auctionManager.js';
import {bidderSettings} from '../bidderSettings.js';
import {useMetrics} from '../utils/perfMetrics.js';
import {isActivityAllowed} from '../activities/rules.js';
import {activityParams} from '../activities/activityParams.js';
import {MODULE_TYPE_BIDDER} from '../activities/modules.js';
import {ACTIVITY_TRANSMIT_TID} from '../activities/activities.js';

/**
* This file aims to support Adapters during the Prebid 0.x -> 1.x transition.
Expand Down Expand Up @@ -180,6 +184,38 @@ export function registerBidder(spec) {
}
}

function guardTids(bidderCode) {
if (isActivityAllowed(ACTIVITY_TRANSMIT_TID, activityParams(MODULE_TYPE_BIDDER, bidderCode))) {
return {
bidRequest: (br) => br,
bidderRequest: (br) => br
};
}
function get(target, prop, receiver) {
if (['transactionId', 'auctionId'].includes(prop)) {
return null;
}
return Reflect.get(target, prop, receiver);
}
const bidRequest = memoize((br) => new Proxy(br, {get}), (arg) => arg.bidId)
/**
* Return a view on bidd(er) requests where auctionId/transactionId are nulled if the bidder is not allowed `transmitTid`.
*
* Because both auctionId and transactionId are used for Prebid's own internal bookkeeping, we cannot simply erase them
* from request objects; and because request objects are quite complex and not easily cloneable, we hide the IDs
* with a proxy instead. This should be used only around the adapter logic.
*/
return {
bidRequest,
bidderRequest: (br) => new Proxy(br, {
get(target, prop, receiver) {
if (prop === 'bids') return br.bids.map(bidRequest);
return get(target, prop, receiver);
}
})
}
}

/**
* Make a new bidder from the given spec. This is exported mainly for testing.
* Adapters will probably find it more convenient to use registerBidder instead.
Expand All @@ -196,6 +232,7 @@ export function newBidder(spec) {
if (!Array.isArray(bidderRequest.bids)) {
return;
}
const tidGuard = guardTids(bidderRequest.bidderCode);

const adUnitCodesHandled = {};
function addBidWithCode(adUnitCode, bid) {
Expand All @@ -221,7 +258,7 @@ export function newBidder(spec) {
}

const validBidRequests = adapterMetrics(bidderRequest)
.measureTime('validate', () => bidderRequest.bids.filter(filterAndWarn));
.measureTime('validate', () => bidderRequest.bids.filter((br) => filterAndWarn(tidGuard.bidRequest(br))));

if (validBidRequests.length === 0) {
afterAllResponses();
Expand All @@ -236,7 +273,7 @@ export function newBidder(spec) {
}
});

processBidderRequests(spec, validBidRequests, bidderRequest, ajax, configEnabledCallback, {
processBidderRequests(spec, validBidRequests.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest), ajax, configEnabledCallback, {
onRequest: requestObject => events.emit(CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP, bidderRequest, requestObject),
onResponse: (resp) => {
onTimelyResponse(spec.code);
Expand Down
1 change: 1 addition & 0 deletions test/pages/banner.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
});

pbjs.que.push(function () {
pbjs.setConfig({enableTIDs: true});
pbjs.addAdUnits(adUnits);
pbjs.requestBids({ bidsBackHandler: sendAdServerRequest });
});
Expand Down
1 change: 1 addition & 0 deletions test/pages/bidderSettings.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
});

pbjs.que.push(function () {
pbjs.setConfig({enableTIDs: true});
pbjs.addAdUnits(adUnits);
pbjs.bidderSettings = {
appnexus: {
Expand Down
1 change: 1 addition & 0 deletions test/pages/consent_mgt_gdpr.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
pbjs.que.push(function () {
pbjs.addAdUnits(adUnits);
pbjs.setConfig({
enableTIDs: true,
consentManagement: {
gdpr: {
cmpApi: 'static',
Expand Down
1 change: 1 addition & 0 deletions test/pages/currency.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@

pbjs.que.push(function() {
pbjs.setConfig({
enableTIDs: true,
"currency": {
"adServerCurrency": "GBP",
"granularityMultiplier": 1,
Expand Down
1 change: 1 addition & 0 deletions test/pages/instream.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

pbjs.addAdUnits(videoAdUnit);
pbjs.setConfig({
enableTIDs: true,
debug: true,
cache: {
url: 'https://prebid.adnxs.com/pbc/v1/cache'
Expand Down
2 changes: 1 addition & 1 deletion test/pages/multiple_bidders.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
}
}]
}];

pbjs.setConfig({enableTIDs: true});
pbjs.addAdUnits(adUnits);
pbjs.requestBids({
timeout: PREBID_TIMEOUT,
Expand Down
1 change: 1 addition & 0 deletions test/pages/native.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
});

pbjs.que.push(function () {
pbjs.setConfig({enableTIDs: true});
pbjs.addAdUnits(adUnits);
pbjs.requestBids({ bidsBackHandler: sendAdServerRequest });
});
Expand Down
5 changes: 4 additions & 1 deletion test/pages/outstream.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@
googletag.cmd = googletag.cmd || [];

pbjs.que.push(function () {
pbjs.setConfig({ debug: true });
pbjs.setConfig({
enableTIDs: true,
debug: true
});
pbjs.addAdUnits(outstreamVideoAdUnit);
pbjs.bidderSettings = {
appnexus: {
Expand Down
12 changes: 10 additions & 2 deletions test/spec/activities/redactor_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import {ACTIVITY_PARAM_COMPONENT_NAME, ACTIVITY_PARAM_COMPONENT_TYPE} from '../../../src/activities/params.js';
import {
ACTIVITY_TRANSMIT_EIDS,
ACTIVITY_TRANSMIT_PRECISE_GEO,
ACTIVITY_TRANSMIT_PRECISE_GEO, ACTIVITY_TRANSMIT_TID,
ACTIVITY_TRANSMIT_UFPD
} from '../../../src/activities/activities.js';
import {deepAccess, deepSetValue} from '../../../src/utils.js';
Expand Down Expand Up @@ -271,6 +271,10 @@ describe('redactor', () => {
testAllowDeny(ACTIVITY_TRANSMIT_EIDS, (allowed) => {
testPropertiesAreRemoved(() => redactor.bidRequest, ['userId', 'userIdAsEids'], allowed);
});

testAllowDeny(ACTIVITY_TRANSMIT_TID, (allowed) => {
testPropertiesAreRemoved(() => redactor.bidRequest, ['ortb2Imp.ext.tid'], allowed);
})
});

describe('.ortb2', () => {
Expand All @@ -282,6 +286,10 @@ describe('redactor', () => {
testPropertiesAreRemoved(() => redactor.ortb2, ORTB_UFPD_PATHS, allowed)
});

testAllowDeny(ACTIVITY_TRANSMIT_TID, (allowed) => {
testPropertiesAreRemoved(() => redactor.ortb2, ['source.tid'], allowed);
});

testAllowDeny(ACTIVITY_TRANSMIT_PRECISE_GEO, (allowed) => {
ORTB_GEO_PATHS.forEach(path => {
it(`should ${allowed ? 'NOT ' : ''} round down ${path}`, () => {
Expand All @@ -291,6 +299,6 @@ describe('redactor', () => {
expect(deepAccess(ortb2, path)).to.eql(allowed ? 1.2345 : 1.23);
})
})
})
});
});
})
20 changes: 16 additions & 4 deletions test/spec/unit/core/adapterManager_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1862,10 +1862,22 @@ describe('adapterManager tests', function () {
requests.appnexus.bids.forEach((bid) => expect(bid.ortb2).to.eql(requests.appnexus.ortb2));
});

it('should populate ortb2.source.tid with auctionId', () => {
const reqs = adapterManager.makeBidRequests(adUnits, 0, 'mockAuctionId', 1000, [], {global: {}});
expect(reqs[0].ortb2.source.tid).to.equal('mockAuctionId');
})
describe('source.tid', () => {
beforeEach(() => {
sinon.stub(dep, 'redact').returns({
ortb2: (o) => o,
bidRequest: (b) => b,
});
});
afterEach(() => {
dep.redact.restore();
});

it('should be populated with auctionId', () => {
const reqs = adapterManager.makeBidRequests(adUnits, 0, 'mockAuctionId', 1000, [], {global: {}});
expect(reqs[0].ortb2.source.tid).to.equal('mockAuctionId');
})
});

it('should merge in bid-level ortb2Imp with adUnit-level ortb2Imp', () => {
const adUnit = {
Expand Down
Loading