Remove the old signature of NotificationManager::closePersistent().
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / screen_gaia_signin.js
blob6420bb349d456ffe2643dc1603e58f193c5258c9
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.
5 /**
6  * @fileoverview Oobe signin screen implementation.
7  */
9 login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
10   // Gaia loading time after which error message must be displayed and
11   // lazy portal check should be fired.
12   /** @const */ var GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC = 7;
14   // Maximum Gaia loading time in seconds.
15   /** @const */ var MAX_GAIA_LOADING_TIME_SEC = 60;
17   /** @const */ var HELP_TOPIC_ENTERPRISE_REPORTING = 2535613;
19   return {
20     EXTERNAL_API: [
21       'loadAuthExtension',
22       'updateAuthExtension',
23       'doReload',
24       'onFrameError',
25       'updateCancelButtonState',
26       'switchToFullTab'
27     ],
29     /**
30      * Frame loading error code (0 - no error).
31      * @type {number}
32      * @private
33      */
34     error_: 0,
36     /**
37      * Saved gaia auth host load params.
38      * @type {?string}
39      * @private
40      */
41     gaiaAuthParams_: null,
43     /**
44      * Whether local version of Gaia page is used.
45      * @type {boolean}
46      * @private
47      */
48     isLocal_: false,
50     /**
51      * Whether MinuteMaid flow is active.
52      * @type {boolean}
53      */
54     isMinuteMaid: false,
56     /**
57      * Email of the user, which is logging in using offline mode.
58      * @type {string}
59      */
60     email: '',
62     /**
63      * Whether consumer management enrollment is in progress.
64      * @type {boolean}
65      * @private
66      */
67     isEnrollingConsumerManagement_: false,
69     /**
70      * Timer id of pending load.
71      * @type {number}
72      * @private
73      */
74     loadingTimer_: undefined,
76     /**
77      * Whether user can cancel Gaia screen.
78      * @type {boolean}
79      * @private
80      */
81     cancelAllowed_: undefined,
83     /**
84      * Whether we should show user pods on the login screen.
85      * @type {boolean}
86      * @private
87      */
88     isShowUsers_: undefined,
90     /**
91      * SAML password confirmation attempt count.
92      * @type {number}
93      */
94     samlPasswordConfirmAttempt_: 0,
96     /**
97      * Whether we should show webview based signin.
98      * @type {boolean}
99      * @private
100      */
101     isWebviewSignin: false,
103     /** @override */
104     decorate: function() {
105       this.isWebviewSignin = loadTimeData.getValue('isWebviewSignin');
106       if (this.isWebviewSignin) {
107         // Replace iframe with webview.
108         var webview = this.ownerDocument.createElement('webview');
109         webview.id = 'signin-frame';
110         webview.name = 'signin-frame';
111         webview.hidden = true;
112         $('signin-frame').parentNode.replaceChild(webview, $('signin-frame'));
113         this.gaiaAuthHost_ = new cr.login.GaiaAuthHost(webview);
114       } else {
115         this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
116       }
117       this.gaiaAuthHost_.addEventListener(
118           'ready', this.onAuthReady_.bind(this));
119       this.gaiaAuthHost_.addEventListener(
120           'dialogShown', this.onDialogShown_.bind(this));
121       this.gaiaAuthHost_.addEventListener(
122           'dialogHidden', this.onDialogHidden_.bind(this));
123       this.gaiaAuthHost_.confirmPasswordCallback =
124           this.onAuthConfirmPassword_.bind(this);
125       this.gaiaAuthHost_.noPasswordCallback =
126           this.onAuthNoPassword_.bind(this);
127       this.gaiaAuthHost_.insecureContentBlockedCallback =
128           this.onInsecureContentBlocked_.bind(this);
129       this.gaiaAuthHost_.missingGaiaInfoCallback =
130           this.missingGaiaInfo_.bind(this);
131       this.gaiaAuthHost_.samlApiUsedCallback =
132           this.samlApiUsed_.bind(this);
133       this.gaiaAuthHost_.addEventListener('authFlowChange',
134           this.onAuthFlowChange_.bind(this));
135       this.gaiaAuthHost_.addEventListener('authCompleted',
136           this.onAuthCompletedMessage_.bind(this));
138       $('enterprise-info-hint-link').addEventListener('click', function(e) {
139         chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
140         e.preventDefault();
141       });
143       $('close-button-item').addEventListener('click', function(e) {
144         this.cancel();
145         e.preventDefault();
146       }.bind(this));
148       this.updateLocalizedContent();
149     },
151     /**
152      * Header text of the screen.
153      * @type {string}
154      */
155     get header() {
156       return loadTimeData.getString('signinScreenTitle');
157     },
159     /**
160      * Returns true if local version of Gaia is used.
161      * @type {boolean}
162      */
163     get isLocal() {
164       return this.isLocal_;
165     },
167     /**
168      * Sets whether local version of Gaia is used.
169      * @param {boolean} value Whether local version of Gaia is used.
170      */
171     set isLocal(value) {
172       this.isLocal_ = value;
173       chrome.send('updateOfflineLogin', [value]);
174     },
176     /**
177      * Shows/hides loading UI.
178      * @param {boolean} show True to show loading UI.
179      * @private
180      */
181     showLoadingUI_: function(show) {
182       $('gaia-loading').hidden = !show;
183       $('signin-frame').hidden = show;
184       $('signin-right').hidden = show;
185       $('enterprise-info-container').hidden = show;
186       $('gaia-signin-divider').hidden = show;
187       this.classList.toggle('loading', show);
188     },
190     /**
191      * Handler for Gaia loading suspiciously long timeout.
192      * @private
193      */
194     onLoadingSuspiciouslyLong_: function() {
195       if (this != Oobe.getInstance().currentScreen)
196         return;
197       chrome.send('showLoadingTimeoutError');
198       this.loadingTimer_ = window.setTimeout(
199           this.onLoadingTimeOut_.bind(this),
200           (MAX_GAIA_LOADING_TIME_SEC - GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC) *
201           1000);
202     },
204     /**
205      * Handler for Gaia loading timeout.
206      * @private
207      */
208     onLoadingTimeOut_: function() {
209       this.loadingTimer_ = undefined;
210       chrome.send('showLoadingTimeoutError');
211     },
213     /**
214      * Clears loading timer.
215      * @private
216      */
217     clearLoadingTimer_: function() {
218       if (this.loadingTimer_) {
219         window.clearTimeout(this.loadingTimer_);
220         this.loadingTimer_ = undefined;
221       }
222     },
224     /**
225      * Sets up loading timer.
226      * @private
227      */
228     startLoadingTimer_: function() {
229       this.clearLoadingTimer_();
230       this.loadingTimer_ = window.setTimeout(
231           this.onLoadingSuspiciouslyLong_.bind(this),
232           GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
233     },
235     /**
236      * Whether Gaia is loading.
237      * @type {boolean}
238      */
239     get loading() {
240       return !$('gaia-loading').hidden;
241     },
242     set loading(loading) {
243       if (loading == this.loading)
244         return;
246       this.showLoadingUI_(loading);
247     },
249     /**
250      * Event handler that is invoked just before the frame is shown.
251      * @param {string} data Screen init payload. Url of auth extension start
252      *                      page.
253      */
254     onBeforeShow: function(data) {
255       chrome.send('loginUIStateChanged', ['gaia-signin', true]);
256       $('login-header-bar').signinUIState =
257           this.isEnrollingConsumerManagement_ ?
258               SIGNIN_UI_STATE.CONSUMER_MANAGEMENT_ENROLLMENT :
259               SIGNIN_UI_STATE.GAIA_SIGNIN;
261       // Ensure that GAIA signin (or loading UI) is actually visible.
262       window.requestAnimationFrame(function() {
263         chrome.send('loginVisible', ['gaia-loading']);
264       });
265       $('close-button-item').disabled = false;
266       this.classList.toggle('loading', this.loading);
268       // Button header is always visible when sign in is presented.
269       // Header is hidden once GAIA reports on successful sign in.
270       Oobe.getInstance().headerHidden = false;
271     },
273     onAfterShow: function(data) {
274       if (!this.loading && this.isWebviewSignin)
275         $('signin-frame').focus();
276     },
278     /**
279      * Event handler that is invoked just before the screen is hidden.
280      */
281     onBeforeHide: function() {
282       chrome.send('loginUIStateChanged', ['gaia-signin', false]);
283       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
284     },
286     /**
287      * Loads the authentication extension into the iframe.
288      * @param {Object} data Extension parameters bag.
289      * @private
290      */
291     loadAuthExtension: function(data) {
292       this.isLocal = data.isLocal;
293       this.email = '';
294       this.isMinuteMaid = data.useMinuteMaid;
296       // Reset SAML
297       this.classList.toggle('full-width', false);
298       this.samlPasswordConfirmAttempt_ = 0;
300       this.updateAuthExtension(data);
302       var params = {};
303       for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
304         var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
305         if (data[name])
306           params[name] = data[name];
307       }
309       if (data.localizedStrings)
310         params.localizedStrings = data.localizedStrings;
312       if (this.isMinuteMaid) {
313         $('inner-container').classList.add('minute-maid');
314         $('progress-dots').hidden = true;
315         if (data.enterpriseDomain)
316           params.enterpriseDomain = data.enterpriseDomain;
317         params.chromeType = data.chromeType;
318         data.useEmbedded = false;
319         params.isMinuteMaidChromeOS = true;
320         $('login-header-bar').showGuestButton = true;
321       }
323       if (data.gaiaEndpoint)
324         params.gaiaPath = data.gaiaEndpoint;
326       $('login-header-bar').minuteMaid = this.isMinuteMaid;
328       if (data.useEmbedded)
329         params.gaiaPath = 'EmbeddedSignIn';
331       if (data.forceReload ||
332           JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
333         this.error_ = 0;
335         var authMode = cr.login.GaiaAuthHost.AuthMode.DEFAULT;
336         if (data.useOffline)
337           authMode = cr.login.GaiaAuthHost.AuthMode.OFFLINE;
338         else if (data.useEmbedded)
339           authMode = cr.login.GaiaAuthHost.AuthMode.DESKTOP;
341         this.gaiaAuthHost_.load(authMode,
342                                 params,
343                                 this.onAuthCompleted_.bind(this));
344         this.gaiaAuthParams_ = params;
346         this.loading = true;
347         this.startLoadingTimer_();
348       } else if (this.loading && this.error_) {
349         // An error has occurred, so trying to reload.
350         this.doReload();
351       }
352     },
354     /**
355      * Updates the authentication extension with new parameters, if needed.
356      * @param {Object} data New extension parameters bag.
357      * @private
358      */
359     updateAuthExtension: function(data) {
360       var reasonLabel = $('gaia-signin-reason');
361       if (data.passwordChanged) {
362         reasonLabel.textContent =
363             loadTimeData.getString('signinScreenPasswordChanged');
364         reasonLabel.hidden = false;
365       } else {
366         reasonLabel.hidden = true;
367       }
369       if (this.isMinuteMaid) {
370         $('login-header-bar').showCreateSupervisedButton =
371             data.supervisedUsersCanCreate;
372       } else {
373         $('createAccount').hidden = !data.createAccount;
374         $('guestSignin').hidden = !data.guestSignin;
375         $('createSupervisedUserPane').hidden = !data.supervisedUsersEnabled;
377         $('createSupervisedUserLinkPlaceholder').hidden =
378             !data.supervisedUsersCanCreate;
379         $('createSupervisedUserNoManagerText').hidden =
380             data.supervisedUsersCanCreate;
381         $('createSupervisedUserNoManagerText').textContent =
382             data.supervisedUsersRestrictionReason;
383       }
385       var isEnrollingConsumerManagement = data.isEnrollingConsumerManagement;
386       $('consumerManagementEnrollment').hidden = !isEnrollingConsumerManagement;
388       this.isShowUsers_ = data.isShowUsers;
389       this.updateCancelButtonState();
391       this.isEnrollingConsumerManagement_ = isEnrollingConsumerManagement;
393       // Sign-in right panel is hidden if all of its items are hidden.
394       var noRightPanel = $('gaia-signin-reason').hidden &&
395                          $('createAccount').hidden &&
396                          $('guestSignin').hidden &&
397                          $('createSupervisedUserPane').hidden &&
398                          $('consumerManagementEnrollment').hidden;
399       this.classList.toggle('no-right-panel', noRightPanel);
400       this.classList.toggle('full-width', false);
401       if (Oobe.getInstance().currentScreen === this)
402         Oobe.getInstance().updateScreenSize(this);
403     },
405     /**
406      * Updates [Cancel] button state. Allow cancellation of screen only when
407      * user pods can be displayed.
408      */
409     updateCancelButtonState: function() {
410       this.cancelAllowed_ = this.isShowUsers_ && $('pod-row').pods.length;
411       $('login-header-bar').allowCancel = this.cancelAllowed_;
412       if (this.isMinuteMaid)
413         $('close-button-item').hidden = !this.cancelAllowed_;
414     },
416     switchToFullTab: function() {
417       this.classList.toggle('no-right-panel', true);
418       this.classList.toggle('full-width', true);
419     },
421     /**
422      * Whether the current auth flow is SAML.
423      */
424     isSAML: function() {
425        return this.gaiaAuthHost_.authFlow ==
426            cr.login.GaiaAuthHost.AuthFlow.SAML;
427     },
429     /**
430      * Invoked when the authFlow property is changed no the gaia host.
431      * @param {Event} e Property change event.
432      */
433     onAuthFlowChange_: function(e) {
434       var isSAML = this.isSAML();
436       if (isSAML) {
437         $('saml-notice-message').textContent = loadTimeData.getStringF(
438             'samlNotice',
439             this.gaiaAuthHost_.authDomain);
440       }
442       this.classList.toggle('no-right-panel', isSAML);
443       this.classList.toggle('full-width', isSAML);
444       $('saml-notice-container').hidden = !isSAML;
446       if (Oobe.getInstance().currentScreen === this) {
447         Oobe.getInstance().updateScreenSize(this);
448         $('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
449         if (this.isMinuteMaid)
450           $('close-button-item').hidden = !(isSAML || this.cancelAllowed_);
451       }
452     },
454     /**
455      * Invoked when the auth host emits 'ready' event.
456      * @private
457      */
458     onAuthReady_: function() {
459       this.loading = false;
460       this.clearLoadingTimer_();
462       // Show deferred error bubble.
463       if (this.errorBubble_) {
464         this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
465         this.errorBubble_ = undefined;
466       }
468       chrome.send('loginWebuiReady');
469       chrome.send('loginVisible', ['gaia-signin']);
471       // Warm up the user images screen.
472       Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
473     },
475     /**
476      * Invoked when the auth host emits 'dialogShown' event.
477      * @private
478      */
479     onDialogShown_: function() {
480       $('close-button-item').disabled = true;
481     },
483     /**
484      * Invoked when the auth host emits 'dialogHidden' event.
485      * @private
486      */
487     onDialogHidden_: function() {
488       $('close-button-item').disabled = false;
489     },
491     /**
492      * Invoked when the user has successfully authenticated via SAML, the
493      * principals API was not used and the auth host needs the user to confirm
494      * the scraped password.
495      * @param {number} passwordCount The number of passwords that were scraped.
496      * @private
497      */
498     onAuthConfirmPassword_: function(passwordCount) {
499       this.loading = true;
500       Oobe.getInstance().headerHidden = false;
502       if (this.samlPasswordConfirmAttempt_ == 0)
503         chrome.send('scrapedPasswordCount', [passwordCount]);
505       if (this.samlPasswordConfirmAttempt_ < 2) {
506         login.ConfirmPasswordScreen.show(
507             this.samlPasswordConfirmAttempt_,
508             this.onConfirmPasswordCollected_.bind(this));
509       } else {
510         chrome.send('scrapedPasswordVerificationFailed');
511         this.showFatalAuthError(
512             loadTimeData.getString('fatalErrorMessageVerificationFailed'));
513       }
514     },
516     /**
517      * Invoked when the confirm password screen is dismissed.
518      * @private
519      */
520     onConfirmPasswordCollected_: function(password) {
521       this.samlPasswordConfirmAttempt_++;
522       this.gaiaAuthHost_.verifyConfirmedPassword(password);
524       // Shows signin UI again without changing states.
525       Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
526     },
528     /**
529      * Inovked when the user has successfully authenticated via SAML, the
530      * principals API was not used and no passwords could be scraped.
531      * @param {string} email The authenticated user's e-mail.
532      */
533     onAuthNoPassword_: function(email) {
534       this.showFatalAuthError(loadTimeData.getString(
535           'fatalErrorMessageNoPassword'));
536       chrome.send('scrapedPasswordCount', [0]);
537     },
539     /**
540      * Invoked when the authentication flow had to be aborted because content
541      * served over an unencrypted connection was detected. Shows a fatal error.
542      * This method is only called on Chrome OS, where the entire authentication
543      * flow is required to be encrypted.
544      * @param {string} url The URL that was blocked.
545      */
546     onInsecureContentBlocked_: function(url) {
547       this.showFatalAuthError(loadTimeData.getStringF(
548           'fatalErrorMessageInsecureURL',
549           url));
550     },
552     /**
553      * Shows the fatal auth error.
554      * @param {string} message The error message to show.
555      */
556     showFatalAuthError: function(message) {
557       login.FatalErrorScreen.show(message, Oobe.showSigninUI);
558     },
560     /**
561      * Show fatal auth error when information is missing from GAIA.
562      */
563     missingGaiaInfo_: function() {
564       this.showFatalAuthError(
565           loadTimeData.getString('fatalErrorMessageNoAccountDetails'));
566     },
568     /**
569      * Record that SAML API was used during sign-in.
570      */
571     samlApiUsed_: function() {
572       chrome.send('usingSAMLAPI');
573     },
575     /**
576      * Invoked when auth is completed successfully.
577      * @param {!Object} credentials Credentials of the completed authentication.
578      * @private
579      */
580     onAuthCompleted_: function(credentials) {
581       if (credentials.useOffline) {
582         this.email = credentials.email;
583         chrome.send('authenticateUser',
584                     [credentials.email,
585                      credentials.password]);
586       } else if (credentials.authCode) {
587         if (credentials.hasOwnProperty('authCodeOnly') &&
588             credentials.authCodeOnly) {
589           chrome.send('completeAuthenticationAuthCodeOnly',
590                       [credentials.authCode]);
591         } else {
592           chrome.send('completeAuthentication',
593                       [credentials.gaiaId,
594                        credentials.email,
595                        credentials.password,
596                        credentials.authCode]);
597         }
598       } else {
599         chrome.send('completeLogin',
600                     [credentials.gaiaId,
601                      credentials.email,
602                      credentials.password,
603                      credentials.usingSAML]);
604       }
606       this.loading = true;
607       // Now that we're in logged in state header should be hidden.
608       Oobe.getInstance().headerHidden = true;
609       // Clear any error messages that were shown before login.
610       Oobe.clearErrors();
611     },
613     /**
614      * Invoked when onAuthCompleted message received.
615      * @param {!Object} e Payload of the received HTML5 message.
616      * @private
617      */
618     onAuthCompletedMessage_: function(e) {
619       this.onAuthCompleted_(e.detail);
620     },
622     /**
623      * Clears input fields and switches to input mode.
624      * @param {boolean} takeFocus True to take focus.
625      * @param {boolean} forceOnline Whether online sign-in should be forced.
626      * If |forceOnline| is false previously used sign-in type will be used.
627      */
628     reset: function(takeFocus, forceOnline) {
629       // Reload and show the sign-in UI if needed.
630       if (takeFocus) {
631         if (!forceOnline && this.isLocal) {
632           // Show 'Cancel' button to allow user to return to the main screen
633           // (e.g. this makes sense when connection is back).
634           Oobe.getInstance().headerHidden = false;
635           $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
636           // Do nothing, since offline version is reloaded after an error comes.
637         } else {
638           Oobe.showSigninUI();
639         }
640       }
641     },
643     /**
644      * Reloads extension frame.
645      */
646     doReload: function() {
647       this.error_ = 0;
648       this.gaiaAuthHost_.reload();
649       this.loading = true;
650       this.startLoadingTimer_();
651     },
653     /**
654      * Updates localized content of the screen that is not updated via template.
655      */
656     updateLocalizedContent: function() {
657       $('createAccount').innerHTML = loadTimeData.getStringF(
658           'createAccount',
659           '<a id="createAccountLink" class="signin-link" href="#">',
660           '</a>');
661       $('guestSignin').innerHTML = loadTimeData.getStringF(
662           'guestSignin',
663           '<a id="guestSigninLink" class="signin-link" href="#">',
664           '</a>');
665       $('createSupervisedUserLinkPlaceholder').innerHTML =
666           loadTimeData.getStringF(
667               'createSupervisedUser',
668               '<a id="createSupervisedUserLink" class="signin-link" href="#">',
669               '</a>');
670       $('consumerManagementEnrollment').innerHTML = loadTimeData.getString(
671           'consumerManagementEnrollmentSigninMessage');
672       $('createAccountLink').addEventListener('click', function(e) {
673         chrome.send('createAccount');
674         e.preventDefault();
675       });
676       $('guestSigninLink').addEventListener('click', function(e) {
677         chrome.send('launchIncognito');
678         e.preventDefault();
679       });
680       $('createSupervisedUserLink').addEventListener('click', function(e) {
681         chrome.send('showSupervisedUserCreationScreen');
682         e.preventDefault();
683       });
684     },
686     /**
687      * Shows sign-in error bubble.
688      * @param {number} loginAttempts Number of login attemps tried.
689      * @param {HTMLElement} content Content to show in bubble.
690      */
691     showErrorBubble: function(loginAttempts, error) {
692       if (this.isLocal) {
693         $('add-user-button').hidden = true;
694         $('cancel-add-user-button').hidden = false;
695         // Reload offline version of the sign-in extension, which will show
696         // error itself.
697         chrome.send('offlineLogin', [this.email]);
698       } else if (!this.loading) {
699         // We want to show bubble near "Email" field, but we can't calculate
700         // it's position because it is located inside iframe. So we only
701         // can hardcode some constants.
702         /** @const */ var ERROR_BUBBLE_OFFSET = 84;
703         /** @const */ var ERROR_BUBBLE_PADDING = 0;
704         $('bubble').showContentForElement($('login-box'),
705                                           cr.ui.Bubble.Attachment.LEFT,
706                                           error,
707                                           ERROR_BUBBLE_OFFSET,
708                                           ERROR_BUBBLE_PADDING);
709       } else {
710         // Defer the bubble until the frame has been loaded.
711         this.errorBubble_ = [loginAttempts, error];
712       }
713     },
715     /**
716      * Called when user canceled signin.
717      */
718     cancel: function() {
719       if (!this.cancelAllowed_) {
720         // In OOBE signin screen, cancel is not allowed because there is
721         // no other screen to show. If user is in middle of a saml flow,
722         // reset signin screen to get out of the saml flow.
723         if (this.isSAML())
724           Oobe.resetSigninUI(true);
726         return;
727       }
729       $('pod-row').loadLastWallpaper();
730       Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
731       Oobe.resetSigninUI(true);
732     },
734     /**
735      * Handler for iframe's error notification coming from the outside.
736      * For more info see C++ class 'WebUILoginView' which calls this method.
737      * @param {number} error Error code.
738      * @param {string} url The URL that failed to load.
739      */
740     onFrameError: function(error, url) {
741       this.error_ = error;
742       chrome.send('frameLoadingCompleted', [this.error_]);
743     },
744   };