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 * Creates a new gaia auth extension host.
95 * @param {HTMLIFrameElement|string} container The iframe element or its id
96 * to host the auth extension.
98 * @extends {cr.EventTarget}
100 function GaiaAuthHost(container) {
101 this.frame_ = typeof container == 'string' ? $(container) : container;
103 window.addEventListener('message',
104 this.onMessage_.bind(this), false);
105 window.addEventListener('popstate',
106 this.onPopState_.bind(this), false);
109 GaiaAuthHost.prototype = {
110 __proto__: cr.EventTarget.prototype,
113 * An url to use with {@code reload}.
120 * Invoked when authentication is completed successfully with credential
121 * data. A credential data object looks like this:
125 * email: 'xx@gmail.com',
126 * password: 'xxxx', // May not present
127 * authCode: 'x/xx', // May not present
128 * authMode: 'x', // Authorization mode, default/inline/offline.
132 * @type {function(Object)}
135 successCallback_: null,
138 * Invoked when the auth flow needs a user to confirm his/her passwords.
139 * This could happen when there are more than one passwords scraped during
140 * SAML flow. The embedder of GaiaAuthHost should show an UI to collect a
141 * password from user then call GaiaAuthHost.verifyConfirmedPassword to
142 * verify. If the password is good, the auth flow continues with success
143 * path. Otherwise, confirmPasswordCallback_ is invoked again.
146 confirmPasswordCallback_: null,
149 * Similar to confirmPasswordCallback_ but is used when there is no
150 * password scraped after a success authentication. The authenticated user
151 * account is passed to the callback. The embedder should take over the
152 * flow and decide what to do next.
153 * @type {function(string)}
155 noPasswordCallback_: null,
158 * Invoked when the auth page hosted inside the extension is loaded.
159 * Param {@code saml} is true when the auth page is a SAML page (out of
161 * @type {function{boolean)}
163 authPageLoadedCallback_: null,
166 * The iframe container.
167 * @type {HTMLIFrameElement}
174 * Sets confirmPasswordCallback_.
177 set confirmPasswordCallback(callback) {
178 this.confirmPasswordCallback_ = callback;
182 * Sets noPasswordCallback_.
185 set noPasswordCallback(callback) {
186 this.noPasswordCallback_ = callback;
190 * Sets authPageLoadedCallback_.
191 * @type {function(boolean)}
193 set authPageLoadedCallback(callback) {
194 this.authPageLoadedCallback_ = callback;
198 * Loads the auth extension.
199 * @param {AuthMode} authMode Authorization mode.
200 * @param {Object} data Parameters for the auth extension. See the auth
201 * extension's main.js for all supported params and their defaults.
202 * @param {function(Object)} successCallback A function to be called when
203 * the authentication is completed successfully. The callback is
204 * invoked with a credential object.
206 load: function(authMode, data, successCallback) {
209 var populateParams = function(nameList, values) {
213 for (var i in nameList) {
214 var name = nameList[i];
216 params.push(name + '=' + encodeURIComponent(values[name]));
220 populateParams(SUPPORTED_PARAMS, data);
221 populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings);
222 params.push('parentPage=' + encodeURIComponent(window.location.origin));
226 case AuthMode.OFFLINE:
227 url = OFFLINE_AUTH_URL;
229 case AuthMode.INLINE:
230 url = INLINE_AUTH_URL;
231 params.push('inlineMode=1');
236 url += '?' + params.join('&');
238 this.frame_.src = url;
239 this.reloadUrl_ = url;
240 this.successCallback_ = successCallback;
244 * Reloads the auth extension.
247 this.frame_.src = this.reloadUrl_;
251 * Verifies the supplied password by sending it to the auth extension,
252 * which will then check if it matches the scraped passwords.
253 * @param {string} password The confirmed password that needs verification.
255 verifyConfirmedPassword: function(password) {
257 method: 'verifyConfirmedPassword',
260 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
264 * Invoked to process authentication success.
265 * @param {Object} credentials Credential object to pass to success
269 onAuthSuccess_: function(credentials) {
270 if (this.successCallback_)
271 this.successCallback_(credentials);
272 cr.dispatchSimpleEvent(this, 'completed');
276 * Checks if message comes from the loaded authentication extension.
277 * @param {Object} e Payload of the received HTML5 message.
280 isAuthExtMessage_: function(e) {
281 return this.frame_.src &&
282 this.frame_.src.indexOf(e.origin) == 0 &&
283 e.source == this.frame_.contentWindow;
287 * Event handler that is invoked when HTML5 message is received.
288 * @param {object} e Payload of the received HTML5 message.
290 onMessage_: function(e) {
293 // In the inline sign in flow, the embedded gaia webview posts credential
294 // directly to the inline sign in page, because its parent JavaScript
295 // reference points to the top frame of the embedder instead of the sub
296 // frame of the gaia auth extension.
297 if (e.origin == GAIA_ORIGIN && msg.method == 'attemptLogin') {
298 this.email_ = msg.email;
299 this.password_ = msg.password;
300 this.chooseWhatToSync_ = msg.chooseWhatToSync;
304 if (!this.isAuthExtMessage_(e))
307 if (msg.method == 'loginUILoaded') {
308 cr.dispatchSimpleEvent(this, 'ready');
312 if (/^complete(Login|Authentication)$|^offlineLogin$/.test(msg.method)) {
313 this.onAuthSuccess_({email: msg.email || this.email_,
314 password: msg.password || this.password_,
315 authCode: msg.authCode,
316 useOffline: msg.method == 'offlineLogin',
317 chooseWhatToSync: this.chooseWhatToSync_});
321 if (msg.method == 'confirmPassword') {
322 if (this.confirmPasswordCallback_)
323 this.confirmPasswordCallback_();
325 console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.');
329 if (msg.method == 'noPassword') {
330 if (this.noPasswordCallback_)
331 this.noPasswordCallback_(msg.email);
333 console.error('GaiaAuthHost: Invalid noPasswordCallback_.');
337 if (msg.method == 'authPageLoaded') {
338 if (this.authPageLoadedCallback_)
339 this.authPageLoadedCallback_(msg.isSAML);
343 if (msg.method == 'reportState') {
344 var newUrl = setQueryParam(location, 'frameUrl', msg.src);
346 if (history.state.src != msg.src) {
347 history.pushState({src: msg.src}, '', newUrl);
350 history.replaceState({src: msg.src}, '', newUrl);
355 if (msg.method == 'switchToFullTab') {
356 chrome.send('switchToFullTab', [msg.url]);
360 console.error('Unknown message method=' + msg.method);
364 * Event handler that is invoked when the history state is changed.
365 * @param {object} e The popstate event being triggered.
367 onPopState_: function(e) {
374 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
379 GaiaAuthHost.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
380 GaiaAuthHost.LOCALIZED_STRING_PARAMS = LOCALIZED_STRING_PARAMS;
381 GaiaAuthHost.AuthMode = AuthMode;
384 GaiaAuthHost: GaiaAuthHost