| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview An UI component to host gaia auth extension in an iframe. |
| * After the component binds with an iframe, call its {@code load} to start the |
| * authentication flow. There are two events would be raised after this point: |
| * a 'ready' event when the authentication UI is ready to use and a 'completed' |
| * event when the authentication is completed successfully. If caller is |
| * interested in the user credentials, he may supply a success callback with |
| * {@code load} call. The callback will be invoked when the authentication is |
| * completed successfully and with the available credential data. |
| */ |
| |
| cr.define('cr.login', function() { |
| 'use strict'; |
| |
| /** |
| * Base URL of gaia auth extension. |
| * @const |
| */ |
| var AUTH_URL_BASE = 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik'; |
| |
| /** |
| * Auth URL to use for online flow. |
| * @const |
| */ |
| var AUTH_URL = AUTH_URL_BASE + '/main.html'; |
| |
| /** |
| * Auth URL to use for offline flow. |
| * @const |
| */ |
| var OFFLINE_AUTH_URL = AUTH_URL_BASE + '/offline.html'; |
| |
| /** |
| * Origin of the gaia sign in page. |
| * @const |
| */ |
| var GAIA_ORIGIN = 'https://accounts.google.com'; |
| |
| /** |
| * Supported params of auth extension. For a complete list, check out the |
| * auth extension's main.js. |
| * @type {!Array.<string>} |
| * @const |
| */ |
| var SUPPORTED_PARAMS = [ |
| 'gaiaUrl', // Gaia url to use; |
| 'gaiaPath', // Gaia path to use without a leading slash; |
| 'hl', // Language code for the user interface; |
| 'email', // Pre-fill the email field in Gaia UI; |
| 'service', // Name of Gaia service; |
| 'continueUrl', // Continue url to use; |
| 'frameUrl', // Initial frame URL to use. If empty defaults to gaiaUrl. |
| 'constrained' // Whether the extension is loaded in a constrained window; |
| ]; |
| |
| /** |
| * Supported localized strings. For a complete list, check out the auth |
| * extension's offline.js |
| * @type {!Array.<string>} |
| * @const |
| */ |
| var LOCALIZED_STRING_PARAMS = [ |
| 'stringSignIn', |
| 'stringEmail', |
| 'stringPassword', |
| 'stringEmptyEmail', |
| 'stringEmptyPassword', |
| 'stringError' |
| ]; |
| |
| /** |
| * Enum for the authorization mode, must match AuthMode defined in |
| * chrome/browser/ui/webui/inline_login_ui.cc. |
| * @enum {number} |
| */ |
| var AuthMode = { |
| DEFAULT: 0, |
| OFFLINE: 1, |
| DESKTOP: 2 |
| }; |
| |
| /** |
| * Enum for the auth flow. |
| * @enum {number} |
| */ |
| var AuthFlow = { |
| GAIA: 0, |
| SAML: 1 |
| }; |
| |
| /** |
| * Creates a new gaia auth extension host. |
| * @param {HTMLIFrameElement|string} container The iframe element or its id |
| * to host the auth extension. |
| * @constructor |
| * @extends {cr.EventTarget} |
| */ |
| function GaiaAuthHost(container) { |
| this.frame_ = typeof container == 'string' ? $(container) : container; |
| assert(this.frame_); |
| window.addEventListener('message', |
| this.onMessage_.bind(this), false); |
| } |
| |
| GaiaAuthHost.prototype = { |
| __proto__: cr.EventTarget.prototype, |
| |
| /** |
| * An url to use with {@code reload}. |
| * @type {?string} |
| * @private |
| */ |
| reloadUrl_: null, |
| |
| /** |
| * The domain name of the current auth page. |
| * @type {string} |
| */ |
| authDomain: '', |
| |
| /** |
| * Invoked when authentication is completed successfully with credential |
| * data. A credential data object looks like this: |
| * <pre> |
| * {@code |
| * { |
| * email: 'xx@gmail.com', |
| * password: 'xxxx', // May not present |
| * authCode: 'x/xx', // May not present |
| * authMode: 'x', // Authorization mode, default/offline/desktop. |
| * } |
| * } |
| * </pre> |
| * @type {function(Object)} |
| * @private |
| */ |
| successCallback_: null, |
| |
| /** |
| * Invoked when the auth flow needs a user to confirm his/her passwords. |
| * This could happen when there are more than one passwords scraped during |
| * SAML flow. The embedder of GaiaAuthHost should show an UI to collect a |
| * password from user then call GaiaAuthHost.verifyConfirmedPassword to |
| * verify. If the password is good, the auth flow continues with success |
| * path. Otherwise, confirmPasswordCallback_ is invoked again. |
| * @type {function()} |
| */ |
| confirmPasswordCallback_: null, |
| |
| /** |
| * Similar to confirmPasswordCallback_ but is used when there is no |
| * password scraped after a success authentication. The authenticated user |
| * account is passed to the callback. The embedder should take over the |
| * flow and decide what to do next. |
| * @type {function(string)} |
| */ |
| noPasswordCallback_: null, |
| |
| /** |
| * Invoked when the authentication flow had to be aborted because content |
| * served over an unencrypted connection was detected. |
| */ |
| insecureContentBlockedCallback_: null, |
| |
| /** |
| * Invoked to display an error message to the user when a GAIA error occurs |
| * during authentication. |
| * @type {function()} |
| */ |
| missingGaiaInfoCallback_: null, |
| |
| /** |
| * Invoked to record that the credentials passing API was used. |
| * @type {function()} |
| */ |
| samlApiUsedCallback_: null, |
| |
| /** |
| * The iframe container. |
| * @type {HTMLIFrameElement} |
| */ |
| get frame() { |
| return this.frame_; |
| }, |
| |
| /** |
| * Sets confirmPasswordCallback_. |
| * @type {function()} |
| */ |
| set confirmPasswordCallback(callback) { |
| this.confirmPasswordCallback_ = callback; |
| }, |
| |
| /** |
| * Sets noPasswordCallback_. |
| * @type {function()} |
| */ |
| set noPasswordCallback(callback) { |
| this.noPasswordCallback_ = callback; |
| }, |
| |
| /** |
| * Sets insecureContentBlockedCallback_. |
| * @type {function(string)} |
| */ |
| set insecureContentBlockedCallback(callback) { |
| this.insecureContentBlockedCallback_ = callback; |
| }, |
| |
| /** |
| * Sets missingGaiaInfoCallback_. |
| * @type {function()} |
| */ |
| set missingGaiaInfoCallback(callback) { |
| this.missingGaiaInfoCallback_ = callback; |
| }, |
| |
| /** |
| * Sets samlApiUsedCallback_. |
| * @type {function()} |
| */ |
| set samlApiUsedCallback(callback) { |
| this.samlApiUsedCallback_ = callback; |
| }, |
| |
| /** |
| * Loads the auth extension. |
| * @param {AuthMode} authMode Authorization mode. |
| * @param {Object} data Parameters for the auth extension. See the auth |
| * extension's main.js for all supported params and their defaults. |
| * @param {function(Object)} successCallback A function to be called when |
| * the authentication is completed successfully. The callback is |
| * invoked with a credential object. |
| */ |
| load: function(authMode, data, successCallback) { |
| var params = []; |
| |
| var populateParams = function(nameList, values) { |
| if (!values) |
| return; |
| |
| for (var i in nameList) { |
| var name = nameList[i]; |
| if (values[name]) |
| params.push(name + '=' + encodeURIComponent(values[name])); |
| } |
| }; |
| |
| populateParams(SUPPORTED_PARAMS, data); |
| populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings); |
| params.push('parentPage=' + encodeURIComponent(window.location.origin)); |
| params.push('needPassword=1'); |
| |
| var url; |
| switch (authMode) { |
| case AuthMode.OFFLINE: |
| url = OFFLINE_AUTH_URL; |
| break; |
| case AuthMode.DESKTOP: |
| url = AUTH_URL; |
| params.push('desktopMode=1'); |
| break; |
| default: |
| url = AUTH_URL; |
| } |
| url += '?' + params.join('&'); |
| |
| this.frame_.src = url; |
| this.reloadUrl_ = url; |
| this.successCallback_ = successCallback; |
| this.authFlow = AuthFlow.GAIA; |
| }, |
| |
| /** |
| * Reloads the auth extension. |
| */ |
| reload: function() { |
| this.frame_.src = this.reloadUrl_; |
| this.authFlow = AuthFlow.GAIA; |
| }, |
| |
| /** |
| * Verifies the supplied password by sending it to the auth extension, |
| * which will then check if it matches the scraped passwords. |
| * @param {string} password The confirmed password that needs verification. |
| */ |
| verifyConfirmedPassword: function(password) { |
| var msg = { |
| method: 'verifyConfirmedPassword', |
| password: password |
| }; |
| this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE); |
| }, |
| |
| /** |
| * Invoked to process authentication success. |
| * @param {Object} credentials Credential object to pass to success |
| * callback. |
| * @private |
| */ |
| onAuthSuccess_: function(credentials) { |
| if (this.successCallback_) |
| this.successCallback_(credentials); |
| cr.dispatchSimpleEvent(this, 'completed'); |
| }, |
| |
| /** |
| * Checks if message comes from the loaded authentication extension. |
| * @param {Object} e Payload of the received HTML5 message. |
| * @type {boolean} |
| */ |
| isAuthExtMessage_: function(e) { |
| return this.frame_.src && |
| this.frame_.src.indexOf(e.origin) == 0 && |
| e.source == this.frame_.contentWindow; |
| }, |
| |
| /** |
| * Event handler that is invoked when HTML5 message is received. |
| * @param {object} e Payload of the received HTML5 message. |
| */ |
| onMessage_: function(e) { |
| var msg = e.data; |
| |
| if (!this.isAuthExtMessage_(e)) |
| return; |
| |
| if (msg.method == 'loginUILoaded') { |
| cr.dispatchSimpleEvent(this, 'ready'); |
| return; |
| } |
| |
| if (/^complete(Login|Authentication)$|^offlineLogin$/.test(msg.method)) { |
| if (!msg.email && !this.email_ && !msg.skipForNow) { |
| var msg = {method: 'redirectToSignin'}; |
| this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE); |
| return; |
| } |
| this.onAuthSuccess_({email: msg.email, |
| password: msg.password, |
| gaiaId: msg.gaiaId, |
| useOffline: msg.method == 'offlineLogin', |
| usingSAML: msg.usingSAML || false, |
| chooseWhatToSync: msg.chooseWhatToSync, |
| skipForNow: msg.skipForNow || false, |
| sessionIndex: msg.sessionIndex || ''}); |
| return; |
| } |
| |
| if (msg.method == 'confirmPassword') { |
| if (this.confirmPasswordCallback_) |
| this.confirmPasswordCallback_(msg.passwordCount); |
| else |
| console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.'); |
| return; |
| } |
| |
| if (msg.method == 'noPassword') { |
| if (this.noPasswordCallback_) |
| this.noPasswordCallback_(msg.email); |
| else |
| console.error('GaiaAuthHost: Invalid noPasswordCallback_.'); |
| return; |
| } |
| |
| if (msg.method == 'authPageLoaded') { |
| this.authDomain = msg.domain; |
| this.authFlow = msg.isSAML ? AuthFlow.SAML : AuthFlow.GAIA; |
| return; |
| } |
| |
| if (msg.method == 'insecureContentBlocked') { |
| if (this.insecureContentBlockedCallback_) { |
| this.insecureContentBlockedCallback_(msg.url); |
| } else { |
| console.error( |
| 'GaiaAuthHost: Invalid insecureContentBlockedCallback_.'); |
| } |
| return; |
| } |
| |
| if (msg.method == 'switchToFullTab') { |
| chrome.send('switchToFullTab', [msg.url]); |
| return; |
| } |
| |
| if (msg.method == 'missingGaiaInfo') { |
| if (this.missingGaiaInfoCallback_) { |
| this.missingGaiaInfoCallback_(); |
| } else { |
| console.error('GaiaAuthHost: Invalid missingGaiaInfoCallback_.'); |
| } |
| return; |
| } |
| |
| if (msg.method == 'samlApiUsed') { |
| if (this.samlApiUsedCallback_) { |
| this.samlApiUsedCallback_(); |
| } else { |
| console.error('GaiaAuthHost: Invalid samlApiUsedCallback_.'); |
| } |
| return; |
| } |
| |
| console.error('Unknown message method=' + msg.method); |
| } |
| }; |
| |
| /** |
| * The current auth flow of the hosted gaia_auth extension. |
| * @type {AuthFlow} |
| */ |
| cr.defineProperty(GaiaAuthHost, 'authFlow'); |
| |
| GaiaAuthHost.SUPPORTED_PARAMS = SUPPORTED_PARAMS; |
| GaiaAuthHost.LOCALIZED_STRING_PARAMS = LOCALIZED_STRING_PARAMS; |
| GaiaAuthHost.AuthMode = AuthMode; |
| GaiaAuthHost.AuthFlow = AuthFlow; |
| |
| return { |
| GaiaAuthHost: GaiaAuthHost |
| }; |
| }); |