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

Shared ID gdpr support #6275

Merged
merged 2 commits into from
Feb 8, 2021
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
3 changes: 2 additions & 1 deletion modules/id5IdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,11 @@ export const id5IdSubmodule = {
* It's permissible to return neither, one, or both fields.
* @function extendId
* @param {SubmoduleConfig} config
* @param {ConsentData|undefined} consentData
* @param {Object} cacheIdObj - existing id, if any
* @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback.
*/
extendId(config, cacheIdObj) {
extendId(config, consentData, cacheIdObj) {
const partnerId = (config && config.params && config.params.partner) || 0;
incrementNb(partnerId);
return cacheIdObj;
Expand Down
20 changes: 16 additions & 4 deletions modules/pubCommonIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ function handleResponse(pubcid, callback, config) {
}
}

/**
* Builds and returns the shared Id URL with attached consent data if applicable
* @param {Object} consentData
* @return {string}
*/
function sharedIdUrl(consentData) {
if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return SHAREDID_URL;

return `${SHAREDID_URL}?gdpr=1&gdpr_consent=${consentData.consentString}`
}

/**
* Wraps pixelCallback in order to call sharedid sync
* @param {string} pubcid Pubcommon id value
Expand All @@ -144,12 +155,12 @@ function handleResponse(pubcid, callback, config) {
* @return {function(...[*]=)}
*/

function getIdCallback(pubcid, pixelCallback, config) {
function getIdCallback(pubcid, pixelCallback, config, consentData) {
return function (callback) {
if (typeof pixelCallback === 'function') {
pixelCallback();
}
ajax(SHAREDID_URL, handleResponse(pubcid, callback, config), undefined, {method: 'GET', withCredentials: true});
ajax(sharedIdUrl(consentData), handleResponse(pubcid, callback, config), undefined, {method: 'GET', withCredentials: true});
}
}

Expand Down Expand Up @@ -227,7 +238,7 @@ export const pubCommonIdSubmodule = {
}

const pixelCallback = this.makeCallback(pixelUrl, newId);
const combinedCallback = enableSharedId ? getIdCallback(newId, pixelCallback, config) : pixelCallback;
const combinedCallback = enableSharedId ? getIdCallback(newId, pixelCallback, config, consentData) : pixelCallback;

return {id: newId, callback: combinedCallback};
},
Expand All @@ -247,10 +258,11 @@ export const pubCommonIdSubmodule = {
*
* @function
* @param {SubmoduleParams} [config]
* @param {ConsentData|undefined} consentData
* @param {Object} storedId existing id
* @returns {IdResponse|undefined}
*/
extendId: function(config = {}, storedId) {
extendId: function(config = {}, consentData, storedId) {
const {params: {extend = false, pixelUrl, enableSharedId = SHAREDID_DEFAULT_STATE} = {}} = config;

if (extend) {
Expand Down
21 changes: 17 additions & 4 deletions modules/sharedIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,17 @@ function detectPrng(root) {
return () => Math.random();
}

/**
* Builds and returns the shared Id URL with attached consent data if applicable
* @param {Object} consentData
* @return {string}
*/
function sharedIdUrl(consentData) {
if (!consentData || typeof consentData.gdprApplies !== 'boolean' || !consentData.gdprApplies) return ID_SVC;

return `${ID_SVC}?gdpr=1&gdpr_consent=${consentData.consentString}`
}

/** @type {Submodule} */
export const sharedIdSubmodule = {
/**
Expand Down Expand Up @@ -303,23 +314,25 @@ export const sharedIdSubmodule = {
* performs action to obtain id and return a value.
* @function
* @param {SubmoduleConfig} [config]
* @param {ConsentData|undefined} consentData
* @returns {sharedId}
*/
getId(config) {
getId(config, consentData) {
const resp = function (callback) {
utils.logInfo('SharedId: Sharedid doesnt exists, new cookie creation');
ajax(ID_SVC, idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true});
ajax(sharedIdUrl(consentData), idGenerationCallback(callback), undefined, {method: 'GET', withCredentials: true});
};
return {callback: resp};
},

/**
* performs actions even if the id exists and returns a value
* @param config
* @param consentData
* @param storedId
* @returns {{callback: *}}
*/
extendId(config, storedId) {
extendId(config, consentData, storedId) {
const configParams = (config && config.params) || {};
utils.logInfo('SharedId: Existing shared id ' + storedId.id);
const resp = function (callback) {
Expand All @@ -329,7 +342,7 @@ export const sharedIdSubmodule = {
const sharedIdPayload = {};
sharedIdPayload.sharedId = storedId.id;
const payloadString = JSON.stringify(sharedIdPayload);
ajax(ID_SVC, existingIdCallback(storedId, callback), payloadString, {method: 'POST', withCredentials: true});
ajax(sharedIdUrl(consentData), existingIdCallback(storedId, callback), payloadString, {method: 'POST', withCredentials: true});
}
};
return {callback: resp};
Expand Down
3 changes: 2 additions & 1 deletion modules/userId/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* It's permissible to return neither, one, or both fields.
* @name Submodule#extendId
* @param {SubmoduleConfig} config
* @param {ConsentData|undefined} consentData
* @param {Object} storedId - existing id, if any
* @return {(IdResponse|function(callback:function))} A response object that contains id and/or callback.
*/
Expand Down Expand Up @@ -621,7 +622,7 @@ function populateSubmoduleId(submodule, consentData, storedConsentData, forceRef
response = submodule.submodule.getId(submodule.config, consentData, storedId);
} else if (typeof submodule.submodule.extendId === 'function') {
// If the id exists already, give submodule a chance to decide additional actions that need to be taken
response = submodule.submodule.extendId(submodule.config, storedId);
response = submodule.submodule.extendId(submodule.config, consentData, storedId);
}

if (utils.isPlainObject(response)) {
Expand Down
55 changes: 55 additions & 0 deletions test/spec/modules/sharedIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
sharedIdSubmodule,
} from 'modules/sharedIdSystem.js';
import { server } from 'test/mocks/xhr.js';

let expect = require('chai').expect;

describe('SharedId System', function() {
const SHAREDID_RESPONSE = {sharedId: 'testsharedid'};

describe('Xhr Requests from getId()', function() {
let callbackSpy = sinon.spy();

beforeEach(function() {
callbackSpy.resetHistory();
});

afterEach(function () {

});

it('should call shared id endpoint without consent data and handle a valid response', function () {
let submoduleCallback = sharedIdSubmodule.getId(undefined, undefined).callback;
submoduleCallback(callbackSpy);

let request = server.requests[0];
expect(request.url).to.equal('https://id.sharedid.org/id');
expect(request.withCredentials).to.be.true;

request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE));

expect(callbackSpy.calledOnce).to.be.true;
expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId);
});

it('should call shared id endpoint with consent data and handle a valid response', function () {
let consentData = {
gdprApplies: true,
consentString: 'abc12345234',
};

let submoduleCallback = sharedIdSubmodule.getId(undefined, consentData).callback;
submoduleCallback(callbackSpy);

let request = server.requests[0];
expect(request.url).to.equal('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234');
expect(request.withCredentials).to.be.true;

request.respond(200, {}, JSON.stringify(SHAREDID_RESPONSE));

expect(callbackSpy.calledOnce).to.be.true;
expect(callbackSpy.lastCall.lastArg.id).to.equal(SHAREDID_RESPONSE.sharedId);
});
});
});
53 changes: 53 additions & 0 deletions test/spec/modules/userId_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,8 @@ describe('User ID', function () {
coreStorage.setCookie('pubcid_sharedid', '', EXPIRED_COOKIE_DATE);
coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE);
coreStorage.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE);
resetConsentData();
delete window.__tcfapi;
});

it('pubcid callback with url', function () {
Expand Down Expand Up @@ -2171,6 +2173,57 @@ describe('User ID', function () {
expect(server.requests[0].url).to.equal('https://id.sharedid.org/id');
expect(coreStorage.getCookie('pubcid_sharedid')).to.be.null;
});

it('verify sharedid called with consent data when gdpr applies', function () {
let adUnits = [getAdUnitMock()];
let customCfg = getConfigMock(['pubCommonId', 'pubcid', 'cookie']);
let consentConfig = {
cmpApi: 'iab',
timeout: 7500,
allowAuctionWithoutConsent: false
};
customCfg = addConfig(customCfg, 'params', {pixelUrl: '/any/pubcid/url', enableSharedId: true});

server.respondWith('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234', function(xhr) {
xhr.respond(200, {}, '{"sharedId":"testsharedid"}');
});
server.respondImmediately = true;

let testConsentData = {
tcString: 'abc12345234',
gdprApplies: true,
purposeOneTreatment: false,
eventStatus: 'tcloaded',
vendor: {consents: {887: true}},
purpose: {
consents: {
1: true
}
}
};

window.__tcfapi = function () { };
sinon.stub(window, '__tcfapi').callsFake((...args) => {
args[2](testConsentData, true);
});

setSubmoduleRegistry([pubCommonIdSubmodule]);
init(config);
config.setConfig(customCfg);
setConsentConfig(consentConfig);

consentManagementRequestBidsHook(() => {
}, {});
requestBidsHook((config) => {
}, {adUnits});

expect(utils.triggerPixel.called).to.be.false;
events.emit(CONSTANTS.EVENTS.AUCTION_END, {});
expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url');

expect(server.requests[0].url).to.equal('https://id.sharedid.org/id?gdpr=1&gdpr_consent=abc12345234');
expect(coreStorage.getCookie('pubcid_sharedid')).to.equal('testsharedid');
});
});

describe('Set cookie behavior', function () {
Expand Down