From 3a216b3a7587fecb3f1a56505458e0138cd4ad6e Mon Sep 17 00:00:00 2001 From: Manasi Date: Wed, 30 Mar 2022 14:34:16 +0530 Subject: [PATCH] Refreshuserid (#517) * 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 --- modules/userId/index.js | 125 ++++++++++++++++++++++++------- src/constants.json | 9 +++ src/utils.js | 3 +- test/spec/modules/userId_spec.js | 77 ++++++++++++++++++- 4 files changed, 183 insertions(+), 31 deletions(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 29357e1ff32..7ae8996a32f 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -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'; @@ -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; @@ -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(); diff --git a/src/constants.json b/src/constants.json index cc39246d088..33ccd1838b0 100644 --- a/src/constants.json +++ b/src/constants.json @@ -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" + } } } \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index b618e29acba..9bd628ff860 100644 --- a/src/utils.js +++ b/src/utils.js @@ -40,7 +40,8 @@ export const internal = { logInfo, parseQS, formatQS, - deepEqual + deepEqual, + isEmpty }; let prebidInternal = {} diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 85f22693607..153815beecd 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -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'; @@ -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); + }) + }); });