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

[Auth] Implement origin validation for fast erroring-out in Cordova popup redirect resolver #5103

Merged
merged 2 commits into from
Jul 2, 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
Original file line number Diff line number Diff line change
Expand Up @@ -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<GetProjectConfigResponse> {
return _performApiRequest<GetProjectConfigRequest, GetProjectConfigResponse>(
auth,
HttpMethod.GET,
Endpoint.GET_PROJECT_CONFIG,
{}
request
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => {

beforeEach(async () => {
auth = await testAuth();
resolver = new (cordovaPopupRedirectResolver as SingletonInstantiator<PopupRedirectResolverInternal>)();
resolver =
new (cordovaPopupRedirectResolver as SingletonInstantiator<PopupRedirectResolverInternal>)();
provider = new GoogleAuthProvider();
utilsStubs = sinon.stub(utils);
eventsStubs = {
Expand All @@ -88,7 +89,7 @@ describe('platform_cordova/popup_redirect/popup_redirect', () => {
afterEach(() => {
sinon.restore();
universalLinksCb = null;
const anyWindow = (win as unknown) as Record<string, unknown>;
const anyWindow = win as unknown as Record<string, unknown>;
delete anyWindow.universalLinks;
delete anyWindow.BuildInfo;
});
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
_checkCordovaConfiguration,
_generateHandlerUrl,
_performRedirect,
_validateOrigin,
_waitForAppResume
} from './utils';
import {
Expand All @@ -54,6 +55,7 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
readonly _redirectPersistence = browserSessionPersistence;
readonly _shouldInitProactively = true; // This is lightweight for Cordova
private readonly eventManagers = new Map<string, CordovaAuthEventManager>();
private readonly originValidationPromises: Record<string, Promise<void>> = {};

_completeRedirectFn = _getRedirectResult;

Expand Down Expand Up @@ -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);
Expand All @@ -102,8 +106,13 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
throw new Error('Method not implemented.');
}

_originValidation(): Promise<void> {
return Promise.resolve();
_originValidation(auth: AuthInternal): Promise<void> {
const key = auth._key();
if (!this.originValidationPromises[key]) {
this.originValidationPromises[key] = _validateOrigin(auth);
}

return this.originValidationPromises[key];
}

private attachCallbackListeners(
Expand Down Expand Up @@ -176,7 +185,8 @@ class CordovaPopupRedirectResolver implements PopupRedirectResolverInternal {
*
* @public
*/
export const cordovaPopupRedirectResolver: PopupRedirectResolver = CordovaPopupRedirectResolver;
export const cordovaPopupRedirectResolver: PopupRedirectResolver =
CordovaPopupRedirectResolver;

function generateNoEvent(): AuthEvent {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
_checkCordovaConfiguration,
_generateHandlerUrl,
_performRedirect,
_validateOrigin,
_waitForAppResume
} from './utils';
import { AuthEvent, AuthEventType } from '../../model/popup_redirect';
Expand All @@ -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)';
Expand Down Expand Up @@ -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(() => {
Expand Down
22 changes: 22 additions & 0 deletions packages-exp/auth-exp/src/platform_cordova/popup_redirect/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<void> {
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<InAppBrowserRef | null> {
Expand Down