Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / chromeos / login / screen_gaia_signin.js
1 // Copyright (c) 2012 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.
4
5 /**
6  * @fileoverview Oobe signin screen implementation.
7  */
8
9 <include src="../../gaia_auth_host/gaia_auth_host.js"></include>
10
11 login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
12   // Gaia loading time after which error message must be displayed and
13   // lazy portal check should be fired.
14   /** @const */ var GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC = 7;
15
16   // Maximum Gaia loading time in seconds.
17   /** @const */ var MAX_GAIA_LOADING_TIME_SEC = 60;
18
19   /** @const */ var HELP_TOPIC_ENTERPRISE_REPORTING = 2535613;
20
21   return {
22     EXTERNAL_API: [
23       'loadAuthExtension',
24       'updateAuthExtension',
25       'setAuthenticatedUserEmail',
26       'doReload',
27       'onFrameError',
28       'updateCancelButtonState'
29     ],
30
31     /**
32      * Frame loading error code (0 - no error).
33      * @type {number}
34      * @private
35      */
36     error_: 0,
37
38     /**
39      * Saved gaia auth host load params.
40      * @type {?string}
41      * @private
42      */
43     gaiaAuthParams_: null,
44
45     /**
46      * Whether local version of Gaia page is used.
47      * @type {boolean}
48      * @private
49      */
50     isLocal_: false,
51
52     /**
53      * Email of the user, which is logging in using offline mode.
54      * @type {string}
55      */
56     email: '',
57
58     /**
59      * Timer id of pending load.
60      * @type {number}
61      * @private
62      */
63     loadingTimer_: undefined,
64
65     /**
66      * Whether user can cancel Gaia screen.
67      * @type {boolean}
68      * @private
69      */
70     cancelAllowed_: undefined,
71
72     /**
73      * Whether we should show user pods on the login screen.
74      * @type {boolean}
75      * @private
76      */
77     isShowUsers_: undefined,
78
79     /**
80      * SAML password confirmation attempt count.
81      * @type {number}
82      */
83     samlPasswordConfirmAttempt_: 0,
84
85     /** @override */
86     decorate: function() {
87       this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
88       this.gaiaAuthHost_.addEventListener(
89           'ready', this.onAuthReady_.bind(this));
90       this.gaiaAuthHost_.retrieveAuthenticatedUserEmailCallback =
91           this.onRetrieveAuthenticatedUserEmail_.bind(this);
92       this.gaiaAuthHost_.confirmPasswordCallback =
93           this.onAuthConfirmPassword_.bind(this);
94       this.gaiaAuthHost_.noPasswordCallback =
95           this.onAuthNoPassword_.bind(this);
96       this.gaiaAuthHost_.insecureContentBlockedCallback =
97           this.onInsecureContentBlocked_.bind(this);
98       this.gaiaAuthHost_.addEventListener('authFlowChange',
99           this.onAuthFlowChange_.bind(this));
100
101       $('enterprise-info-hint-link').addEventListener('click', function(e) {
102         chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
103         e.preventDefault();
104       });
105
106
107       this.updateLocalizedContent();
108     },
109
110     /**
111      * Header text of the screen.
112      * @type {string}
113      */
114     get header() {
115       return loadTimeData.getString('signinScreenTitle');
116     },
117
118     /**
119      * Returns true if local version of Gaia is used.
120      * @type {boolean}
121      */
122     get isLocal() {
123       return this.isLocal_;
124     },
125
126     /**
127      * Sets whether local version of Gaia is used.
128      * @param {boolean} value Whether local version of Gaia is used.
129      */
130     set isLocal(value) {
131       this.isLocal_ = value;
132       chrome.send('updateOfflineLogin', [value]);
133     },
134
135     /**
136      * Shows/hides loading UI.
137      * @param {boolean} show True to show loading UI.
138      * @private
139      */
140     showLoadingUI_: function(show) {
141       $('gaia-loading').hidden = !show;
142       this.gaiaAuthHost_.frame.hidden = show;
143       $('signin-right').hidden = show;
144       $('enterprise-info-container').hidden = show;
145       $('gaia-signin-divider').hidden = show;
146     },
147
148     /**
149      * Handler for Gaia loading suspiciously long timeout.
150      * @private
151      */
152     onLoadingSuspiciouslyLong_: function() {
153       if (this != Oobe.getInstance().currentScreen)
154         return;
155       chrome.send('showLoadingTimeoutError');
156       this.loadingTimer_ = window.setTimeout(
157           this.onLoadingTimeOut_.bind(this),
158           (MAX_GAIA_LOADING_TIME_SEC - GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC) *
159           1000);
160     },
161
162     /**
163      * Handler for Gaia loading timeout.
164      * @private
165      */
166     onLoadingTimeOut_: function() {
167       this.loadingTimer_ = undefined;
168       chrome.send('showLoadingTimeoutError');
169     },
170
171     /**
172      * Clears loading timer.
173      * @private
174      */
175     clearLoadingTimer_: function() {
176       if (this.loadingTimer_) {
177         window.clearTimeout(this.loadingTimer_);
178         this.loadingTimer_ = undefined;
179       }
180     },
181
182     /**
183      * Sets up loading timer.
184      * @private
185      */
186     startLoadingTimer_: function() {
187       this.clearLoadingTimer_();
188       this.loadingTimer_ = window.setTimeout(
189           this.onLoadingSuspiciouslyLong_.bind(this),
190           GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
191     },
192
193     /**
194      * Whether Gaia is loading.
195      * @type {boolean}
196      */
197     get loading() {
198       return !$('gaia-loading').hidden;
199     },
200     set loading(loading) {
201       if (loading == this.loading)
202         return;
203
204       this.showLoadingUI_(loading);
205     },
206
207     /**
208      * Event handler that is invoked just before the frame is shown.
209      * @param {string} data Screen init payload. Url of auth extension start
210      *                      page.
211      */
212     onBeforeShow: function(data) {
213       chrome.send('loginUIStateChanged', ['gaia-signin', true]);
214       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
215
216       // Ensure that GAIA signin (or loading UI) is actually visible.
217       window.webkitRequestAnimationFrame(function() {
218         chrome.send('loginVisible', ['gaia-loading']);
219       });
220
221       // Button header is always visible when sign in is presented.
222       // Header is hidden once GAIA reports on successful sign in.
223       Oobe.getInstance().headerHidden = false;
224     },
225
226     /**
227      * Event handler that is invoked just before the screen is hidden.
228      */
229     onBeforeHide: function() {
230       chrome.send('loginUIStateChanged', ['gaia-signin', false]);
231       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
232     },
233
234     /**
235      * Loads the authentication extension into the iframe.
236      * @param {Object} data Extension parameters bag.
237      * @private
238      */
239     loadAuthExtension: function(data) {
240       this.isLocal = data.isLocal;
241       this.email = '';
242
243       // Reset SAML
244       this.classList.toggle('saml', false);
245       this.samlPasswordConfirmAttempt_ = 0;
246
247       this.updateAuthExtension(data);
248
249       var params = {};
250       for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
251         var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
252         if (data[name])
253           params[name] = data[name];
254       }
255
256       if (data.localizedStrings)
257         params.localizedStrings = data.localizedStrings;
258
259       if (data.forceReload ||
260           JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
261         this.error_ = 0;
262         this.gaiaAuthHost_.load(data.useOffline ?
263                                     cr.login.GaiaAuthHost.AuthMode.OFFLINE :
264                                     cr.login.GaiaAuthHost.AuthMode.DEFAULT,
265                                 params,
266                                 this.onAuthCompleted_.bind(this));
267         this.gaiaAuthParams_ = params;
268
269         this.loading = true;
270         this.startLoadingTimer_();
271       } else if (this.loading && this.error_) {
272         // An error has occurred, so trying to reload.
273         this.doReload();
274       }
275     },
276
277     /**
278      * Updates the authentication extension with new parameters, if needed.
279      * @param {Object} data New extension parameters bag.
280      * @private
281      */
282     updateAuthExtension: function(data) {
283       var reasonLabel = $('gaia-signin-reason');
284       if (data.passwordChanged) {
285         reasonLabel.textContent =
286             loadTimeData.getString('signinScreenPasswordChanged');
287         reasonLabel.hidden = false;
288       } else {
289         reasonLabel.hidden = true;
290       }
291
292       $('createAccount').hidden = !data.createAccount;
293       $('guestSignin').hidden = !data.guestSignin;
294       $('createManagedUserPane').hidden = !data.managedUsersEnabled;
295
296       $('createManagedUserLinkPlaceholder').hidden =
297           !data.managedUsersCanCreate;
298       $('createManagedUserNoManagerText').hidden = data.managedUsersCanCreate;
299       $('createManagedUserNoManagerText').textContent =
300           data.managedUsersRestrictionReason;
301
302       this.isShowUsers_ = data.isShowUsers;
303       this.updateCancelButtonState();
304
305       // Sign-in right panel is hidden if all of its items are hidden.
306       var noRightPanel = $('gaia-signin-reason').hidden &&
307                          $('createAccount').hidden &&
308                          $('guestSignin').hidden &&
309                          $('createManagedUserPane').hidden;
310       this.classList.toggle('no-right-panel', noRightPanel);
311       if (Oobe.getInstance().currentScreen === this)
312         Oobe.getInstance().updateScreenSize(this);
313     },
314
315     /**
316      * Sends the authenticated user's e-mail address to the auth extension.
317      * @param {number} attemptToken The opaque token provided to
318      *     onRetrieveAuthenticatedUserEmail_.
319      * @param {string} email The authenticated user's e-mail address.
320      */
321     setAuthenticatedUserEmail: function(attemptToken, email) {
322       if (!email)
323         this.showFatalAuthError();
324       else
325         this.gaiaAuthHost_.setAuthenticatedUserEmail(attemptToken, email);
326     },
327
328     /**
329      * Updates [Cancel] button state. Allow cancellation of screen only when
330      * user pods can be displayed.
331      */
332     updateCancelButtonState: function() {
333       this.cancelAllowed_ = this.isShowUsers_ && $('pod-row').pods.length;
334       $('login-header-bar').allowCancel = this.cancelAllowed_;
335     },
336
337     /**
338      * Whether the current auth flow is SAML.
339      */
340     isSAML: function() {
341        return this.gaiaAuthHost_.authFlow ==
342            cr.login.GaiaAuthHost.AuthFlow.SAML;
343     },
344
345     /**
346      * Invoked when the authFlow property is changed no the gaia host.
347      * @param {Event} e Property change event.
348      */
349     onAuthFlowChange_: function(e) {
350       var isSAML = this.isSAML();
351
352       if (isSAML) {
353         $('saml-notice-message').textContent = loadTimeData.getStringF(
354             'samlNotice',
355             this.gaiaAuthHost_.authDomain);
356       }
357
358       this.classList.toggle('saml', isSAML);
359       $('saml-notice-container').hidden = !isSAML;
360
361       if (Oobe.getInstance().currentScreen === this) {
362         Oobe.getInstance().updateScreenSize(this);
363         $('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
364       }
365     },
366
367     /**
368      * Invoked when the auth host emits 'ready' event.
369      * @private
370      */
371     onAuthReady_: function() {
372       this.loading = false;
373       this.clearLoadingTimer_();
374
375       // Show deferred error bubble.
376       if (this.errorBubble_) {
377         this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
378         this.errorBubble_ = undefined;
379       }
380
381       chrome.send('loginWebuiReady');
382       chrome.send('loginVisible', ['gaia-signin']);
383
384       // Warm up the user images screen.
385       Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
386     },
387
388     /**
389      * Invoked when the user has successfully authenticated via SAML and the
390      * auth host needs to retrieve the user's e-mail.
391      * @param {number} attemptToken Opaque token to be passed to
392      *     setAuthenticatedUserEmail along with the e-mail address.
393      * @param {boolean} apiUsed Whether the principals API was used during
394      *     authentication.
395      * @private
396      */
397     onRetrieveAuthenticatedUserEmail_: function(attemptToken, apiUsed) {
398       if (apiUsed) {
399         // If the principals API was used, report this to the C++ backend so
400         // that statistics can be kept. If password scraping was used instead,
401         // there is no need to inform the C++ backend at this point: Either
402         // onAuthNoPassword_ or onAuthConfirmPassword_ will be called in a
403         // moment, both of which imply to the backend that the API was not used.
404         chrome.send('usingSAMLAPI');
405       }
406       chrome.send('retrieveAuthenticatedUserEmail', [attemptToken]);
407     },
408
409     /**
410      * Invoked when the user has successfully authenticated via SAML, the
411      * principals API was not used and the auth host needs the user to confirm
412      * the scraped password.
413      * @param {number} passwordCount The number of passwords that were scraped.
414      * @private
415      */
416     onAuthConfirmPassword_: function(passwordCount) {
417       this.loading = true;
418       Oobe.getInstance().headerHidden = false;
419
420       if (this.samlPasswordConfirmAttempt_ == 0)
421         chrome.send('scrapedPasswordCount', [passwordCount]);
422
423       if (this.samlPasswordConfirmAttempt_ < 2) {
424         login.ConfirmPasswordScreen.show(
425             this.samlPasswordConfirmAttempt_,
426             this.onConfirmPasswordCollected_.bind(this));
427       } else {
428         chrome.send('scrapedPasswordVerificationFailed');
429         this.showFatalAuthError();
430       }
431     },
432
433     /**
434      * Invoked when the confirm password screen is dismissed.
435      * @private
436      */
437     onConfirmPasswordCollected_: function(password) {
438       this.samlPasswordConfirmAttempt_++;
439       this.gaiaAuthHost_.verifyConfirmedPassword(password);
440
441       // Shows signin UI again without changing states.
442       Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
443     },
444
445     /**
446      * Inovked when the user has successfully authenticated via SAML, the
447      * principals API was not used and no passwords could be scraped.
448      * @param {string} email The authenticated user's e-mail.
449      */
450     onAuthNoPassword_: function(email) {
451       this.showFatalAuthError();
452       chrome.send('scrapedPasswordCount', [0]);
453     },
454
455     /**
456      * Invoked when the authentication flow had to be aborted because content
457      * served over an unencrypted connection was detected. Shows a fatal error.
458      * This method is only called on Chrome OS, where the entire authentication
459      * flow is required to be encrypted.
460      * @param {string} url The URL that was blocked.
461      */
462     onInsecureContentBlocked_: function(url) {
463       this.showFatalAuthError(loadTimeData.getStringF(
464           'fatalErrorMessageInsecureURL',
465           url));
466     },
467
468     /**
469      * Shows the fatal auth error.
470      * @param {string} message The error message to show.
471      */
472     showFatalAuthError: function(message) {
473       if (!message)
474         message = loadTimeData.getString('fatalErrorMessageGeneric');
475       login.FatalErrorScreen.show(message, Oobe.showSigninUI);
476     },
477
478     /**
479      * Invoked when auth is completed successfully.
480      * @param {!Object} credentials Credentials of the completed authentication.
481      * @private
482      */
483     onAuthCompleted_: function(credentials) {
484       if (credentials.useOffline) {
485         this.email = credentials.email;
486         chrome.send('authenticateUser',
487                     [credentials.email, credentials.password]);
488       } else if (credentials.authCode) {
489         chrome.send('completeAuthentication',
490                     [credentials.email,
491                      credentials.password,
492                      credentials.authCode]);
493       } else {
494         chrome.send('completeLogin',
495                     [credentials.email,
496                      credentials.password,
497                      credentials.usingSAML]);
498       }
499
500       this.loading = true;
501       // Now that we're in logged in state header should be hidden.
502       Oobe.getInstance().headerHidden = true;
503       // Clear any error messages that were shown before login.
504       Oobe.clearErrors();
505     },
506
507     /**
508      * Clears input fields and switches to input mode.
509      * @param {boolean} takeFocus True to take focus.
510      * @param {boolean} forceOnline Whether online sign-in should be forced.
511      * If |forceOnline| is false previously used sign-in type will be used.
512      */
513     reset: function(takeFocus, forceOnline) {
514       // Reload and show the sign-in UI if needed.
515       if (takeFocus) {
516         if (!forceOnline && this.isLocal) {
517           // Show 'Cancel' button to allow user to return to the main screen
518           // (e.g. this makes sense when connection is back).
519           Oobe.getInstance().headerHidden = false;
520           $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
521           // Do nothing, since offline version is reloaded after an error comes.
522         } else {
523           Oobe.showSigninUI();
524         }
525       }
526     },
527
528     /**
529      * Reloads extension frame.
530      */
531     doReload: function() {
532       this.error_ = 0;
533       this.gaiaAuthHost_.reload();
534       this.loading = true;
535       this.startLoadingTimer_();
536     },
537
538     /**
539      * Updates localized content of the screen that is not updated via template.
540      */
541     updateLocalizedContent: function() {
542       $('createAccount').innerHTML = loadTimeData.getStringF(
543           'createAccount',
544           '<a id="createAccountLink" class="signin-link" href="#">',
545           '</a>');
546       $('guestSignin').innerHTML = loadTimeData.getStringF(
547           'guestSignin',
548           '<a id="guestSigninLink" class="signin-link" href="#">',
549           '</a>');
550       $('createManagedUserLinkPlaceholder').innerHTML = loadTimeData.getStringF(
551             'createLocallyManagedUser',
552             '<a id="createManagedUserLink" class="signin-link" href="#">',
553             '</a>');
554       $('createAccountLink').addEventListener('click', function(e) {
555         chrome.send('createAccount');
556         e.preventDefault();
557       });
558       $('guestSigninLink').addEventListener('click', function(e) {
559         chrome.send('launchIncognito');
560         e.preventDefault();
561       });
562       $('createManagedUserLink').addEventListener('click', function(e) {
563         chrome.send('showLocallyManagedUserCreationScreen');
564         e.preventDefault();
565       });
566     },
567
568     /**
569      * Shows sign-in error bubble.
570      * @param {number} loginAttempts Number of login attemps tried.
571      * @param {HTMLElement} content Content to show in bubble.
572      */
573     showErrorBubble: function(loginAttempts, error) {
574       if (this.isLocal) {
575         $('add-user-button').hidden = true;
576         $('cancel-add-user-button').hidden = false;
577         // Reload offline version of the sign-in extension, which will show
578         // error itself.
579         chrome.send('offlineLogin', [this.email]);
580       } else if (!this.loading) {
581         // We want to show bubble near "Email" field, but we can't calculate
582         // it's position because it is located inside iframe. So we only
583         // can hardcode some constants.
584         /** @const */ var ERROR_BUBBLE_OFFSET = 84;
585         /** @const */ var ERROR_BUBBLE_PADDING = 0;
586         $('bubble').showContentForElement($('login-box'),
587                                           cr.ui.Bubble.Attachment.LEFT,
588                                           error,
589                                           ERROR_BUBBLE_OFFSET,
590                                           ERROR_BUBBLE_PADDING);
591       } else {
592         // Defer the bubble until the frame has been loaded.
593         this.errorBubble_ = [loginAttempts, error];
594       }
595     },
596
597     /**
598      * Called when user canceled signin.
599      */
600     cancel: function() {
601       if (!this.cancelAllowed_) {
602         // In OOBE signin screen, cancel is not allowed because there is
603         // no other screen to show. If user is in middle of a saml flow,
604         // reset signin screen to get out of the saml flow.
605         if (this.isSAML())
606           Oobe.resetSigninUI(true);
607
608         return;
609       }
610
611       $('pod-row').loadLastWallpaper();
612       Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
613       Oobe.resetSigninUI(true);
614     },
615
616     /**
617      * Handler for iframe's error notification coming from the outside.
618      * For more info see C++ class 'WebUILoginView' which calls this method.
619      * @param {number} error Error code.
620      * @param {string} url The URL that failed to load.
621      */
622     onFrameError: function(error, url) {
623       this.error_ = error;
624       chrome.send('frameLoadingCompleted', [this.error_]);
625     },
626   };
627 });