UNPKG

voluptasmollitia

Version:
1,521 lines (1,428 loc) 530 kB
/** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Tests for authuser.js */ goog.provide('fireauth.AuthUserTest'); goog.require('fireauth.ActionCodeSettings'); goog.require('fireauth.Auth'); goog.require('fireauth.AuthCredential'); goog.require('fireauth.AuthError'); goog.require('fireauth.AuthErrorWithCredential'); goog.require('fireauth.AuthEvent'); goog.require('fireauth.AuthEventManager'); goog.require('fireauth.AuthUser'); goog.require('fireauth.AuthUserInfo'); goog.require('fireauth.EmailAuthProvider'); goog.require('fireauth.GoogleAuthProvider'); goog.require('fireauth.MultiFactorAssertion'); goog.require('fireauth.MultiFactorInfo'); goog.require('fireauth.MultiFactorSession'); goog.require('fireauth.MultiFactorUser'); goog.require('fireauth.OAuthSignInHandler'); goog.require('fireauth.PhoneAuthCredential'); goog.require('fireauth.PhoneAuthProvider'); goog.require('fireauth.ProactiveRefresh'); goog.require('fireauth.RpcHandler'); goog.require('fireauth.SAMLAuthProvider'); goog.require('fireauth.StsTokenManager'); goog.require('fireauth.TokenRefreshTime'); goog.require('fireauth.UserEventType'); goog.require('fireauth.UserMetadata'); goog.require('fireauth.authenum.Error'); goog.require('fireauth.common.testHelper'); goog.require('fireauth.constants'); goog.require('fireauth.deprecation'); goog.require('fireauth.idp'); goog.require('fireauth.iframeclient.IfcHandler'); goog.require('fireauth.object'); goog.require('fireauth.storage.PendingRedirectManager'); goog.require('fireauth.storage.RedirectUserManager'); goog.require('fireauth.util'); goog.require('goog.Promise'); goog.require('goog.Uri'); goog.require('goog.events'); goog.require('goog.events.EventTarget'); goog.require('goog.object'); goog.require('goog.testing.AsyncTestCase'); goog.require('goog.testing.MockClock'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.recordFunction'); goog.setTestOnly('fireauth.AuthUserTest'); var config = { apiKey: 'apiKey1' }; var user = null; var accountInfo = null; var accountInfoWithPhone = null; var providerData1 = null; var providerData2 = null; var providerDataPhone = null; var config1 = null; var config2 = null; var rpcHandler = null; var token = null; var tokenResponse = null; var accountInfo2 = null; var getAccountInfoResponse = null; var getAccountInfoResponseProviderData1 = null; var getAccountInfoResponseProviderData2 = null; // A sample JWT, along with its decoded contents. var idTokenGmail = { data: { iss: 'https://securetoken.google.com/projectId', auth_time: 1522715325, sub: '679', aud: 'projectId', iat: 1522776807, provider_id: 'gmail.com', email: 'test123456@gmail.com', federated_id: 'https://www.google.com/accounts/123456789', firebase: { identities: { email: [ 'test123456@gmail.com' ] }, sign_in_provider: 'password' } } }; var idTokenSaml = { data: { iss: 'https://securetoken.google.com/projectId', sub: '679', aud: 'projectId', federated_id: 'https://www.example.com/saml/1234567890', provider_id: 'saml.provider', email: 'test123456@gmail.com' } }; var idTokenCustomClaims = { data: { iss: 'https://securetoken.google.com/projectId', name: 'John Doe', admin: true, aud: 'projectId', auth_time: 1522715325, sub: 'nep2uwNCK4PqjvoKjb0InVJHlGi1', iat: 1522776807, email: "testuser@gmail.com", email_verified: true, firebase: { identities: { email: [ 'testuser@gmail.com' ] }, sign_in_provider: 'password' } } }; var expectedTokenResponseWithIdPData; var expectedAdditionalUserInfo; var expectedGoogleCredential; var expectedReauthenticateTokenResponse; var now = Date.now(); var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); var stubs = new goog.testing.PropertyReplacer(); var ignoreArgument; var mockControl; var app; var auth; var getAccountInfoResponseGoogleProviderData; var getAccountInfoResponsePhoneAuthProviderData; var expectedPhoneNumber; var appVerifier; var expectedRecaptchaToken; var actionCodeSettings = { 'url': 'https://www.example.com/?state=abc', 'iOS': { 'bundleId': 'com.example.ios' }, 'android': { 'packageName': 'com.example.android', 'installApp': true, 'minimumVersion': '12' }, 'handleCodeInApp': true, 'dynamicLinkDomain': 'example.page.link' }; var lastLoginAt = '1506050282000'; var createdAt = '1506044998000'; var lastLoginAt2 = '1506053999000'; var createdAt2 = '1505980145000'; var expectedSamlTokenResponseWithIdPData; var expectedSamlAdditionalUserInfo; var jwt; var newJwt; var userReloadedEventHandler; var multiFactor; var mfaInfo; var multiFactorErrorServerResponse; var multiFactorTokenResponse; var multiFactorGetAccountInfoResponse; var nonMultiFactorTokenResponse; function setUp() { // Disable Auth event manager by default unless needed for a specific test. fireauth.AuthEventManager.ENABLED = false; config1 = { apiKey: 'apiKey1', appName: 'appId1' }; config2 = { apiKey: 'apiKey2', appName: 'appId2' }; idTokenGmail.data.exp = now / 1000 + 3600; idTokenGmail.jwt = fireauth.common.testHelper.createMockJwt(idTokenGmail.data); idTokenSaml.data.exp = now / 1000 + 3600; idTokenSaml.jwt = fireauth.common.testHelper.createMockJwt(idTokenSaml.data); idTokenCustomClaims.data.exp = now / 1000 + 3600; idTokenCustomClaims.jwt = fireauth.common.testHelper.createMockJwt(idTokenCustomClaims.data); // Assume origin is a valid one. stubs.replace( fireauth.RpcHandler.prototype, 'getAuthorizedDomains', function() { var uri = goog.Uri.parse(fireauth.util.getCurrentUrl()); var domain = uri.getDomain(); return goog.Promise.resolve([domain]); }); // Simulate tab can run in background. stubs.replace( fireauth.util, 'runsInBackground', function() { return true; }); // In case the tests are run from an iframe. stubs.replace( fireauth.util, 'isIframe', function() { return false; }); stubs.replace( Date, 'now', function() { return now; }); multiFactor = { 'enrolledFactors': [ { 'uid': 'ENROLLMENT_UID1', 'displayName': 'Work phone number', 'enrollmentTime': new Date(now).toUTCString(), 'factorId': fireauth.constants.SecondFactorType.PHONE, 'phoneNumber': '+16505551234' }, { 'uid': 'ENROLLMENT_UID2', 'displayName': null, 'enrollmentTime': new Date(now).toUTCString(), 'factorId': fireauth.constants.SecondFactorType.PHONE, 'phoneNumber': '+16505556789' } ] }; mfaInfo = [ { 'mfaEnrollmentId': 'ENROLLMENT_UID1', 'displayName': 'Work phone number', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+16505551234' }, { 'mfaEnrollmentId': 'ENROLLMENT_UID2', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+16505556789' } ]; accountInfo = { 'uid': 'defaultUserId', 'email': 'user@default.com', 'displayName': 'defaultDisplayName', 'photoURL': 'https://www.default.com/default/default.png', 'emailVerified': true, 'lastLoginAt': lastLoginAt, 'createdAt': createdAt }; accountInfoWithPhone = { 'uid': 'defaultUserId', 'email': 'user@default.com', 'displayName': 'defaultDisplayName', 'photoURL': 'https://www.default.com/default/default.png', 'emailVerified': true, 'phoneNumber': '+16505550101', 'lastLoginAt': lastLoginAt, 'createdAt': createdAt }; accountInfoWithEnrolledFactors = { 'uid': 'defaultUserId', 'email': 'user@default.com', 'displayName': 'defaultDisplayName', 'photoURL': 'https://www.default.com/default/default.png', 'emailVerified': true, 'lastLoginAt': lastLoginAt, 'createdAt': createdAt, 'multiFactor': multiFactor }; accountInfo2 = { 'uid': '14584746072031976743', 'email': 'uid123@fake.com', 'displayName': 'John Doe', // common_typos_disable. 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + 't_profile_3_normal.png', 'emailVerified': true, 'lastLoginAt': lastLoginAt2, 'createdAt': createdAt2 }; providerData1 = new fireauth.AuthUserInfo( 'providerUserId1', 'providerId1', 'user1@example.com', 'user1', 'https://www.example.com/user1/photo.png'); providerData2 = new fireauth.AuthUserInfo( 'providerUserId2', 'providerId2', 'user2@example.com', 'user2', 'https://www.example.com/user2/photo.png'); providerDataPhone = new fireauth.AuthUserInfo( '+16505550101', 'phone', undefined, undefined, undefined, '+16505550101'); rpcHandler = new fireauth.RpcHandler('apiKey1'); token = new fireauth.StsTokenManager(rpcHandler); token.setRefreshToken('refreshToken'); jwt = fireauth.common.testHelper.createMockJwt( {'group': '1'}, now + 3600 * 1000); newJwt = fireauth.common.testHelper.createMockJwt( {'group': '2'}, now + 3600 * 1000); token.setAccessToken(jwt); tokenResponse = { 'idToken': jwt, 'refreshToken': 'refreshToken', 'expiresIn': '4800' }; // accountInfo in the format of a getAccountInfo response. getAccountInfoResponse = { 'users': [{ 'localId': 'defaultUserId', 'email': 'user@default.com', 'emailVerified': true, 'phoneNumber': '+16505550101', 'displayName': 'defaultDisplayName', 'providerUserInfo': [], 'photoUrl': 'https://www.default.com/default/default.png', 'passwordUpdatedAt': 0.0, 'disabled': false, 'lastLoginAt': lastLoginAt, 'createdAt': createdAt }] }; // providerData1 and providerData2 in the format of a getAccountInfo response. getAccountInfoResponseProviderData1 = { 'providerId': 'providerId1', 'displayName': 'user1', 'email': 'user1@example.com', 'photoUrl': 'https://www.example.com/user1/photo.png', 'rawId': 'providerUserId1' }; getAccountInfoResponseProviderData2 = { 'providerId': 'providerId2', 'displayName': 'user2', 'email': 'user2@example.com', 'photoUrl': 'https://www.example.com/user2/photo.png', 'rawId': 'providerUserId2' }; getAccountInfoResponseGoogleProviderData = { 'providerId': 'google.com', 'displayName': 'My Google Name', 'email': 'me@gmail.com', 'photoUrl': 'https://www.google.com/me.png', 'rawId': 'myGoogleId' }; getAccountInfoResponsePhoneAuthProviderData = { 'providerId': 'phone', 'rawId': '+16505550101', 'phoneNumber': '+16505550101' }; expectedTokenResponseWithIdPData = { 'idToken': newJwt, 'refreshToken': 'newRefreshToken', // Credential returned. 'providerId': 'google.com', 'oauthAccessToken': 'googleAccessToken', 'oauthIdToken': 'googleIdToken', 'oauthExpireIn': 3600, // Additional user info data. 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + 'me":{"givenName":"John","familyName":"Doe"}}' }; expectedReauthenticateTokenResponse = { 'idToken': idTokenGmail.jwt, 'refreshToken': 'myRefreshToken', // Credential returned. 'providerId': 'google.com', 'oauthAccessToken': 'googleAccessToken', 'oauthIdToken': 'googleIdToken', 'oauthExpireIn': 3600, // Additional user info data. 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + 'me":{"givenName":"John","familyName":"Doe"}}' }; expectedAdditionalUserInfo = { 'profile': { 'kind': 'plus#person', 'displayName': 'John Doe', 'name': { 'givenName': 'John', 'familyName': 'Doe' } }, 'providerId': 'google.com', 'isNewUser': false }; expectedGoogleCredential = fireauth.GoogleAuthProvider.credential( 'googleIdToken', 'googleAccessToken'); expectedSamlTokenResponseWithIdPData = { 'idToken': newJwt, 'refreshToken': 'newRefreshToken', 'providerId': 'saml.provider', // Additional user info data. 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe","na' + 'me":{"givenName":"John","familyName":"Doe"}}' }; expectedSamlAdditionalUserInfo = { 'profile': { 'kind': 'plus#person', 'displayName': 'John Doe', 'name': { 'givenName': 'John', 'familyName': 'Doe' } }, 'providerId': 'saml.provider', 'isNewUser': false }; expectedPhoneNumber = '+16505550101'; expectedRecaptchaToken = 'RECAPTCHA_TOKEN'; appVerifier = { 'type': 'recaptcha', 'verify': function() { return goog.Promise.resolve(expectedRecaptchaToken); } }; ignoreArgument = goog.testing.mockmatchers.ignoreArgument; mockControl = new goog.testing.MockControl(); mockControl.$resetAll(); nonMultiFactorTokenResponse = { 'idToken': fireauth.common.testHelper.createMockJwt({ 'sub': 'defaultUserId', 'firebase': { 'sign_in_provider': 'password' } }), 'refreshToken': 'SINGLE_FACTOR_REFRESH_TOKEN' }; multiFactorTokenResponse = { 'idToken': fireauth.common.testHelper.createMockJwt({ 'sub': 'defaultUserId', 'firebase': { 'sign_in_provider': 'password', 'sign_in_second_factor': 'phone' } }), 'refreshToken': 'MULTI_FACTOR_REFRESH_TOKEN' }; multiFactorErrorServerResponse = { 'mfaInfo': [ { 'mfaEnrollmentId': 'ENROLLMENT_UID1', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+*******1234' }, { 'mfaEnrollmentId': 'ENROLLMENT_UID2', 'displayName': 'Spouse phone number', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+*******6789' } ], 'mfaPendingCredential': 'PENDING_CREDENTIAL', // Credential returned. 'providerId': 'google.com', 'oauthAccessToken': 'googleAccessToken', 'oauthIdToken': 'googleIdToken', 'oauthExpireIn': 3600, // Additional user info data. 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + '"name":{"givenName":"John","familyName":"Doe"}}' }; multiFactorGetAccountInfoResponse = { 'users': [{ 'localId': 'defaultUserId', 'email': 'user@default.com', 'emailVerified': true, 'displayName': 'defaultDisplayName', 'providerUserInfo': [], 'photoUrl': 'https://www.default.com/default/default.png', 'passwordUpdatedAt': 0.0, 'disabled': false, 'lastLoginAt': '1506050282000', 'createdAt': '1506050282000', 'mfaInfo': [ { 'mfaEnrollmentId': 'ENROLLMENT_UID1', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+16505551234' }, { 'mfaEnrollmentId': 'ENROLLMENT_UID2', 'displayName': 'Spouse phone number', 'enrolledAt': new Date(now).toISOString(), 'phoneInfo': '+16505556789' } ] }] }; } function tearDown() { for (var i = 0; i < firebase.apps.length; i++) { asyncTestCase.waitForSignals(1); firebase.apps[i].delete().then(function() { asyncTestCase.signal(); }); } if (auth) { auth.delete(); } // Reset already initialized Auth event managers. fireauth.AuthEventManager.manager_ = {}; user = null; accountInfo = null; accountInfoWithPhone = null; accountInfoWithEnrolledFactors = null; accountInfo2 = null; getAccountInfoResponse = null; getAccountInfoResponseProviderData1 = null; getAccountInfoResponseProviderData2 = null; providerData1 = null; providerData2 = null; providerDataPhone = null; rpcHandler = null; token = null; tokenResponse = null; config1 = null; config2 = null; multiFactor = null; mfaInfo = null; multiFactorErrorServerResponse = null; multiFactorTokenResponse = null; multiFactorGetAccountInfoResponse = null; nonMultiFactorTokenResponse = null; window.localStorage.clear(); window.sessionStorage.clear(); stubs.reset(); try { mockControl.$verifyAll(); } finally { mockControl.$tearDown(); } } /** @return {!goog.events.EventTarget} The event dispatcher test object. */ function createEventDispatcher() { return new goog.events.EventTarget(); } /** * Asserts that token events do not trigger. * @param {!fireauth.AuthUser} user */ function assertNoTokenEvents(user) { goog.events.listen( user, fireauth.UserEventType.TOKEN_CHANGED, function(event) { fail('Token change should not trigger due to token being unchanged!'); }); } /** * Asserts that user invalidated events do not trigger. * @param {!fireauth.AuthUser} user */ function assertNoUserInvalidatedEvents(user) { goog.events.listen( user, fireauth.UserEventType.USER_INVALIDATED, function(event) { fail('User invalidate event should not trigger!'); }); } /** * Asserts that state events do not trigger. * @param {!fireauth.AuthUser} user */ function assertNoStateEvents(user) { user.addStateChangeListener(function(userTemp) { fail('State change listener should not trigger!'); }); } /** * Asserts that delete events do not trigger. * @param {!fireauth.AuthUser} user */ function assertNoDeleteEvents(user) { goog.events.listen( user, fireauth.UserEventType.USER_DELETED, function(event) { fail('User deleted listener should not trigger!'); }); } /** * Asserts that a method should fail when user is destroyed and no listeners * are triggered. * @param {string} methodName The name of the method of AuthUser that should * fail if the user is destroyed. * @param {!Array} parameters The arguments to pass to the method. */ function assertFailsWhenUserIsDestroyed(methodName, parameters) { asyncTestCase.waitForSignals(1); user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); assertNoStateEvents(user); assertNoTokenEvents(user); assertNoDeleteEvents(user); assertNoUserInvalidatedEvents(user); user.destroy(); user[methodName].apply(user, parameters).then(fail, function(error) { fireauth.common.testHelper.assertErrorEquals( new fireauth.AuthError(fireauth.authenum.Error.MODULE_DESTROYED), error); asyncTestCase.signal(1); }); } function testProviderData() { assertEquals('providerUserId1', providerData1['uid']); assertEquals('providerId1', providerData1['providerId']); assertEquals('user1@example.com', providerData1['email']); assertEquals('user1', providerData1['displayName']); assertEquals( 'https://www.example.com/user1/photo.png', providerData1['photoURL']); } function testUser() { accountInfo['email'] = null; providerData1 = new fireauth.AuthUserInfo( 'providerUserId1', 'providerId1', 'user1@example.com', null, 'https://www.example.com/user1/photo.png'); user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user.addProviderData(providerData1); user.addProviderData(providerData2); assertObjectEquals( new fireauth.UserMetadata(createdAt, lastLoginAt), user.metadata); assertEquals(jwt, user.lastAccessToken_); assertEquals('defaultUserId', user['uid']); assertEquals('defaultDisplayName', user['displayName']); assertNull(user['email']); assertEquals('https://www.default.com/default/default.png', user['photoURL']); assertEquals('firebase', user['providerId']); assertEquals(false, user['isAnonymous']); assertNull(user['tenantId']); assertArrayEquals(['providerId1', 'providerId2'], user.getProviderIds()); assertObjectEquals( { 'uid': 'providerUserId1', 'displayName': null, 'photoURL': 'https://www.example.com/user1/photo.png', 'email': 'user1@example.com', 'providerId': 'providerId1', 'phoneNumber': null }, user['providerData'][0]); assertObjectEquals( { 'uid': 'providerUserId2', 'displayName': 'user2', 'photoURL': 'https://www.example.com/user2/photo.png', 'email': 'user2@example.com', 'providerId': 'providerId2', 'phoneNumber': null }, user['providerData'][1]); // Test popup event ID setters and getters. assertNull(user.getPopupEventId()); user.setPopupEventId('1234'); assertEquals('1234', user.getPopupEventId()); user.setPopupEventId('5678'); assertEquals('5678', user.getPopupEventId()); // Test redirect event ID setters and getters. assertNull(user.getRedirectEventId()); user.setRedirectEventId('1234'); assertEquals('1234', user.getRedirectEventId()); user.setRedirectEventId('5678'); assertEquals('5678', user.getRedirectEventId()); // Test ApiKey getter. assertEquals('apiKey1', user.getApiKey()); } /** * Asserts that a user initiated with an emulator config will propagate * the config to the RPC handler. */ function tesUser_initWithEmulator() { // Listen to emulator config calls on RpcHandler. stubs.replace( fireauth.RpcHandler.prototype, 'updateEmulatorConfig', goog.testing.recordFunction()); // Initialize a user. user = new fireauth.AuthUser( { appName: config1.appName, apiKey: config1.apiKey, emulatorConfig: { url: 'http://emulator.test.domain:1234' } } ); // Should notify the RPC handler. assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertObjectEquals( { url: 'http://emulator.test.domain:1234', }, fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() .getArgument(0) ); } function testUser_multiFactor() { user = new fireauth.AuthUser( config1, tokenResponse, accountInfoWithEnrolledFactors); user.addProviderData(providerData1); user.addProviderData(providerData2); assertTrue(user.multiFactor instanceof fireauth.MultiFactorUser); assertObjectEquals( new fireauth.MultiFactorUser(user, accountInfoWithEnrolledFactors), user.multiFactor); } function testUser_copyUser() { config1['authDomain'] = 'subdomain.firebaseapp.com'; config2['authDomain'] = 'subdomain.firebaseapp.com'; asyncTestCase.waitForSignals(1); // Sets the tenant ID on user to be copied. accountInfo['tenantId'] = 'TENANT_ID'; user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user.addProviderData(providerData1); user.addProviderData(providerData2); stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', goog.testing.recordFunction(function(idToken) { // Mocks that tenant ID is returned in getAccountInfo response. getAccountInfoResponse['users'][0]['tenantId'] = 'TENANT_ID'; return goog.Promise.resolve(getAccountInfoResponse); })); var expectedEventId = '1234'; stubs.replace( fireauth.util, 'generateEventId', function() { // An event ID should be generated. return expectedEventId; }); storageManager = new fireauth.storage.RedirectUserManager( fireauth.util.createStorageKey(config2['apiKey'], config2['appName'])); var frameworks = ['firebaseui', 'angularfire']; fireauth.AuthEventManager.ENABLED = true; var oAuthSignInHandlerInstance = mockControl.createStrictMock(fireauth.OAuthSignInHandler); mockControl.createConstructorMock(fireauth, 'OAuthSignInHandler'); var instantiateOAuthSignInHandler = mockControl.createMethodMock( fireauth.AuthEventManager, 'instantiateOAuthSignInHandler'); instantiateOAuthSignInHandler( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument).$returns(oAuthSignInHandlerInstance); oAuthSignInHandlerInstance.shouldBeInitializedEarly().$returns(false); oAuthSignInHandlerInstance.hasVolatileStorage().$returns(false); oAuthSignInHandlerInstance.processRedirect( ignoreArgument, ignoreArgument, ignoreArgument, ignoreArgument).$does(function( actualMode, actualProvider, actualEventId) { assertEquals( fireauth.AuthEvent.Type.LINK_VIA_REDIRECT, actualMode); assertEquals(expectedProvider, actualProvider); assertEquals(expectedEventId, actualEventId); return goog.Promise.resolve(); }); oAuthSignInHandlerInstance.unloadsOnRedirect().$returns(false); mockControl.$replayAll(); var copiedUser = fireauth.AuthUser.copyUser( user, config2, storageManager, frameworks); // Verifies that user is not reloaded on copying. assertEquals( 0, fireauth.RpcHandler.prototype.getAccountInfoByIdToken.getCallCount()); fireauth.common.testHelper.assertUserEqualsInWithDiffApikey( user, copiedUser, config1['apiKey'], config2['apiKey']); assertFalse(copiedUser['isAnonymous']); assertEquals('TENANT_ID', copiedUser['tenantId']); // Confirm frameworks set on created user. assertArrayEquals(frameworks, copiedUser.getFramework()); var expectedProvider = new fireauth.GoogleAuthProvider(); expectedProvider.addScope('scope1'); expectedProvider.addScope('scope2'); copiedUser.enablePopupRedirect(); copiedUser.linkWithRedirect(expectedProvider).then(function() { assertEquals(1, fireauth.RpcHandler.prototype.getAccountInfoByIdToken.getCallCount()); // Redirect event ID should be saved. assertEquals(expectedEventId, copiedUser.getRedirectEventId()); // Redirect user should be saved in storage with correct redirect event ID. storageManager.getRedirectUser().then(function(user) { assertEquals(expectedEventId, user.getRedirectEventId()); assertObjectEquals(copiedUser.toPlainObject(), user.toPlainObject()); asyncTestCase.signal(); }); }); } function testUser_copyUser_defaultConfig() { user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user.addProviderData(providerData1); user.addProviderData(providerData2); var copiedUser = fireauth.AuthUser.copyUser(user); assertObjectEquals(copiedUser.toPlainObject(), user.toPlainObject()); assertNull(user.getPopupEventId()); assertNull(user.getRedirectEventId()); } function testUser_copyUser_expiredToken() { // Mock the expired token. tokenResponse['idToken'] = fireauth.common.testHelper.createMockJwt(null, now - 5); stubs.replace( fireauth.RpcHandler.prototype, 'requestStsToken', function(data) { // Copying user should not trigger token refresh even if the token // expires. fail('The token should not be refreshed!'); }); user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); var copiedUser = fireauth.AuthUser.copyUser(user); assertObjectEquals(copiedUser.toPlainObject(), user.toPlainObject()); } function testUser_copyUser_multiFactor() { user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user2 = new fireauth.AuthUser( config1, tokenResponse, accountInfoWithEnrolledFactors); user3 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); var multiFactorUser1 = user1.multiFactor; user1.copy(user2); // Same reference should be kept. assertEquals(user1.multiFactor, multiFactorUser1); assertObjectEquals( new fireauth.MultiFactorUser(user2, accountInfoWithEnrolledFactors) .toPlainObject(), user1.multiFactor.toPlainObject()); // user1.multiFactor user reference should keep pointing to user1. assertEquals(user1, user1.multiFactor.getUser()); user1.copy(user3); // Same reference should be kept. assertEquals(user1.multiFactor, multiFactorUser1); assertObjectEquals( new fireauth.MultiFactorUser(user3, accountInfo).toPlainObject(), user1.multiFactor.toPlainObject()); assertEquals(0, user1.multiFactor.enrolledFactors.length); // user1.multiFactor user reference should keep pointing to user1. assertEquals(user1, user1.multiFactor.getUser()); } function testUser_rpcHandlerEndpoints() { // Confirm expected endpoint config passed to underlying RPC handler. var endpoint = fireauth.constants.Endpoint.STAGING; var endpointConfig = { 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, 'secureTokenEndpoint': endpoint.secureTokenEndpoint, 'identityPlatformEndpoint': endpoint.identityPlatformEndpoint }; stubs.replace( fireauth.constants, 'getEndpointConfig', function(opt_id) { return endpointConfig; }); var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); var rpcHandlerConstructor = mockControl.createConstructorMock( fireauth, 'RpcHandler'); rpcHandlerConstructor(config1['apiKey'], endpointConfig, ignoreArgument) .$returns(rpcHandler); rpcHandler.updateTenantId(null); mockControl.$replayAll(); user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); } function testUser_rpcHandlerEndpoints_tenantId() { // Confirm expected endpoint config passed to underlying RPC handler. var endpoint = fireauth.constants.Endpoint.STAGING; var endpointConfig = { 'firebaseEndpoint': endpoint.firebaseAuthEndpoint, 'secureTokenEndpoint': endpoint.secureTokenEndpoint }; stubs.replace( fireauth.constants, 'getEndpointConfig', function(opt_id) { return endpointConfig; }); var rpcHandler = mockControl.createStrictMock(fireauth.RpcHandler); var rpcHandlerConstructor = mockControl.createConstructorMock( fireauth, 'RpcHandler'); rpcHandlerConstructor(config1['apiKey'], endpointConfig, ignoreArgument) .$returns(rpcHandler); // Tenant ID of RPC handler should be updated. rpcHandler.updateTenantId('TENANT_ID'); mockControl.$replayAll(); // Sets the tenant ID on user. accountInfo['tenantId'] = 'TENANT_ID'; user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); assertEquals('TENANT_ID', user['tenantId']); } function testUser_stateChangeListeners() { // Test user state change listeners: adding, removing and their execution. asyncTestCase.waitForSignals(3); var listener1 = goog.testing.recordFunction(function(userTemp) { assertEquals(user, userTemp); // Whether it resolves or rejects, it shouldn't affect the outcome. return goog.Promise.resolve(); }); var listener2 = goog.testing.recordFunction(function(userTemp) { assertEquals(user, userTemp); // Whether it resolves or rejects, it shouldn't affect the outcome. return goog.Promise.reject(); }); // Listener that does not return a promise. var listener3 = goog.testing.recordFunction(); user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); // Add all listeners. user.addStateChangeListener(listener1); user.addStateChangeListener(listener2); user.addStateChangeListener(listener3); // Notify listeners. user.notifyStateChangeListeners_().then(function(userTemp) { assertEquals(user, userTemp); // All should run. assertEquals(1, listener1.getCallCount()); assertEquals(1, listener2.getCallCount()); assertEquals(1, listener3.getCallCount()); // Remove second and third listener. user.removeStateChangeListener(listener2); user.removeStateChangeListener(listener3); asyncTestCase.signal(); // Notify listeners. user.notifyStateChangeListeners_().then(function(userTemp) { assertEquals(user, userTemp); // Only first listener should run. assertEquals(2, listener1.getCallCount()); assertEquals(1, listener2.getCallCount()); assertEquals(1, listener3.getCallCount()); // Remove remaining listener. user.removeStateChangeListener(listener1); asyncTestCase.signal(); // Notify listeners. user.notifyStateChangeListeners_().then(function(userTemp) { assertEquals(user, userTemp); // No listener should run. assertEquals(2, listener1.getCallCount()); assertEquals(1, listener2.getCallCount()); assertEquals(1, listener3.getCallCount()); asyncTestCase.signal(); }); }); }); } function testGetRpcHandler() { user1 = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user2 = new fireauth.AuthUser(config2, tokenResponse, accountInfo); assertTrue(user1.getRpcHandler() instanceof fireauth.RpcHandler); assertTrue(user2.getRpcHandler() instanceof fireauth.RpcHandler); assertEquals(config1['apiKey'], user1.getRpcHandler().getApiKey()); assertEquals(config2['apiKey'], user2.getRpcHandler().getApiKey()); } function testAddProviderData_sameProviderId() { var providerData1 = new fireauth.AuthUserInfo( 'providerUserId1', 'theProviderId', 'user1@example.com', null, 'https://www.example.com/user1/photo.png'); var providerData2 = new fireauth.AuthUserInfo( 'providerUserId2', 'theProviderId', 'user2@example.com', null, 'https://www.example.com/user2/photo.png'); user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user.addProviderData(providerData1); user.addProviderData(providerData2); assertArrayEquals(['theProviderId'], user.getProviderIds()); assertArrayEquals([{ 'uid': 'providerUserId2', 'displayName': null, 'photoURL': 'https://www.example.com/user2/photo.png', 'email': 'user2@example.com', 'providerId': 'theProviderId', 'phoneNumber': null }], user['providerData']); } function testUser_removeProviderData() { user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user.addProviderData(providerData1); user.addProviderData(providerData2); assertArrayEquals(['providerId1', 'providerId2'], user.getProviderIds()); user.removeProviderData('providerId1'); assertArrayEquals(['providerId2'], user.getProviderIds()); } function testUser_setUserAccountInfoFromToken_success() { var response = { 'users': [{ 'localId': '14584746072031976743', 'email': 'uid123@fake.com', 'emailVerified': true, 'displayName': 'John Doe', 'providerUserInfo': [ { 'email': 'user@gmail.com', 'providerId': 'google.com', 'displayName': 'John G. Doe', 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', 'federatedId': 'https://accounts.google.com/123456789', 'rawId': '123456789' }, { 'providerId': 'twitter.com', 'displayName': 'John Gammell Doe', 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'federatedId': 'http://twitter.com/987654321', 'rawId': '987654321' } ], 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'passwordUpdatedAt': 0.0, 'disabled': false }] }; var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { 'uid': '14584746072031976743', 'email': 'uid123@fake.com', 'displayName': 'John Doe', 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + 't_profile_3_normal.png', 'emailVerified': true }); expectedUser.addProviderData(new fireauth.AuthUserInfo( '123456789', 'google.com', 'user@gmail.com', 'John G. Doe', 'https://lh5.googleusercontent.com/123456789/photo.jpg')); expectedUser.addProviderData(new fireauth.AuthUserInfo( '987654321', 'twitter.com', null, 'John Gammell Doe', 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + '3_normal.png')); stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(data) { return new goog.Promise(function(resolve, reject) { assertEquals(jwt, data); resolve(response); }); }); asyncTestCase.waitForSignals(1); // Initialize user with no account info or provider data. user = new fireauth.AuthUser(config1, tokenResponse); // Record event triggers on USER_RELOADED. var userReloadedEventHandler = goog.testing.recordFunction(); goog.events.listen( user, fireauth.UserEventType.USER_RELOADED, userReloadedEventHandler); var stateChangedCounter = 0; user.addStateChangeListener(function(user) { stateChangedCounter++; return goog.Promise.resolve(); }); assertNoTokenEvents(user); assertNoUserInvalidatedEvents(user); assertEquals(0, userReloadedEventHandler.getCallCount()); user.reload().then(function() { assertEquals(1, stateChangedCounter); fireauth.common.testHelper.assertUserEquals(expectedUser, user); // Confirm event triggered on reload with the expected properties. assertEquals(1, userReloadedEventHandler.getCallCount()); var event = userReloadedEventHandler.getLastCall().getArgument(0); assertObjectEquals(response.users[0], event.userServerResponse); asyncTestCase.signal(); }); } function testSetUserAccountInfoFromToken_success_emailAndPassword() { var response = { 'users': [{ 'localId': '14584746072031976743', 'email': 'uid123@fake.com', 'emailVerified': true, 'displayName': 'John Doe', 'passwordHash': 'PASSWORD_HASH', 'providerUserInfo': [], 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'passwordUpdatedAt': 0.0, 'disabled': false }] }; var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { 'uid': '14584746072031976743', 'email': 'uid123@fake.com', 'displayName': 'John Doe', 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + 't_profile_3_normal.png', 'emailVerified': true }); stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(data) { return new goog.Promise(function(resolve, reject) { assertEquals(jwt, data); resolve(response); }); }); user = new fireauth.AuthUser(config1, tokenResponse); // Record event triggers on USER_RELOADED. var userReloadedEventHandler = goog.testing.recordFunction(); goog.events.listen( user, fireauth.UserEventType.USER_RELOADED, userReloadedEventHandler); asyncTestCase.waitForSignals(1); assertEquals(0, userReloadedEventHandler.getCallCount()); user.reload().then(function() { fireauth.common.testHelper.assertUserEquals(expectedUser, user); // Confirm event triggered on reload with the expected properties. assertEquals(1, userReloadedEventHandler.getCallCount()); var event = userReloadedEventHandler.getLastCall().getArgument(0); assertObjectEquals(response.users[0], event.userServerResponse); asyncTestCase.signal(); }); } function testSetUserAccountInfoFromToken_success_emailNoPassword() { var response = { 'users': [{ 'localId': '14584746072031976743', 'email': 'uid123@fake.com', 'emailVerified': true, 'displayName': 'John Doe', 'providerUserInfo': [], 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + 't_profile_3_normal.png', 'passwordUpdatedAt': 0.0, 'disabled': false }] }; var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { 'uid': '14584746072031976743', 'email': 'uid123@fake.com', 'displayName': 'John Doe', 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + 't_profile_3_normal.png', 'emailVerified': true, 'isAnonymous': false }); stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(data) { return new goog.Promise(function(resolve, reject) { assertEquals(jwt, data); resolve(response); }); }); user = new fireauth.AuthUser(config1, tokenResponse); asyncTestCase.waitForSignals(1); user.reload().then(function() { assertObjectEquals(expectedUser.toPlainObject(), user.toPlainObject()); asyncTestCase.signal(); }); } function testSetUserAccountInfoFromToken_success_passwordNoEmail() { var response = { 'users': [{ 'localId': '14584746072031976743', 'email': '', 'displayName': 'John Doe', 'passwordHash': 'PASSWORD_HASH', 'providerUserInfo': [], 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + 't_profile_3_normal.png', 'passwordUpdatedAt': 0.0, 'disabled': false }] }; var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { 'uid': '14584746072031976743', 'email': '', 'displayName': 'John Doe', 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + 't_profile_3_normal.png', 'isAnonymous': false }); stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(data) { return new goog.Promise(function(resolve, reject) { assertEquals(jwt, data); resolve(response); }); }); user = new fireauth.AuthUser(config1, tokenResponse); asyncTestCase.waitForSignals(1); user.reload().then(function() { fireauth.common.testHelper.assertUserEquals(expectedUser, user); asyncTestCase.signal(); }); } function testUser_setUserAccountInfoFromToken_multiFactor_success() { var response = { 'users': [{ 'localId': '14584746072031976743', 'email': 'uid123@fake.com', 'emailVerified': true, 'displayName': 'John Doe', 'providerUserInfo': [ { 'email': 'user@gmail.com', 'providerId': 'google.com', 'displayName': 'John G. Doe', 'photoUrl': 'https://lh5.googleusercontent.com/123456789/photo.jpg', 'federatedId': 'https://accounts.google.com/123456789', 'rawId': '123456789' }, { 'providerId': 'twitter.com', 'displayName': 'John Gammell Doe', 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'federatedId': 'http://twitter.com/987654321', 'rawId': '987654321' } ], 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'passwordUpdatedAt': 0.0, 'disabled': false, 'mfaInfo': mfaInfo }] }; var expectedUser = new fireauth.AuthUser(config1, tokenResponse, { 'uid': '14584746072031976743', 'email': 'uid123@fake.com', 'displayName': 'John Doe', 'photoURL': 'http://abs.twimg.com/sticky/default_profile_images/defaul' + 't_profile_3_normal.png', 'emailVerified': true, 'multiFactor': multiFactor }); expectedUser.addProviderData(new fireauth.AuthUserInfo( '123456789', 'google.com', 'user@gmail.com', 'John G. Doe', 'https://lh5.googleusercontent.com/123456789/photo.jpg')); expectedUser.addProviderData(new fireauth.AuthUserInfo( '987654321', 'twitter.com', null, 'John Gammell Doe', 'http://abs.twimg.com/sticky/default_profile_images/default_profile_' + '3_normal.png')); stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(data) { return new goog.Promise(function(resolve, reject) { assertEquals(jwt, data); resolve(response); }); }); asyncTestCase.waitForSignals(1); // Initialize user with no account info or provider data. user = new fireauth.AuthUser(config1, tokenResponse); var multiFactorUser = user.multiFactor; assertEquals(0, user.multiFactor.enrolledFactors.length); // Record event triggers on USER_RELOADED. var userReloadedEventHandler = goog.testing.recordFunction(); goog.events.listen( user, fireauth.UserEventType.USER_RELOADED, userReloadedEventHandler); var stateChangedCounter = 0; user.addStateChangeListener(function(user) { stateChangedCounter++; return goog.Promise.resolve(); }); assertNoTokenEvents(user); assertNoUserInvalidatedEvents(user); assertEquals(0, userReloadedEventHandler.getCallCount()); user.reload().then(function() { assertEquals(1, stateChangedCounter); fireauth.common.testHelper.assertUserEquals(expectedUser, user); // Confirm event triggered on reload with the expected properties. assertEquals(1, userReloadedEventHandler.getCallCount()); var event = userReloadedEventHandler.getLastCall().getArgument(0); assertObjectEquals(response.users[0], event.userServerResponse); // Confirm enrolled factors updated. assertEquals(mfaInfo.length, user.multiFactor.enrolledFactors.length); assertObjectEquals( fireauth.MultiFactorInfo.fromServerResponse(mfaInfo[0]), user.multiFactor.enrolledFactors[0]); assertObjectEquals( fireauth.MultiFactorInfo.fromServerResponse(mfaInfo[1]), user.multiFactor.enrolledFactors[1]); // Multifactor reference should not be updated. assertEquals(multiFactorUser, user.multiFactor); asyncTestCase.signal(); }); } function testSetUserAccountInfoFromToken_success_tenantId() { var response = { 'users': [{ 'localId': '14584746072031976743', 'email': 'uid123@fake.com', 'emailVerified': true, 'displayName': 'John Doe', 'passwordHash': 'PASSWORD_HASH', 'providerUserInfo': [], 'photoUrl': 'http://abs.twimg.com/sticky/default_profile_images/' + 'default_profile_3_normal.png', 'passwordUpdatedAt': 0.0, 'disabled': false, 'tenantId': 'TENANT_ID' }] }; var updateTenantId = mockControl.createMethodMock( fireauth.RpcHandler.prototype, 'updateTenantId'); // Tenant ID of RPC handler should be initialized to null. updateTenantId(null).$once(); // Tenant ID of RPC handler should be updated by setAccountInfo. updateTenantId('TENANT_ID').$once(); stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(data) { assertEquals(jwt, data); return goog.Promise.resolve(response); }); mockControl.$replayAll(); user = new fireauth.AuthUser(config1, tokenResponse); asyncTestCase.waitForSignals(1); user.reload().then(function() { assertEquals('TENANT_ID', user['tenantId']); asyncTestCase.signal(); }); } function testUser_setUserAccountInfoFromToken_error() { var error = { 'error': fireauth.authenum.Error.INTERNAL_ERROR }; stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(data) { return goog.Promise.reject(error); }); asyncTestCase.waitForSignals(1); user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user.reload().thenCatch(function(e) { // User data unchanged. for (var key in accountInfo) { // Metadata is structured differently in user compared to accountInfo. if (key == 'lastLoginAt') { assertEquals( fireauth.util.utcTimestampToDateString(accountInfo[key]), user['metadata']['lastSignInTime']); } else if (key == 'createdAt') { assertEquals( fireauth.util.utcTimestampToDateString(accountInfo[key]), user['metadata']['creationTime']); } else { assertEquals(accountInfo[key], user[key]); } } assertObjectEquals(error, e); asyncTestCase.signal(); }); } function testUser_setUserAccountInfoFromToken_invalidResponse() { // Test with invalid server response. stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(data) { // Resolve getAccountInfo with invalid server response. return goog.Promise.resolve({}); }); asyncTestCase.waitForSignals(1); user = new fireauth.AuthUser(config1, tokenResponse, accountInfo); user.reload().thenCatch(function(error) { var expected = new fireauth.AuthError( fireauth.authenum.Error.INTERNAL_ERROR); assertEquals(expected.code, error.code); // User data unchanged. for (var key in accountInfo) { // Metadata is structured differently in user compared to accountInfo. if (key == 'lastLoginAt') { assertEquals( fireauth.util.utcTimestampToDateString(accountInfo[key]), user['metadata']['lastSignInTime']); } else if (key == 'createdAt') { assertEquals( fireauth.util.utcTimestampToDateString(accountInfo[key]), user['metadata']['creationTime']); } else { assertEquals(accountInfo[key], user[key]); } } asyncTestCase.signal(); }); } function testUser_reload_success() { user = new fireauth.AuthUser(config1, tokenResponse, accountInfo2); user.addStateChangeListener(function(user) { asyncTestCase.signal(); return goog.Promise.resolve(); }); assertNoTokenEvents(user); assertNoUserInvalidatedEvents(user); stubs.replace( fireauth.RpcHandler.prototype, 'getAccountInfoByIdToken', function(idToken) { assertEquals(jwt, idToken); user.copy(updatedUs