diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index fa9b1e3b5fd..71299a4a6e1 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -236,6 +236,7 @@ }, { name: "sharedId", + // bidders: ["rubicon", "sampleBidders"], // to allow this ID for specific bidders params: { syncTime: 60 // in seconds, default is 24 hours }, diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 8fb512ff7e2..7274912efb5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -12,6 +12,7 @@ import includes from 'core-js-pure/features/array/includes.js'; import { S2S_VENDORS } from './config.js'; import { ajax } from '../../src/ajax.js'; import find from 'core-js-pure/features/array/find.js'; +import { getEidPermissions } from '../userId/index.js'; const getConfig = config.getConfig; @@ -455,7 +456,7 @@ export function resetWurlMap() { } const OPEN_RTB_PROTOCOL = { - buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig) { + buildRequest(s2sBidRequest, bidRequests, adUnits, s2sConfig, requestedBidders) { let imps = []; let aliases = {}; const firstBidRequest = bidRequests[0]; @@ -700,6 +701,18 @@ const OPEN_RTB_PROTOCOL = { utils.deepSetValue(request, 'user.ext.eids', bidUserIdAsEids); } + const eidPermissions = getEidPermissions(); + if (utils.isArray(eidPermissions) && eidPermissions.length > 0) { + if (requestedBidders && utils.isArray(requestedBidders)) { + eidPermissions.forEach(i => { + if (i.bidders) { + i.bidders = i.bidders.filter(bidder => requestedBidders.includes(bidder)) + } + }); + } + utils.deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); + } + if (bidRequests) { if (firstBidRequest.gdprConsent) { // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module @@ -958,7 +971,7 @@ export function PrebidServer() { queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig); } - const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig); + const request = OPEN_RTB_PROTOCOL.buildRequest(s2sBidRequest, bidRequests, validAdUnits, s2sBidRequest.s2sConfig, requestedBidders); const requestJson = request && JSON.stringify(request); if (request && requestJson) { ajax( diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 27665de4136..80750ccaae8 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -224,3 +224,25 @@ export function createEidsArray(bidRequestUserId) { } return eids; } + +/** + * @param {SubmoduleContainer[]} submodules + */ +export function buildEidPermissions(submodules) { + let eidPermissions = []; + submodules.filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length) + .forEach(i => { + Object.keys(i.idObj).forEach(key => { + if (utils.deepAccess(i, 'config.bidders') && Array.isArray(i.config.bidders) && + utils.deepAccess(USER_IDS_CONFIG, key + '.source')) { + eidPermissions.push( + { + source: USER_IDS_CONFIG[key].source, + bidders: i.config.bidders + } + ); + } + }); + }); + return eidPermissions; +} diff --git a/modules/userId/index.js b/modules/userId/index.js index 9294311de69..8f7d2a36699 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -136,7 +136,7 @@ import { getGlobal } from '../../src/prebidGlobal.js'; import { gdprDataHandler } from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; import { module, hook } from '../../src/hook.js'; -import { createEidsArray } from './eids.js'; +import { createEidsArray, buildEidPermissions } from './eids.js'; import { getCoreStorageManager } from '../../src/storageManager.js'; const MODULE_NAME = 'User ID'; @@ -214,6 +214,10 @@ export function setStoredValue(submodule, value) { } } +export function getEidPermissions() { + return buildEidPermissions(initializedSubmodules); +} + /** * @param {SubmoduleStorage} storage * @param {String|undefined} key optional key of the value @@ -433,6 +437,26 @@ function getCombinedSubmoduleIds(submodules) { return combinedSubmoduleIds; } +/** + * This function will create a combined object for bidder with allowed subModule Ids + * @param {SubmoduleContainer[]} submodules + * @param {string} bidder + */ +function getCombinedSubmoduleIdsForBidder(submodules, bidder) { + if (!Array.isArray(submodules) || !submodules.length || !bidder) { + return {}; + } + return submodules + .filter(i => !i.config.bidders || !utils.isArray(i.config.bidders) || i.config.bidders.includes(bidder)) + .filter(i => utils.isPlainObject(i.idObj) && Object.keys(i.idObj).length) + .reduce((carry, i) => { + Object.keys(i.idObj).forEach(key => { + carry[key] = i.idObj[key]; + }); + return carry; + }, {}); +} + /** * @param {AdUnit[]} adUnits * @param {SubmoduleContainer[]} submodules @@ -441,19 +465,18 @@ function addIdDataToAdUnitBids(adUnits, submodules) { if ([adUnits].some(i => !Array.isArray(i) || !i.length)) { return; } - const combinedSubmoduleIds = getCombinedSubmoduleIds(submodules); - const combinedSubmoduleIdsAsEids = createEidsArray(combinedSubmoduleIds); - if (Object.keys(combinedSubmoduleIds).length) { - adUnits.forEach(adUnit => { - if (adUnit.bids && utils.isArray(adUnit.bids)) { - adUnit.bids.forEach(bid => { + adUnits.forEach(adUnit => { + if (adUnit.bids && utils.isArray(adUnit.bids)) { + adUnit.bids.forEach(bid => { + const combinedSubmoduleIds = getCombinedSubmoduleIdsForBidder(submodules, bid.bidder); + if (Object.keys(combinedSubmoduleIds).length) { // create a User ID object on the bid, bid.userId = combinedSubmoduleIds; - bid.userIdAsEids = combinedSubmoduleIdsAsEids; - }); - } - }); - } + bid.userIdAsEids = createEidsArray(combinedSubmoduleIds); + } + }); + } + }); } /** diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index a39bf9b26d5..53f4ace8381 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -26,7 +26,8 @@ function flagErrors(context, node, importPath) { if ( path.dirname(absImportPath) === absModulePath || ( absImportPath.startsWith(absModulePath) && - path.basename(absImportPath) === 'index.js' + path.basename(absImportPath) === 'index.js' && + path.basename(absFileDir) !== 'prebidServerBidAdapter' ) ) { context.report(node, `import "${importPath}" cannot require module entry point`); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index f17cd3ab14f..4652bbb63f7 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -953,17 +953,15 @@ describe('S2S Adapter', function () { adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - - expect(requestBid.ext).to.deep.equal({ - prebid: { - aliases: { - brealtime: 'appnexus' - }, - auctiontimestamp: 1510852447530, - targeting: { - includebidderkeys: false, - includewinners: true - } + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.include({ + aliases: { + brealtime: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true } }); }); @@ -985,17 +983,15 @@ describe('S2S Adapter', function () { adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - - expect(requestBid.ext).to.deep.equal({ - prebid: { - aliases: { - [alias]: 'appnexus' - }, - auctiontimestamp: 1510852447530, - targeting: { - includebidderkeys: false, - includewinners: true - } + expect(requestBid.ext).to.haveOwnProperty('prebid'); + expect(requestBid.ext.prebid).to.deep.include({ + aliases: { + [alias]: 'appnexus' + }, + auctiontimestamp: 1510852447530, + targeting: { + includebidderkeys: false, + includewinners: true } }); }); @@ -1376,7 +1372,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, foo: 'bar', targeting: { @@ -1410,7 +1406,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, targeting: { includewinners: false, @@ -1446,7 +1442,7 @@ describe('S2S Adapter', function () { expect(requestBid).to.haveOwnProperty('ext'); expect(requestBid.ext).to.haveOwnProperty('prebid'); - expect(requestBid.ext.prebid).to.deep.equal({ + expect(requestBid.ext.prebid).to.deep.include({ auctiontimestamp: 1510852447530, cache: { vastxml: 'vastxml-set-though-extPrebid.cache.vastXml' diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index ed592c0cba5..1ae023ae947 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -2,6 +2,7 @@ import { attachIdSystem, auctionDelay, coreStorage, + getEidPermissions, init, requestBidsHook, setStoredConsentData, @@ -70,7 +71,7 @@ describe('User ID', function () { code, mediaTypes: {banner: {}, native: {}}, sizes: [[300, 200], [300, 600]], - bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}, {bidder: 'anotherSampleBidder', params: {placementId: 'banner-only-bidder'}}] }; } @@ -1196,6 +1197,120 @@ describe('User ID', function () { }, {adUnits}); }); + it('eidPermissions fun with bidders', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test222', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'sharedId', + bidders: [ + 'sampleBidder' + ], + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } + } + ] + } + }); + + requestBidsHook(function () { + const eidPermissions = getEidPermissions(); + expect(eidPermissions).to.deep.equal( + [ + {source: 'sharedid.org', bidders: ['sampleBidder']} + ] + ); + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + if (bid.bidder === 'sampleBidder') { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid.id).to.equal('test222'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [ + { + id: 'test222', + atype: 1, + ext: { + third: 'test222' + } + } + ] + }); + } + if (bid.bidder === 'anotherSampleBidder') { + expect(bid).to.not.have.deep.nested.property('userId.sharedid'); + expect(bid).to.not.have.property('userIdAsEids'); + } + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('eidPermissions fun without bidders', function (done) { + coreStorage.setCookie('sharedid', JSON.stringify({ + 'id': 'test222', + 'ts': 1590525289611 + }), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([sharedIdSubmodule]); + init(config); + config.setConfig({ + userSync: { + syncDelay: 0, + userIds: [ + { + name: 'sharedId', + storage: { + type: 'cookie', + name: 'sharedid', + expires: 28 + } + } + ] + } + }); + + requestBidsHook(function () { + const eidPermissions = getEidPermissions(); + expect(eidPermissions).to.deep.equal( + [] + ); + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.sharedid'); + expect(bid.userId.sharedid.id).to.equal('test222'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'sharedid.org', + uids: [ + { + id: 'test222', + atype: 1, + ext: { + third: 'test222' + } + }] + }); + }); + }); + coreStorage.setCookie('sharedid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + it('test hook from pubProvidedId config params', function (done) { setSubmoduleRegistry([pubProvidedIdSubmodule]); init(config);