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 * Auth URL to use for inline flow.
41 var INLINE_AUTH_URL = AUTH_URL_BASE + '/inline_main.html';
44 * Origin of the gaia sign in page.
47 var GAIA_ORIGIN = 'https://accounts.google.com';
50 * Supported params of auth extension. For a complete list, check out the
51 * auth extension's main.js.
52 * @type {!Array.<string>}
55 var SUPPORTED_PARAMS = [
56 'gaiaUrl', // Gaia url to use;
57 'gaiaPath', // Gaia path to use without a leading slash;
58 'hl', // Language code for the user interface;
59 'email', // Pre-fill the email field in Gaia UI;
60 'service', // Name of Gaia service;
61 'continueUrl', // Continue url to use;
62 'partitionId', // Partition ID for the embedded Gaia webview;
63 'frameUrl', // Initial frame URL to use. If empty defaults to gaiaUrl.
64 'constrained' // Whether the extension is loaded in a constrained window;
68 * Supported localized strings. For a complete list, check out the auth
69 * extension's offline.js
70 * @type {!Array.<string>}
73 var LOCALIZED_STRING_PARAMS = [
78 'stringEmptyPassword',
83 * Enum for the authorization mode, must match AuthMode defined in
84 * chrome/browser/ui/webui/inline_login_ui.cc.
94 * Enum for the auth flow.
103 * Creates a new gaia auth extension host.
104 * @param {HTMLIFrameElement|string} container The iframe element or its id
105 * to host the auth extension.
107 * @extends {cr.EventTarget}
109 function GaiaAuthHost(container) {
110 this.frame_ = typeof container == 'string' ? $(container) : container;
112 window.addEventListener('message',
113 this.onMessage_.bind(this), false);
114 window.addEventListener('popstate',
115 this.onPopState_.bind(this), false);
118 GaiaAuthHost.prototype = {
119 __proto__: cr.EventTarget.prototype,
122 * An url to use with {@code reload}.
129 * The domain name of the current auth page.
135 * Invoked when authentication is completed successfully with credential
136 * data. A credential data object looks like this:
140 * email: 'xx@gmail.com',
141 * password: 'xxxx', // May not present
142 * authCode: 'x/xx', // May not present
143 * authMode: 'x', // Authorization mode, default/inline/offline.
147 * @type {function(Object)}
150 successCallback_: null,
153 * Invoked when GAIA indicates login success and SAML was used. At this
154 * point, GAIA cookies are present but the identity of the authenticated
155 * user is not known. The embedder of GaiaAuthHost should extract the GAIA
156 * cookies from the cookie jar, query GAIA for the authenticated user's
157 * e-mail address and invoke GaiaAuthHost.setAuthenticatedUserEmail with the
158 * result. The argument is an opaque token that should be passed back to
159 * GaiaAuthHost.setAuthenticatedUserEmail.
160 * @type {function(number)}
162 retrieveAuthenticatedUserEmailCallback_: null,
165 * Invoked when the auth flow needs a user to confirm his/her passwords.
166 * This could happen when there are more than one passwords scraped during
167 * SAML flow. The embedder of GaiaAuthHost should show an UI to collect a
168 * password from user then call GaiaAuthHost.verifyConfirmedPassword to
169 * verify. If the password is good, the auth flow continues with success
170 * path. Otherwise, confirmPasswordCallback_ is invoked again.
173 confirmPasswordCallback_: null,
176 * Similar to confirmPasswordCallback_ but is used when there is no
177 * password scraped after a success authentication. The authenticated user
178 * account is passed to the callback. The embedder should take over the
179 * flow and decide what to do next.
180 * @type {function(string)}
182 noPasswordCallback_: null,
185 * The iframe container.
186 * @type {HTMLIFrameElement}
193 * Sets retrieveAuthenticatedUserEmailCallback_.
196 set retrieveAuthenticatedUserEmailCallback(callback) {
197 this.retrieveAuthenticatedUserEmailCallback_ = callback;
201 * Sets confirmPasswordCallback_.
204 set confirmPasswordCallback(callback) {
205 this.confirmPasswordCallback_ = callback;
209 * Sets noPasswordCallback_.
212 set noPasswordCallback(callback) {
213 this.noPasswordCallback_ = callback;
217 * Loads the auth extension.
218 * @param {AuthMode} authMode Authorization mode.
219 * @param {Object} data Parameters for the auth extension. See the auth
220 * extension's main.js for all supported params and their defaults.
221 * @param {function(Object)} successCallback A function to be called when
222 * the authentication is completed successfully. The callback is
223 * invoked with a credential object.
225 load: function(authMode, data, successCallback) {
228 var populateParams = function(nameList, values) {
232 for (var i in nameList) {
233 var name = nameList[i];
235 params.push(name + '=' + encodeURIComponent(values[name]));
239 populateParams(SUPPORTED_PARAMS, data);
240 populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings);
241 params.push('parentPage=' + encodeURIComponent(window.location.origin));
245 case AuthMode.OFFLINE:
246 url = OFFLINE_AUTH_URL;
248 case AuthMode.INLINE:
249 url = INLINE_AUTH_URL;
250 params.push('inlineMode=1');
255 url += '?' + params.join('&');
257 this.frame_.src = url;
258 this.reloadUrl_ = url;
259 this.successCallback_ = successCallback;
260 this.authFlow = AuthFlow.GAIA;
264 * Reloads the auth extension.
267 this.frame_.src = this.reloadUrl_;
268 this.authFlow = AuthFlow.GAIA;
272 * Verifies the supplied password by sending it to the auth extension,
273 * which will then check if it matches the scraped passwords.
274 * @param {string} password The confirmed password that needs verification.
276 verifyConfirmedPassword: function(password) {
278 method: 'verifyConfirmedPassword',
281 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
285 * Sends the authenticated user's e-mail address to the auth extension.
286 * @param {number} attemptToken The opaque token provided to the
287 * retrieveAuthenticatedUserEmailCallback_.
288 * @param {string} email The authenticated user's e-mail address.
290 setAuthenticatedUserEmail: function(attemptToken, email) {
292 method: 'setAuthenticatedUserEmail',
293 attemptToken: attemptToken,
296 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
300 * Invoked to process authentication success.
301 * @param {Object} credentials Credential object to pass to success
305 onAuthSuccess_: function(credentials) {
306 if (this.successCallback_)
307 this.successCallback_(credentials);
308 cr.dispatchSimpleEvent(this, 'completed');
312 * Checks if message comes from the loaded authentication extension.
313 * @param {Object} e Payload of the received HTML5 message.
316 isAuthExtMessage_: function(e) {
317 return this.frame_.src &&
318 this.frame_.src.indexOf(e.origin) == 0 &&
319 e.source == this.frame_.contentWindow;
323 * Event handler that is invoked when HTML5 message is received.
324 * @param {object} e Payload of the received HTML5 message.
326 onMessage_: function(e) {
329 // In the inline sign in flow, the embedded gaia webview posts credential
330 // directly to the inline sign in page, because its parent JavaScript
331 // reference points to the top frame of the embedder instead of the sub
332 // frame of the gaia auth extension.
333 if (e.origin == GAIA_ORIGIN && msg.method == 'attemptLogin') {
334 this.email_ = msg.email;
335 this.password_ = msg.password;
336 this.chooseWhatToSync_ = msg.chooseWhatToSync;
340 if (!this.isAuthExtMessage_(e))
343 if (msg.method == 'loginUILoaded') {
344 cr.dispatchSimpleEvent(this, 'ready');
348 if (/^complete(Login|Authentication)$|^offlineLogin$/.test(msg.method)) {
349 if (!msg.email && !this.email_ && !msg.skipForNow) {
350 var msg = {method: 'redirectToSignin'};
351 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
354 this.onAuthSuccess_({email: msg.email || this.email_,
355 password: msg.password || this.password_,
356 authCode: msg.authCode,
357 useOffline: msg.method == 'offlineLogin',
358 chooseWhatToSync: this.chooseWhatToSync_,
359 skipForNow: msg.skipForNow || false });
363 if (msg.method == 'retrieveAuthenticatedUserEmail') {
364 if (this.retrieveAuthenticatedUserEmailCallback_) {
365 this.retrieveAuthenticatedUserEmailCallback_(msg.attemptToken);
368 'GaiaAuthHost: Invalid retrieveAuthenticatedUserEmailCallback_.');
373 if (msg.method == 'confirmPassword') {
374 if (this.confirmPasswordCallback_)
375 this.confirmPasswordCallback_();
377 console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.');
381 if (msg.method == 'noPassword') {
382 if (this.noPasswordCallback_)
383 this.noPasswordCallback_(msg.email);
385 console.error('GaiaAuthHost: Invalid noPasswordCallback_.');
389 if (msg.method == 'authPageLoaded') {
390 this.authDomain = msg.domain;
391 this.authFlow = msg.isSAML ? AuthFlow.SAML : AuthFlow.GAIA;
395 if (msg.method == 'reportState') {
396 var newUrl = setQueryParam(location, 'frameUrl', msg.src);
398 if (history.state.src != msg.src) {
399 history.pushState({src: msg.src}, '', newUrl);
402 history.replaceState({src: msg.src});
407 if (msg.method == 'switchToFullTab') {
408 chrome.send('switchToFullTab', [msg.url]);
412 console.error('Unknown message method=' + msg.method);
416 * Event handler that is invoked when the history state is changed.
417 * @param {object} e The popstate event being triggered.
419 onPopState_: function(e) {
426 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
432 * The current auth flow of the hosted gaia_auth extension.
435 cr.defineProperty(GaiaAuthHost, 'authFlow');
437 GaiaAuthHost.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
438 GaiaAuthHost.LOCALIZED_STRING_PARAMS = LOCALIZED_STRING_PARAMS;
439 GaiaAuthHost.AuthMode = AuthMode;
440 GaiaAuthHost.AuthFlow = AuthFlow;
443 GaiaAuthHost: GaiaAuthHost