Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / gaia_auth_host / gaia_auth_host.js
blobc77ea9b84eec5f6ad68f6424b7c869e2ac3c42b3
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.
5 /**
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.
14  */
16 cr.define('cr.login', function() {
17   'use strict';
19   /**
20    * Base URL of gaia auth extension.
21    * @const
22    */
23   var AUTH_URL_BASE = 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik';
25   /**
26    * Auth URL to use for online flow.
27    * @const
28    */
29   var AUTH_URL = AUTH_URL_BASE + '/main.html';
31   /**
32    * Auth URL to use for offline flow.
33    * @const
34    */
35   var OFFLINE_AUTH_URL = AUTH_URL_BASE + '/offline.html';
37   /**
38    * Auth URL to use for inline flow.
39    * @const
40    */
41   var INLINE_AUTH_URL = AUTH_URL_BASE + '/inline_main.html';
43   /**
44    * Origin of the gaia sign in page.
45    * @const
46    */
47   var GAIA_ORIGIN = 'https://accounts.google.com';
49   /**
50    * Supported params of auth extension. For a complete list, check out the
51    * auth extension's main.js.
52    * @type {!Array.<string>}
53    * @const
54    */
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;
65   ];
67   /**
68    * Supported localized strings. For a complete list, check out the auth
69    * extension's offline.js
70    * @type {!Array.<string>}
71    * @const
72    */
73   var LOCALIZED_STRING_PARAMS = [
74       'stringSignIn',
75       'stringEmail',
76       'stringPassword',
77       'stringEmptyEmail',
78       'stringEmptyPassword',
79       'stringError'
80   ];
82   /**
83    * Enum for the authorization mode, must match AuthMode defined in
84    * chrome/browser/ui/webui/inline_login_ui.cc.
85    * @enum {number}
86    */
87   var AuthMode = {
88     DEFAULT: 0,
89     OFFLINE: 1,
90     INLINE: 2
91   };
93   /**
94    * Enum for the auth flow.
95    * @enum {number}
96    */
97   var AuthFlow = {
98     GAIA: 0,
99     SAML: 1
100   };
102   /**
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.
106    * @constructor
107    * @extends {cr.EventTarget}
108    */
109   function GaiaAuthHost(container) {
110     this.frame_ = typeof container == 'string' ? $(container) : container;
111     assert(this.frame_);
112     window.addEventListener('message',
113                             this.onMessage_.bind(this), false);
114     window.addEventListener('popstate',
115                             this.onPopState_.bind(this), false);
116   }
118   GaiaAuthHost.prototype = {
119     __proto__: cr.EventTarget.prototype,
121     /**
122      * An url to use with {@code reload}.
123      * @type {?string}
124      * @private
125      */
126     reloadUrl_: null,
128     /**
129      * The domain name of the current auth page.
130      * @type {string}
131      */
132     authDomain: '',
134     /**
135      * Invoked when authentication is completed successfully with credential
136      * data. A credential data object looks like this:
137      * <pre>
138      * {@code
139      * {
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.
144      * }
145      * }
146      * </pre>
147      * @type {function(Object)}
148      * @private
149      */
150     successCallback_: null,
152     /**
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)}
161      */
162     retrieveAuthenticatedUserEmailCallback_: null,
164     /**
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.
171      * @type {function()}
172      */
173     confirmPasswordCallback_: null,
175     /**
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)}
181      */
182     noPasswordCallback_: null,
184     /**
185      * The iframe container.
186      * @type {HTMLIFrameElement}
187      */
188     get frame() {
189       return this.frame_;
190     },
192     /**
193      * Sets retrieveAuthenticatedUserEmailCallback_.
194      * @type {function()}
195      */
196     set retrieveAuthenticatedUserEmailCallback(callback) {
197       this.retrieveAuthenticatedUserEmailCallback_ = callback;
198     },
200     /**
201      * Sets confirmPasswordCallback_.
202      * @type {function()}
203      */
204     set confirmPasswordCallback(callback) {
205       this.confirmPasswordCallback_ = callback;
206     },
208     /**
209      * Sets noPasswordCallback_.
210      * @type {function()}
211      */
212     set noPasswordCallback(callback) {
213       this.noPasswordCallback_ = callback;
214     },
216     /**
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.
224      */
225     load: function(authMode, data, successCallback) {
226       var params = [];
228       var populateParams = function(nameList, values) {
229         if (!values)
230           return;
232         for (var i in nameList) {
233           var name = nameList[i];
234           if (values[name])
235             params.push(name + '=' + encodeURIComponent(values[name]));
236         }
237       };
239       populateParams(SUPPORTED_PARAMS, data);
240       populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings);
241       params.push('parentPage=' + encodeURIComponent(window.location.origin));
243       var url;
244       switch (authMode) {
245         case AuthMode.OFFLINE:
246           url = OFFLINE_AUTH_URL;
247           break;
248         case AuthMode.INLINE:
249           url = INLINE_AUTH_URL;
250           params.push('inlineMode=1');
251           break;
252         default:
253           url = AUTH_URL;
254       }
255       url += '?' + params.join('&');
257       this.frame_.src = url;
258       this.reloadUrl_ = url;
259       this.successCallback_ = successCallback;
260       this.authFlow = AuthFlow.GAIA;
261     },
263     /**
264      * Reloads the auth extension.
265      */
266     reload: function() {
267       this.frame_.src = this.reloadUrl_;
268       this.authFlow = AuthFlow.GAIA;
269     },
271     /**
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.
275      */
276     verifyConfirmedPassword: function(password) {
277       var msg = {
278         method: 'verifyConfirmedPassword',
279         password: password
280       };
281       this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
282     },
284     /**
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.
289      */
290     setAuthenticatedUserEmail: function(attemptToken, email) {
291       var msg = {
292         method: 'setAuthenticatedUserEmail',
293         attemptToken: attemptToken,
294         email: email
295       };
296       this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
297     },
299     /**
300      * Invoked to process authentication success.
301      * @param {Object} credentials Credential object to pass to success
302      *     callback.
303      * @private
304      */
305     onAuthSuccess_: function(credentials) {
306       if (this.successCallback_)
307         this.successCallback_(credentials);
308       cr.dispatchSimpleEvent(this, 'completed');
309     },
311     /**
312      * Checks if message comes from the loaded authentication extension.
313      * @param {Object} e Payload of the received HTML5 message.
314      * @type {boolean}
315      */
316     isAuthExtMessage_: function(e) {
317       return this.frame_.src &&
318           this.frame_.src.indexOf(e.origin) == 0 &&
319           e.source == this.frame_.contentWindow;
320     },
322     /**
323      * Event handler that is invoked when HTML5 message is received.
324      * @param {object} e Payload of the received HTML5 message.
325      */
326     onMessage_: function(e) {
327       var msg = e.data;
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;
337         return;
338       }
340       if (!this.isAuthExtMessage_(e))
341         return;
343       if (msg.method == 'loginUILoaded') {
344         cr.dispatchSimpleEvent(this, 'ready');
345         return;
346       }
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);
352           return;
353         }
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 });
360         return;
361       }
363       if (msg.method == 'retrieveAuthenticatedUserEmail') {
364         if (this.retrieveAuthenticatedUserEmailCallback_) {
365           this.retrieveAuthenticatedUserEmailCallback_(msg.attemptToken);
366         } else {
367           console.error(
368               'GaiaAuthHost: Invalid retrieveAuthenticatedUserEmailCallback_.');
369         }
370         return;
371       }
373       if (msg.method == 'confirmPassword') {
374         if (this.confirmPasswordCallback_)
375           this.confirmPasswordCallback_();
376         else
377           console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.');
378         return;
379       }
381       if (msg.method == 'noPassword') {
382         if (this.noPasswordCallback_)
383           this.noPasswordCallback_(msg.email);
384         else
385           console.error('GaiaAuthHost: Invalid noPasswordCallback_.');
386         return;
387       }
389       if (msg.method == 'authPageLoaded') {
390         this.authDomain = msg.domain;
391         this.authFlow = msg.isSAML ? AuthFlow.SAML : AuthFlow.GAIA;
392         return;
393       }
395       if (msg.method == 'reportState') {
396         var newUrl = setQueryParam(location, 'frameUrl', msg.src);
397         if (history.state) {
398           if (history.state.src != msg.src) {
399             history.pushState({src: msg.src}, '', newUrl);
400           }
401         } else {
402           history.replaceState({src: msg.src});
403         }
404         return;
405       }
407       if (msg.method == 'switchToFullTab') {
408         chrome.send('switchToFullTab', [msg.url]);
409         return;
410       }
412       console.error('Unknown message method=' + msg.method);
413     },
415     /**
416      * Event handler that is invoked when the history state is changed.
417      * @param {object} e The popstate event being triggered.
418      */
419     onPopState_: function(e) {
420       var state = e.state;
421       if (state) {
422         var msg = {
423           method: 'navigate',
424           src: state.src
425         };
426         this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
427       }
428     }
429   };
431   /**
432    * The current auth flow of the hosted gaia_auth extension.
433    * @type {AuthFlow}
434    */
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;
442   return {
443     GaiaAuthHost: GaiaAuthHost
444   };