From 821f4d30fd0be192bb4bb79d71ff2279bbd14217 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 2 Jul 2021 11:22:35 -0700 Subject: [PATCH] [Auth] Implement origin validation for fast erroring-out in Cordova popup redirect resolver (#5103) * Implement extra origin validation on Cordova for quick-error-out * Formatting --- .../api/project_config/get_project_config.ts | 10 +++++-- .../popup_redirect/popup_redirect.test.ts | 6 ++-- .../popup_redirect/popup_redirect.ts | 16 ++++++++-- .../popup_redirect/utils.test.ts | 29 +++++++++++++++++++ .../platform_cordova/popup_redirect/utils.ts | 22 ++++++++++++++ 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/packages-exp/auth-exp/src/api/project_config/get_project_config.ts b/packages-exp/auth-exp/src/api/project_config/get_project_config.ts index 48df651f33c..7aaca5d8309 100644 --- a/packages-exp/auth-exp/src/api/project_config/get_project_config.ts +++ b/packages-exp/auth-exp/src/api/project_config/get_project_config.ts @@ -18,19 +18,23 @@ import { _performApiRequest, Endpoint, HttpMethod } from '../index'; import { Auth } from '../../model/public_types'; -export interface GetProjectConfigRequest {} +export interface GetProjectConfigRequest { + androidPackageName?: string; + iosBundleId?: string; +} export interface GetProjectConfigResponse { authorizedDomains: string[]; } export async function _getProjectConfig( - auth: Auth + auth: Auth, + request: GetProjectConfigRequest = {} ): Promise { return _performApiRequest( auth, HttpMethod.GET, Endpoint.GET_PROJECT_CONFIG, - {} + request ); } diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts index c532ee3dfcf..9a867a07b9d 100644 --- a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.test.ts @@ -61,7 +61,8 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => { beforeEach(async () => { auth = await testAuth(); - resolver = new (cordovaPopupRedirectResolver as SingletonInstantiator)(); + resolver = + new (cordovaPopupRedirectResolver as SingletonInstantiator)(); provider = new GoogleAuthProvider(); utilsStubs = sinon.stub(utils); eventsStubs = { @@ -88,7 +89,7 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => { afterEach(() => { sinon.restore(); universalLinksCb = null; - const anyWindow = (win as unknown) as Record; + const anyWindow = win as unknown as Record; delete anyWindow.universalLinks; delete anyWindow.BuildInfo; }); @@ -105,6 +106,7 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => { ); utilsStubs._performRedirect.returns(Promise.resolve({})); utilsStubs._waitForAppResume.returns(Promise.resolve()); + utilsStubs._validateOrigin.returns(Promise.resolve()); eventsStubs._generateNewEvent!.returns(event); const redirectPromise = resolver._openRedirect( diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts index d08049a77b5..7c49a651f1d 100644 --- a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/popup_redirect.ts @@ -30,6 +30,7 @@ import { _checkCordovaConfiguration, _generateHandlerUrl, _performRedirect, + _validateOrigin, _waitForAppResume } from './utils'; import { @@ -54,6 +55,7 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal { readonly _redirectPersistence = browserSessionPersistence; readonly _shouldInitProactively = true; // This is lightweight for Cordova private readonly eventManagers = new Map(); + private readonly originValidationPromises: Record> = {}; _completeRedirectFn = _getRedirectResult; @@ -88,6 +90,8 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal { manager.resetRedirect(); _clearRedirectOutcomes(); + await this._originValidation(auth); + const event = _generateNewEvent(auth, authType, eventId); await _savePartialEvent(auth, event); const url = await _generateHandlerUrl(auth, event, provider); @@ -102,8 +106,13 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal { throw new Error('Method not implemented.'); } - _originValidation(): Promise { - return Promise.resolve(); + _originValidation(auth: AuthInternal): Promise { + const key = auth._key(); + if (!this.originValidationPromises[key]) { + this.originValidationPromises[key] = _validateOrigin(auth); + } + + return this.originValidationPromises[key]; } private attachCallbackListeners( @@ -176,7 +185,8 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal { * * @public */ -export const cordovaPopupRedirectResolver: PopupRedirectResolver = CordovaPopupRedirectResolver; +export const cordovaPopupRedirectResolver: PopupRedirectResolver = + CordovaPopupRedirectResolver; function generateNoEvent(): AuthEvent { return { diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts index 0a0859b8f94..53430477960 100644 --- a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.test.ts @@ -25,6 +25,7 @@ import { _checkCordovaConfiguration, _generateHandlerUrl, _performRedirect, + _validateOrigin, _waitForAppResume } from './utils'; import { AuthEvent, AuthEventType } from '../../model/popup_redirect'; @@ -37,6 +38,7 @@ import { } from '../../../test/helpers/timeout_stub'; import { FirebaseError } from '@firebase/util'; import { InAppBrowserRef, _cordovaWindow } from '../plugins'; +import * as projectConfig from '../../api/project_config/get_project_config'; const ANDROID_UA = 'UserAgent/5.0 (Linux; Android 0.0.0)'; const IOS_UA = 'UserAgent/5.0 (iPhone; CPU iPhone 0.0.0)'; @@ -185,6 +187,33 @@ describe('platform_cordova/popup_redirect/utils', () => { }); }); + describe('_validateOrigin', () => { + beforeEach(() => { + sinon.stub(win.BuildInfo, 'packageName').value('com.example.myapp'); + sinon + .stub(projectConfig, '_getProjectConfig') + .returns( + Promise.resolve({ /* does not matter here */ authorizedDomains: [] }) + ); + }); + + it('sets the correct fields for android', async () => { + setUA(ANDROID_UA); + await _validateOrigin(auth); + expect(projectConfig._getProjectConfig).to.have.been.calledWith(auth, { + androidPackageName: 'com.example.myapp' + }); + }); + + it('sets the correct fields for ios', async () => { + setUA(IOS_UA); + await _validateOrigin(auth); + expect(projectConfig._getProjectConfig).to.have.been.calledWith(auth, { + iosBundleId: 'com.example.myapp' + }); + }); + }); + describe('_performRedirect', () => { let isBrowsertabAvailable: boolean; beforeEach(() => { diff --git a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts index 25cf9488e96..ad3b55054e1 100644 --- a/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts +++ b/packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts @@ -28,6 +28,10 @@ import { _getRedirectUrl } from '../../core/util/handler'; import { AuthInternal } from '../../model/auth'; import { AuthEvent } from '../../model/popup_redirect'; import { InAppBrowserRef, _cordovaWindow } from '../plugins'; +import { + GetProjectConfigRequest, + _getProjectConfig +} from '../../api/project_config/get_project_config'; /** * How long to wait after the app comes back into focus before concluding that @@ -76,6 +80,24 @@ export async function _generateHandlerUrl( ); } +/** + * Validates that this app is valid for this project configuration + */ +export async function _validateOrigin(auth: AuthInternal): Promise { + const { BuildInfo } = _cordovaWindow(); + const request: GetProjectConfigRequest = {}; + if (_isIOS()) { + request.iosBundleId = BuildInfo.packageName; + } else if (_isAndroid()) { + request.androidPackageName = BuildInfo.packageName; + } else { + _fail(auth, AuthErrorCode.OPERATION_NOT_SUPPORTED); + } + + // Will fail automatically if package name is not authorized + await _getProjectConfig(auth, request); +} + export function _performRedirect( handlerUrl: string ): Promise {