Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / gaia_auth_host / gaia_auth_host.js
blobe568fc7d81e97b1e53e84ac8b66d8c0459fe68aa
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    * Origin of the gaia sign in page.
39    * @const
40    */
41   var GAIA_ORIGIN = 'https://accounts.google.com';
43   /**
44    * Supported params of auth extension. For a complete list, check out the
45    * auth extension's main.js.
46    * @type {!Array<string>}
47    * @const
48    */
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;
60   ];
62   /**
63    * Supported localized strings. For a complete list, check out the auth
64    * extension's offline.js
65    * @type {!Array<string>}
66    * @const
67    */
68   var LOCALIZED_STRING_PARAMS = [
69       'stringSignIn',
70       'stringEmail',
71       'stringPassword',
72       'stringEmptyEmail',
73       'stringEmptyPassword',
74       'stringError'
75   ];
77   /**
78    * Enum for the authorization mode, must match AuthMode defined in
79    * chrome/browser/ui/webui/inline_login_ui.cc.
80    * @enum {number}
81    */
82   var AuthMode = {
83     DEFAULT: 0,
84     OFFLINE: 1,
85     DESKTOP: 2
86   };
88   /**
89    * Enum for the auth flow.
90    * @enum {number}
91    */
92   var AuthFlow = {
93     GAIA: 0,
94     SAML: 1
95   };
97   /**
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.
101    * @constructor
102    * @extends {cr.EventTarget}
103    */
104   function GaiaAuthHost(container) {
105     this.frame_ = typeof container == 'string' ? $(container) : container;
106     assert(this.frame_);
107     window.addEventListener('message',
108                             this.onMessage_.bind(this), false);
109   }
111   GaiaAuthHost.prototype = {
112     __proto__: cr.EventTarget.prototype,
114     /**
115      * Auth extension params
116      * @type {Object}
117      */
118     authParams_: {},
120     /**
121      * An url to use with {@code reload}.
122      * @type {?string}
123      * @private
124      */
125     reloadUrl_: null,
127     /**
128      * Invoked when authentication is completed successfully with credential
129      * data. A credential data object looks like this:
130      * <pre>
131      * {@code
132      * {
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.
137      * }
138      * }
139      * </pre>
140      * @type {function(Object)}
141      * @private
142      */
143     successCallback_: null,
145     /**
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.
152      * @type {function()}
153      */
154     confirmPasswordCallback_: null,
156     /**
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)}
162      */
163     noPasswordCallback_: null,
165     /**
166      * Invoked when the authentication flow had to be aborted because content
167      * served over an unencrypted connection was detected.
168      */
169     insecureContentBlockedCallback_: null,
171     /**
172      * Invoked to display an error message to the user when a GAIA error occurs
173      * during authentication.
174      * @type {function()}
175      */
176     missingGaiaInfoCallback_: null,
178     /**
179      * Invoked to record that the credentials passing API was used.
180      * @type {function()}
181      */
182     samlApiUsedCallback_: null,
184     /**
185      * The iframe container.
186      * @type {HTMLIFrameElement}
187      */
188     get frame() {
189       return this.frame_;
190     },
192     /**
193      * Sets confirmPasswordCallback_.
194      * @type {function()}
195      */
196     set confirmPasswordCallback(callback) {
197       this.confirmPasswordCallback_ = callback;
198     },
200     /**
201      * Sets noPasswordCallback_.
202      * @type {function()}
203      */
204     set noPasswordCallback(callback) {
205       this.noPasswordCallback_ = callback;
206     },
208     /**
209      * Sets insecureContentBlockedCallback_.
210      * @type {function(string)}
211      */
212     set insecureContentBlockedCallback(callback) {
213       this.insecureContentBlockedCallback_ = callback;
214     },
216     /**
217      * Sets missingGaiaInfoCallback_.
218      * @type {function()}
219      */
220     set missingGaiaInfoCallback(callback) {
221       this.missingGaiaInfoCallback_ = callback;
222     },
224     /**
225      * Sets samlApiUsedCallback_.
226      * @type {function()}
227      */
228     set samlApiUsedCallback(callback) {
229       this.samlApiUsedCallback_ = callback;
230     },
232     /**
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.
240      */
241     load: function(authMode, data, successCallback) {
242       var params = {};
244       var populateParams = function(nameList, values) {
245         if (!values)
246           return;
248         for (var i in nameList) {
249           var name = nameList[i];
250           if (values[name])
251             params[name] = values[name];
252         }
253       };
255       populateParams(SUPPORTED_PARAMS, data);
256       populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings);
257       params['needPassword'] = true;
259       var url;
260       switch (authMode) {
261         case AuthMode.OFFLINE:
262           url = OFFLINE_AUTH_URL;
263           break;
264         case AuthMode.DESKTOP:
265           url = AUTH_URL;
266           params['desktopMode'] = true;
267           break;
268         default:
269           url = AUTH_URL;
270       }
272       this.authParams_ = params;
273       this.reloadUrl_ = url;
274       this.successCallback_ = successCallback;
276       this.reload();
277     },
279     /**
280      * Reloads the auth extension.
281      */
282     reload: function() {
283       this.frame_.src = this.reloadUrl_;
284       this.authFlow = AuthFlow.GAIA;
285     },
287     /**
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.
291      */
292     verifyConfirmedPassword: function(password) {
293       var msg = {
294         method: 'verifyConfirmedPassword',
295         password: password
296       };
297       this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
298     },
300     /**
301      * Invoked to process authentication success.
302      * @param {Object} credentials Credential object to pass to success
303      *     callback.
304      * @private
305      */
306     onAuthSuccess_: function(credentials) {
307       if (this.successCallback_)
308         this.successCallback_(credentials);
309       cr.dispatchSimpleEvent(this, 'completed');
310     },
312     /**
313      * Checks if message comes from the loaded authentication extension.
314      * @param {Object} e Payload of the received HTML5 message.
315      * @type {boolean}
316      */
317     isAuthExtMessage_: function(e) {
318       return this.frame_.src &&
319           this.frame_.src.indexOf(e.origin) == 0 &&
320           e.source == this.frame_.contentWindow;
321     },
323     /**
324      * Event handler that is invoked when HTML5 message is received.
325      * @param {object} e Payload of the received HTML5 message.
326      */
327     onMessage_: function(e) {
328       var msg = e.data;
330       if (!this.isAuthExtMessage_(e))
331         return;
333       if (msg.method == 'loginUIDOMContentLoaded') {
334         this.frame_.contentWindow.postMessage(this.authParams_, AUTH_URL_BASE);
335         return;
336       }
338       if (msg.method == 'loginUILoaded') {
339         cr.dispatchSimpleEvent(this, 'ready');
340         return;
341       }
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);
347           return;
348         }
349         this.onAuthSuccess_({email: msg.email,
350                              password: msg.password,
351                              gaiaId: msg.gaiaId,
352                              useOffline: msg.method == 'offlineLogin',
353                              usingSAML: msg.usingSAML || false,
354                              chooseWhatToSync: msg.chooseWhatToSync,
355                              skipForNow: msg.skipForNow || false,
356                              sessionIndex: msg.sessionIndex || ''});
357         return;
358       }
360       if (msg.method == 'completeAuthenticationAuthCodeOnly') {
361         if (!msg.authCode) {
362           console.error(
363               'GaiaAuthHost: completeAuthentication without auth code.');
364           var msg = {method: 'redirectToSignin'};
365           this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE);
366           return;
367         }
368         this.onAuthSuccess_({authCodeOnly: true, authCode: msg.authCode});
369         return;
370       }
372       if (msg.method == 'confirmPassword') {
373         if (this.confirmPasswordCallback_)
374           this.confirmPasswordCallback_(msg.email, msg.passwordCount);
375         else
376           console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.');
377         return;
378       }
380       if (msg.method == 'noPassword') {
381         if (this.noPasswordCallback_)
382           this.noPasswordCallback_(msg.email);
383         else
384           console.error('GaiaAuthHost: Invalid noPasswordCallback_.');
385         return;
386       }
388       if (msg.method == 'authPageLoaded') {
389         this.authDomain = msg.domain;
390         this.authFlow = msg.isSAML ? AuthFlow.SAML : AuthFlow.GAIA;
391         return;
392       }
394       if (msg.method == 'resetAuthFlow') {
395         this.authFlow = AuthFlow.GAIA;
396         return;
397       }
399       if (msg.method == 'insecureContentBlocked') {
400         if (this.insecureContentBlockedCallback_) {
401           this.insecureContentBlockedCallback_(msg.url);
402         } else {
403           console.error(
404               'GaiaAuthHost: Invalid insecureContentBlockedCallback_.');
405         }
406         return;
407       }
409       if (msg.method == 'switchToFullTab') {
410         chrome.send('switchToFullTab', [msg.url]);
411         return;
412       }
414       if (msg.method == 'missingGaiaInfo') {
415         if (this.missingGaiaInfoCallback_) {
416           this.missingGaiaInfoCallback_();
417         } else {
418           console.error('GaiaAuthHost: Invalid missingGaiaInfoCallback_.');
419         }
420         return;
421       }
423       if (msg.method == 'samlApiUsed') {
424         if (this.samlApiUsedCallback_) {
425           this.samlApiUsedCallback_();
426         } else {
427           console.error('GaiaAuthHost: Invalid samlApiUsedCallback_.');
428         }
429         return;
430       }
432       console.error('Unknown message method=' + msg.method);
433     }
434   };
436   /**
437    * The domain name of the current auth page.
438    * @type {string}
439    */
440   cr.defineProperty(GaiaAuthHost, 'authDomain');
442   /**
443    * The current auth flow of the hosted gaia_auth extension.
444    * @type {AuthFlow}
445    */
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;
453   return {
454     GaiaAuthHost: GaiaAuthHost
455   };