1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview An UI component to host gaia auth extension in an iframe.
7 * After the component binds with an iframe, call its {@code load} to start the
8 * authentication flow. There are two events would be raised after this point:
9 * a 'ready' event when the authentication UI is ready to use and a 'completed'
10 * event when the authentication is completed successfully. If caller is
11 * interested in the user credentials, he may supply a success callback with
12 * {@code load} call. The callback will be invoked when the authentication is
13 * completed successfully and with the available credential data.
16 cr.define('cr.login', function() {
20 * Base URL of gaia auth extension.
23 var AUTH_URL_BASE = 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik';
26 * Auth URL to use for online flow.
29 var AUTH_URL = AUTH_URL_BASE + '/main.html';
32 * Auth URL to use for offline flow.
35 var OFFLINE_AUTH_URL = AUTH_URL_BASE + '/offline.html';
38 * Origin of the gaia sign in page.
41 var GAIA_ORIGIN = 'https://accounts.google.com';
44 * Supported params of auth extension. For a complete list, check out the
45 * auth extension's main.js.
46 * @type {!Array<string>}
49 var SUPPORTED_PARAMS = [
50 'gaiaUrl', // Gaia url to use;
51 'gaiaPath', // Gaia path to use without a leading slash;
52 'hl', // Language code for the user interface;
53 'email', // Pre-fill the email field in Gaia UI;
54 'service', // Name of Gaia service;
55 'continueUrl', // Continue url to use;
56 'frameUrl', // Initial frame URL to use. If empty defaults to gaiaUrl.
57 'useEafe', // Whether to use EAFE.
58 'clientId', // Chrome's client id.
59 'constrained' // Whether the extension is loaded in a constrained window;
63 * Supported localized strings. For a complete list, check out the auth
64 * extension's offline.js
65 * @type {!Array<string>}
68 var LOCALIZED_STRING_PARAMS = [
73 'stringEmptyPassword',
78 * Enum for the authorization mode, must match AuthMode defined in
79 * chrome/browser/ui/webui/inline_login_ui.cc.
89 * Enum for the auth flow.
98 * Creates a new gaia auth extension host.
99 * @param {HTMLIFrameElement|string} container The iframe element or its id
100 * to host the auth extension.
102 * @extends {cr.EventTarget}
104 function GaiaAuthHost(container) {
105 this.frame_ = typeof container == 'string' ? $(container) : container;
107 window.addEventListener('message',
108 this.onMessage_.bind(this), false);
111 GaiaAuthHost.prototype = {
112 __proto__: cr.EventTarget.prototype,
115 * Auth extension params
121 * An url to use with {@code reload}.
128 * Invoked when authentication is completed successfully with credential
129 * data. A credential data object looks like this:
133 * email: 'xx@gmail.com',
134 * password: 'xxxx', // May not present
135 * authCode: 'x/xx', // May not present
136 * authMode: 'x', // Authorization mode, default/offline/desktop.
140 * @type {function(Object)}
143 successCallback_: null,
146 * Invoked when the auth flow needs a user to confirm his/her passwords.
147 * This could happen when there are more than one passwords scraped during
148 * SAML flow. The embedder of GaiaAuthHost should show an UI to collect a
149 * password from user then call GaiaAuthHost.verifyConfirmedPassword to
150 * verify. If the password is good, the auth flow continues with success
151 * path. Otherwise, confirmPasswordCallback_ is invoked again.
154 confirmPasswordCallback_: null,
157 * Similar to confirmPasswordCallback_ but is used when there is no
158 * password scraped after a success authentication. The authenticated user
159 * account is passed to the callback. The embedder should take over the
160 * flow and decide what to do next.
161 * @type {function(string)}
163 noPasswordCallback_: null,
166 * Invoked when the authentication flow had to be aborted because content
167 * served over an unencrypted connection was detected.
169 insecureContentBlockedCallback_: null,
172 * Invoked to display an error message to the user when a GAIA error occurs
173 * during authentication.
176 missingGaiaInfoCallback_: null,
179 * Invoked to record that the credentials passing API was used.
182 samlApiUsedCallback_: null,
185 * The iframe container.
186 * @type {HTMLIFrameElement}
193 * Sets confirmPasswordCallback_.
196 set confirmPasswordCallback(callback) {
197 this.confirmPasswordCallback_ = callback;
201 * Sets noPasswordCallback_.
204 set noPasswordCallback(callback) {
205 this.noPasswordCallback_ = callback;
209 * Sets insecureContentBlockedCallback_.
210 * @type {function(string)}
212 set insecureContentBlockedCallback(callback) {
213 this.insecureContentBlockedCallback_ = callback;
217 * Sets missingGaiaInfoCallback_.
220 set missingGaiaInfoCallback(callback) {
221 this.missingGaiaInfoCallback_ = callback;
225 * Sets samlApiUsedCallback_.
228 set samlApiUsedCallback(callback) {
229 this.samlApiUsedCallback_ = callback;
233 * Loads the auth extension.
234 * @param {AuthMode} authMode Authorization mode.
235 * @param {Object} data Parameters for the auth extension. See the auth
236 * extension's main.js for all supported params and their defaults.
237 * @param {function(Object)} successCallback A function to be called when
238 * the authentication is completed successfully. The callback is
239 * invoked with a credential object.
241 load: function(authMode, data, successCallback) {
244 var populateParams = function(nameList, values) {
248 for (var i in nameList) {
249 var name = nameList[i];
251 params[name] = values[name];
255 populateParams(SUPPORTED_PARAMS, data);
256 populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings);
257 params['needPassword'] = true;
261 case AuthMode.OFFLINE:
262 url = OFFLINE_AUTH_URL;
264 case AuthMode.DESKTOP:
266 params['desktopMode'] = true;
272 this.authParams_ = params;
273 this.reloadUrl_ = url;
274 this.successCallback_ = successCallback;
280 * Reloads the auth extension.
283 this.frame_.src = this.reloadUrl_;
284 this.authFlow = AuthFlow.GAIA;
288 * Verifies the supplied password by sending it to the auth extension,
289 * which will then check if it matches the scraped passwords.
290 * @param {string} password The confirmed password that needs verification.
292 verifyConfirmedPassword: function(password) {
294 method: 'verifyConfirmedPassword',
297 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
301 * Invoked to process authentication success.
302 * @param {Object} credentials Credential object to pass to success
306 onAuthSuccess_: function(credentials) {
307 if (this.successCallback_)
308 this.successCallback_(credentials);
309 cr.dispatchSimpleEvent(this, 'completed');
313 * Checks if message comes from the loaded authentication extension.
314 * @param {Object} e Payload of the received HTML5 message.
317 isAuthExtMessage_: function(e) {
318 return this.frame_.src &&
319 this.frame_.src.indexOf(e.origin) == 0 &&
320 e.source == this.frame_.contentWindow;
324 * Event handler that is invoked when HTML5 message is received.
325 * @param {object} e Payload of the received HTML5 message.
327 onMessage_: function(e) {
330 if (!this.isAuthExtMessage_(e))
333 if (msg.method == 'loginUIDOMContentLoaded') {
334 this.frame_.contentWindow.postMessage(this.authParams_, AUTH_URL_BASE);
338 if (msg.method == 'loginUILoaded') {
339 cr.dispatchSimpleEvent(this, 'ready');
343 if (/^complete(Login|Authentication)$|^offlineLogin$/.test(msg.method)) {
344 if (!msg.email && !this.email_ && !msg.skipForNow) {
345 var msg = {method: 'redirectToSignin'};
346 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
349 this.onAuthSuccess_({email: msg.email,
350 password: msg.password,
352 useOffline: msg.method == 'offlineLogin',
353 usingSAML: msg.usingSAML || false,
354 chooseWhatToSync: msg.chooseWhatToSync,
355 skipForNow: msg.skipForNow || false,
356 sessionIndex: msg.sessionIndex || ''});
360 if (msg.method == 'completeAuthenticationAuthCodeOnly') {
363 'GaiaAuthHost: completeAuthentication without auth code.');
364 var msg = {method: 'redirectToSignin'};
365 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
368 this.onAuthSuccess_({authCodeOnly: true, authCode: msg.authCode});
372 if (msg.method == 'confirmPassword') {
373 if (this.confirmPasswordCallback_)
374 this.confirmPasswordCallback_(msg.email, msg.passwordCount);
376 console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.');
380 if (msg.method == 'noPassword') {
381 if (this.noPasswordCallback_)
382 this.noPasswordCallback_(msg.email);
384 console.error('GaiaAuthHost: Invalid noPasswordCallback_.');
388 if (msg.method == 'authPageLoaded') {
389 this.authDomain = msg.domain;
390 this.authFlow = msg.isSAML ? AuthFlow.SAML : AuthFlow.GAIA;
394 if (msg.method == 'resetAuthFlow') {
395 this.authFlow = AuthFlow.GAIA;
399 if (msg.method == 'insecureContentBlocked') {
400 if (this.insecureContentBlockedCallback_) {
401 this.insecureContentBlockedCallback_(msg.url);
404 'GaiaAuthHost: Invalid insecureContentBlockedCallback_.');
409 if (msg.method == 'switchToFullTab') {
410 chrome.send('switchToFullTab', [msg.url]);
414 if (msg.method == 'missingGaiaInfo') {
415 if (this.missingGaiaInfoCallback_) {
416 this.missingGaiaInfoCallback_();
418 console.error('GaiaAuthHost: Invalid missingGaiaInfoCallback_.');
423 if (msg.method == 'samlApiUsed') {
424 if (this.samlApiUsedCallback_) {
425 this.samlApiUsedCallback_();
427 console.error('GaiaAuthHost: Invalid samlApiUsedCallback_.');
432 console.error('Unknown message method=' + msg.method);
437 * The domain name of the current auth page.
440 cr.defineProperty(GaiaAuthHost, 'authDomain');
443 * The current auth flow of the hosted gaia_auth extension.
446 cr.defineProperty(GaiaAuthHost, 'authFlow');
448 GaiaAuthHost.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
449 GaiaAuthHost.LOCALIZED_STRING_PARAMS = LOCALIZED_STRING_PARAMS;
450 GaiaAuthHost.AuthMode = AuthMode;
451 GaiaAuthHost.AuthFlow = AuthFlow;
454 GaiaAuthHost: GaiaAuthHost