Skip to content

Commit

Permalink
Refreshuserid (#517) (#521)
Browse files Browse the repository at this point in the history
* changes to support refreshuserids use case for sso

* modules/userId/index.js src/constants.json src/utils.js test/spec/modules/userId_spec.js

* changes for refreshuser id test cases

* removed timeout for fb functions

* set loginEvent variable to true when fb and google login is detected

* changes to handle facebook login api failures
  • Loading branch information
pm-manasi-moghe authored Apr 6, 2022
1 parent 47b9497 commit d2bcb8d
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 31 deletions.
125 changes: 96 additions & 29 deletions modules/userId/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ import { createEidsArray, buildEidPermissions } from './eids.js';
import { getCoreStorageManager } from '../../src/storageManager.js';
import {
getPrebidInternal, isPlainObject, logError, isArray, cyrb53Hash, deepAccess, timestamp, delayExecution, logInfo, isFn,
logWarn, isEmptyStr, isNumber
logWarn, isEmptyStr, isNumber, isEmpty
} from '../../src/utils.js';
import includes from 'core-js-pure/features/array/includes.js';
import MD5 from 'crypto-js/md5.js';
Expand Down Expand Up @@ -191,7 +191,7 @@ export let syncDelay;
export let auctionDelay;

/** @type {(Object|undefined)} */
let userIdentity;
let userIdentity = {};
/** @param {Submodule[]} submodules */
export function setSubmoduleRegistry(submodules) {
submoduleRegistry = submodules;
Expand Down Expand Up @@ -657,54 +657,121 @@ function refreshUserIds(options, callback, moduleUpdated) {
}

function setUserIdentities(userIdentityData) {
userIdentity = userIdentityData;
if (isEmpty(userIdentityData)) {
userIdentity = {};
return;
}
var pubProvidedEmailHash = {};
if (userIdentityData.pubProvidedEmail) {
generateEmailHash(userIdentityData.pubProvidedEmail, pubProvidedEmailHash);
userIdentityData.pubProvidedEmailHash = pubProvidedEmailHash;
delete userIdentityData.pubProvidedEmail;
}
Object.assign(userIdentity, userIdentityData);
if (window.PWT && window.PWT.loginEvent) {
reTriggerPartnerCallsWithEmailHashes();
window.PWT.loginEvent = false;
}
};

function updateModuleParams(moduleToUpdate) {
//this is specific to id5id partner. needs to be revisited when we integrate additional partners for email hashes.
moduleToUpdate.params[CONSTANTS.MODULE_PARAM_TO_UPDATE_FOR_SSO[moduleToUpdate.name].param] = '1=' + getUserIdentities().emailHash['SHA256'];
}

export function reTriggerPartnerCallsWithEmailHashes() {
var modulesToRefresh = [];
var scriptBasedModulesToRefresh = [];
var primaryModulesList = CONSTANTS.REFRESH_IDMODULES_LIST.PRIMARY_MODULES;
var scriptBasedModulesList = CONSTANTS.REFRESH_IDMODULES_LIST.SCRIPT_BASED_MODULES;
var moduleName;
var index;
for (index in configRegistry) {
moduleName = configRegistry[index].name;
if (primaryModulesList.indexOf(moduleName) >= 0) {
modulesToRefresh.push(moduleName);
updateModuleParams(configRegistry[index]);
} else if (scriptBasedModulesList.indexOf(moduleName) >= 0) {
scriptBasedModulesToRefresh.push(moduleName);
}
}
getGlobal().refreshUserIds({'submoduleNames': modulesToRefresh});
reTriggerScriptBasedAPICalls(scriptBasedModulesToRefresh);
}

export function reTriggerScriptBasedAPICalls(modulesToRefresh) {
var i = 0;
var userIdentity = getUserIdentities() || {};
for (i in modulesToRefresh) {
switch (modulesToRefresh[i]) {
case 'zeotapIdPlus':
if (window.zeotap && isFn(window.zeotap.callMethod)) {
var userIdentityObject = {
email: userIdentity.emailHash['MD5']
};
window.zeotap.callMethod('setUserIdentities', userIdentityObject, true);
}
break;
case 'identityLink':
if (window.ats && isFn(window.ats.start)) {
var atsObject = window.ats.outputCurrentConfiguration();
atsObject.emailHashes = userIdentity.emailHash ? [userIdentity.emailHash['MD5'], userIdentity.emailHash['SHA1'], userIdentity.emailHash['SHA256']] : undefined;
window.ats.start(atsObject);
}
break;
}
}
}

function getUserIdentities() {
return userIdentity;
}

function processFBLoginData(refThis, response) {
var emailHash = {};
if (response.status === 'connected') {
window.PWT = window.PWT || {};
window.PWT.fbAt = response.authResponse.accessToken;
window.FB && window.FB.api('/me?fields=email&access_token=' + window.PWT.fbAt, function (response) {
logInfo('SSO - Data received from FB API');
if (response.error) {
logInfo('SSO - User information could not be retrieved by facebook api [', response.error.message, ']');
return;
}
logInfo('SSO - Information successfully retrieved by Facebook API.');
generateEmailHash(response.email || undefined, emailHash);
refThis.setUserIdentities({
emailHash: emailHash
});
});
} else {
logInfo('SSO - Error fetching login information from facebook');
}
}

/**
* This function is used to read sso information from facebook and google apis.
* @param {String} provider SSO provider for which the api call is to be made
* @param {Object} userObject Google's user object, passed from google's callback function
*/
function onSSOLogin(data) {
function onSSOLogin(data) {
var refThis = this;
var email;
var emailHash = {};

if (!window.PWT || !window.PWT.ssoEnabled) return;

switch (data.provider) {
case undefined:
case 'facebook':
var timeout = data.provider === 'facebook' ? 0 : 2000;
setTimeout(function() {
if (data.provider === 'facebook') {
window.FB && window.FB.getLoginStatus(function (response) {
if (response.status === 'connected') {
window.PWT = window.PWT || {};
window.PWT.fbAt = response.authResponse.accessToken;
window.FB && window.FB.api('/me?fields=email&access_token=' + window.PWT.fbAt, function (response) {
logInfo('SSO - Data received from FB API');

if (response.error) {
logInfo('SSO - User information could not be retrieved by facebook api [', response.error.message, ']');
return;
}

email = response.email || undefined;
logInfo('SSO - Information successfully retrieved by Facebook API.');
generateEmailHash(email, emailHash);
refThis.setUserIdentities({
emailHash: emailHash
});
});
} else {
logInfo('SSO - Error fetching login information from facebook');
}
processFBLoginData(refThis, response);
}, true);
}, timeout);
} else {
window.FB && window.FB.Event.subscribe('auth.statusChange', function (response) {
processFBLoginData(refThis, response);
});
}
break;
case 'google':
var profile = data.googleUserObject.getBasicProfile();
Expand Down
9 changes: 9 additions & 0 deletions src/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,14 @@
"BID_TARGETING_SET": "targetingSet",
"RENDERED": "rendered",
"BID_REJECTED": "bidRejected"
},
"REFRESH_IDMODULES_LIST": {
"PRIMARY_MODULES": ["id5Id"],
"SCRIPT_BASED_MODULES": ["zeotapIdPlus", "identityLink"]
},
"MODULE_PARAM_TO_UPDATE_FOR_SSO": {
"id5Id": {
"param": "pd"
}
}
}
3 changes: 2 additions & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export const internal = {
logInfo,
parseQS,
formatQS,
deepEqual
deepEqual,
isEmpty
};

let prebidInternal = {}
Expand Down
77 changes: 76 additions & 1 deletion test/spec/modules/userId_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
syncDelay,
PBJS_USER_ID_OPTOUT_NAME,
findRootDomain,
reTriggerScriptBasedAPICalls
} from 'modules/userId/index.js';
import {createEidsArray} from 'modules/userId/eids.js';
import {config} from 'src/config.js';
Expand Down Expand Up @@ -2553,5 +2554,79 @@ describe('User ID', function () {
});
});
});
})
});

describe('Handle SSO Login', function() {
var dummyGoogleUserObject = { 'getBasicProfile': getBasicProfile };
let sandbox;
let auctionSpy;
let adUnits;

function getEmail() {
return 'abc@def.com';
}
function getBasicProfile() {
return { 'getEmail': getEmail }
}
beforeEach(function () {
(getGlobal()).setUserIdentities({});
window.PWT = window.PWT || {};
// sinon.stub($$PREBID_GLOBAL$$, 'refreshUserIds');
window.PWT.ssoEnabled = true;
sandbox = sinon.createSandbox();
adUnits = [getAdUnitMock()];
auctionSpy = sandbox.spy();
});

afterEach(function() {
// $$PREBID_GLOBAL$$.refreshUserIds.restore();
// $$PREBID_GLOBAL$$.requestBids.removeAll();
config.resetConfig();
});

it('Email hashes are not stored in userIdentities Object on SSO login if ssoEnabled is false', function () {
window.PWT.ssoEnabled = false;

expect(typeof (getGlobal()).onSSOLogin).to.equal('function');
getGlobal().onSSOLogin({'provider': 'google', 'googleUserObject': dummyGoogleUserObject});
expect((getGlobal()).getUserIdentities().emailHash).to.not.exist;
});

it('Email hashes are stored in userIdentities Object on SSO login if ssoEnabled is true', function () {
expect(typeof (getGlobal()).onSSOLogin).to.equal('function');
getGlobal().onSSOLogin({'provider': 'google', 'googleUserObject': dummyGoogleUserObject});
expect((getGlobal()).getUserIdentities().emailHash).to.exist;
});

it('Publisher provided emails are stored in userIdentities.pubProvidedEmailHash if available', function() {
getGlobal().setUserIdentities({'pubProvidedEmail': 'abc@xyz.com'});
expect(getGlobal().getUserIdentities().pubProvidedEmailHash).to.exist;
});

it('should call zeotap api if zeotap module is configured', function() {
var scriptBasedModulesToRefresh = ['zeotapIdPlus'];
console.log('calling reTriggerScriptBasedAPICalls');
window.zeotap = {};
window.zeotap.callMethod = function() { console.log('in call method') };
getGlobal().onSSOLogin({'provider': 'google', 'googleUserObject': dummyGoogleUserObject});

setSubmoduleRegistry([zeotapIdPlusSubmodule]);
init(config);
config.setConfig({
userSync: {
userIds: [{
name: 'zeotapIdPlus',
'storage.type': 'cookie',
'storage.expires': '30',
'storage.name': 'IDP',
'partnerId': 'b13e43f5-9846-4349-ae87-23ea3c3c25de',
'params.loadIDP': 'true'
}]
}
});
requestBidsHook(auctionSpy, {adUnits});

getGlobal().refreshUserIds.calledOnce.should.equal(true);
})
});
});

0 comments on commit d2bcb8d

Please sign in to comment.