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.
6 * @fileoverview Oobe signin screen implementation.
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;
22 'updateAuthExtension',
26 'updateCancelButtonState',
27 'showWhitelistCheckFailedError'
31 * Frame loading error code (0 - no error).
38 * Saved gaia auth host load params.
42 gaiaAuthParams_: null,
45 * Whether local version of Gaia page is used.
52 * Whether new Gaia flow is active.
58 * Email of the user, which is logging in using offline mode.
64 * Whether consumer management enrollment is in progress.
68 isEnrollingConsumerManagement_: false,
71 * Timer id of pending load.
75 loadingTimer_: undefined,
78 * Whether user can cancel Gaia screen.
82 cancelAllowed_: undefined,
85 * Whether we should show user pods on the login screen.
89 isShowUsers_: undefined,
92 * SAML password confirmation attempt count.
95 samlPasswordConfirmAttempt_: 0,
98 * Whether we should show webview based signin.
102 isWebviewSignin: false,
105 decorate: function() {
106 this.isWebviewSignin = loadTimeData.getValue('isWebviewSignin');
107 if (this.isWebviewSignin) {
108 // Replace iframe with webview.
109 var webview = this.ownerDocument.createElement('webview');
110 webview.id = 'signin-frame';
111 webview.name = 'signin-frame';
112 webview.hidden = true;
113 $('signin-frame').parentNode.replaceChild(webview, $('signin-frame'));
114 this.gaiaAuthHost_ = new cr.login.GaiaAuthHost(webview);
116 this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
118 this.gaiaAuthHost_.addEventListener(
119 'ready', this.onAuthReady_.bind(this));
120 this.gaiaAuthHost_.addEventListener(
121 'dialogShown', this.onDialogShown_.bind(this));
122 this.gaiaAuthHost_.addEventListener(
123 'dialogHidden', this.onDialogHidden_.bind(this));
124 this.gaiaAuthHost_.addEventListener(
125 'backButton', this.onBackButton_.bind(this));
126 this.gaiaAuthHost_.addEventListener(
127 'showView', this.onShowView_.bind(this));
128 this.gaiaAuthHost_.confirmPasswordCallback =
129 this.onAuthConfirmPassword_.bind(this);
130 this.gaiaAuthHost_.noPasswordCallback =
131 this.onAuthNoPassword_.bind(this);
132 this.gaiaAuthHost_.insecureContentBlockedCallback =
133 this.onInsecureContentBlocked_.bind(this);
134 this.gaiaAuthHost_.missingGaiaInfoCallback =
135 this.missingGaiaInfo_.bind(this);
136 this.gaiaAuthHost_.samlApiUsedCallback =
137 this.samlApiUsed_.bind(this);
138 this.gaiaAuthHost_.addEventListener('authFlowChange',
139 this.onAuthFlowChange_.bind(this));
140 this.gaiaAuthHost_.addEventListener('authCompleted',
141 this.onAuthCompletedMessage_.bind(this));
142 this.gaiaAuthHost_.addEventListener('loadAbort',
143 this.onLoadAbortMessage_.bind(this));
145 $('enterprise-info-hint-link').addEventListener('click', function(e) {
146 chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
150 $('back-button-item').addEventListener('click', function(e) {
151 $('back-button-item').hidden = true;
152 $('signin-frame').back();
155 $('close-button-item').addEventListener('click', function(e) {
160 this.updateLocalizedContent();
164 * Header text of the screen.
168 return loadTimeData.getString('signinScreenTitle');
172 * Returns true if local version of Gaia is used.
176 return this.isLocal_;
180 * Sets whether local version of Gaia is used.
181 * @param {boolean} value Whether local version of Gaia is used.
184 this.isLocal_ = value;
185 chrome.send('updateOfflineLogin', [value]);
189 * Shows/hides loading UI.
190 * @param {boolean} show True to show loading UI.
193 showLoadingUI_: function(show) {
194 $('gaia-loading').hidden = !show;
195 $('signin-frame').hidden = show;
196 $('signin-right').hidden = show;
197 $('enterprise-info-container').hidden = show;
198 $('gaia-signin-divider').hidden = show;
199 this.classList.toggle('loading', show);
201 this.classList.remove('auth-completed');
205 * Handler for Gaia loading suspiciously long timeout.
208 onLoadingSuspiciouslyLong_: function() {
209 if (this != Oobe.getInstance().currentScreen)
211 chrome.send('showLoadingTimeoutError');
212 this.loadingTimer_ = window.setTimeout(
213 this.onLoadingTimeOut_.bind(this),
214 (MAX_GAIA_LOADING_TIME_SEC - GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC) *
219 * Handler for Gaia loading timeout.
222 onLoadingTimeOut_: function() {
223 this.loadingTimer_ = undefined;
224 chrome.send('showLoadingTimeoutError');
228 * Clears loading timer.
231 clearLoadingTimer_: function() {
232 if (this.loadingTimer_) {
233 window.clearTimeout(this.loadingTimer_);
234 this.loadingTimer_ = undefined;
239 * Sets up loading timer.
242 startLoadingTimer_: function() {
243 this.clearLoadingTimer_();
244 this.loadingTimer_ = window.setTimeout(
245 this.onLoadingSuspiciouslyLong_.bind(this),
246 GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
250 * Whether Gaia is loading.
254 return !$('gaia-loading').hidden;
256 set loading(loading) {
257 if (loading == this.loading)
260 this.showLoadingUI_(loading);
264 * Event handler that is invoked just before the frame is shown.
265 * @param {string} data Screen init payload. Url of auth extension start
268 onBeforeShow: function(data) {
269 chrome.send('loginUIStateChanged', ['gaia-signin', true]);
270 $('login-header-bar').signinUIState =
271 this.isEnrollingConsumerManagement_ ?
272 SIGNIN_UI_STATE.CONSUMER_MANAGEMENT_ENROLLMENT :
273 SIGNIN_UI_STATE.GAIA_SIGNIN;
275 // Ensure that GAIA signin (or loading UI) is actually visible.
276 window.requestAnimationFrame(function() {
277 chrome.send('loginVisible', ['gaia-loading']);
279 $('back-button-item').disabled = false;
280 $('back-button-item').hidden = true;
281 $('close-button-item').disabled = false;
282 this.classList.toggle('loading', this.loading);
284 // Button header is always visible when sign in is presented.
285 // Header is hidden once GAIA reports on successful sign in.
286 Oobe.getInstance().headerHidden = false;
289 onAfterShow: function(data) {
290 if (!this.loading && this.isWebviewSignin)
291 $('signin-frame').focus();
295 * Event handler that is invoked just before the screen is hidden.
297 onBeforeHide: function() {
298 chrome.send('loginUIStateChanged', ['gaia-signin', false]);
299 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
303 * Loads the authentication extension into the iframe.
304 * @param {Object} data Extension parameters bag.
307 loadAuthExtension: function(data) {
308 this.isLocal = data.isLocal;
310 this.isNewGaiaFlow = data.useNewGaiaFlow;
313 this.classList.toggle('full-width', false);
314 this.samlPasswordConfirmAttempt_ = 0;
316 this.updateAuthExtension(data);
319 for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
320 var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
322 params[name] = data[name];
325 if (data.localizedStrings)
326 params.localizedStrings = data.localizedStrings;
328 if (this.isNewGaiaFlow) {
329 $('inner-container').classList.add('new-gaia-flow');
330 $('progress-dots').hidden = true;
331 if (data.enterpriseDomain)
332 params.enterpriseDomain = data.enterpriseDomain;
333 params.chromeType = data.chromeType;
334 params.isNewGaiaFlowChromeOS = true;
335 $('login-header-bar').showGuestButton = true;
338 if (data.gaiaEndpoint)
339 params.gaiaPath = data.gaiaEndpoint;
341 $('login-header-bar').newGaiaFlow = this.isNewGaiaFlow;
343 if (data.forceReload ||
344 JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
347 var authMode = cr.login.GaiaAuthHost.AuthMode.DEFAULT;
349 authMode = cr.login.GaiaAuthHost.AuthMode.OFFLINE;
351 this.gaiaAuthHost_.load(authMode,
353 this.onAuthCompleted_.bind(this));
354 this.gaiaAuthParams_ = params;
357 this.startLoadingTimer_();
358 } else if (this.loading && this.error_) {
359 // An error has occurred, so trying to reload.
365 * Updates the authentication extension with new parameters, if needed.
366 * @param {Object} data New extension parameters bag.
369 updateAuthExtension: function(data) {
370 var reasonLabel = $('gaia-signin-reason');
371 if (data.passwordChanged) {
372 reasonLabel.textContent =
373 loadTimeData.getString('signinScreenPasswordChanged');
374 reasonLabel.hidden = false;
376 reasonLabel.hidden = true;
379 if (this.isNewGaiaFlow) {
380 $('login-header-bar').showCreateSupervisedButton =
381 data.supervisedUsersCanCreate;
383 $('createAccount').hidden = !data.createAccount;
384 $('guestSignin').hidden = !data.guestSignin;
385 $('createSupervisedUserPane').hidden = !data.supervisedUsersEnabled;
387 $('createSupervisedUserLinkPlaceholder').hidden =
388 !data.supervisedUsersCanCreate;
389 $('createSupervisedUserNoManagerText').hidden =
390 data.supervisedUsersCanCreate;
391 $('createSupervisedUserNoManagerText').textContent =
392 data.supervisedUsersRestrictionReason;
395 var isEnrollingConsumerManagement = data.isEnrollingConsumerManagement;
396 $('consumerManagementEnrollment').hidden = !isEnrollingConsumerManagement;
398 this.isShowUsers_ = data.isShowUsers;
399 this.updateCancelButtonState();
401 this.isEnrollingConsumerManagement_ = isEnrollingConsumerManagement;
403 // Sign-in right panel is hidden if all of its items are hidden.
404 var noRightPanel = $('gaia-signin-reason').hidden &&
405 $('createAccount').hidden &&
406 $('guestSignin').hidden &&
407 $('createSupervisedUserPane').hidden &&
408 $('consumerManagementEnrollment').hidden;
409 this.classList.toggle('no-right-panel', noRightPanel);
410 this.classList.toggle('full-width', false);
411 if (Oobe.getInstance().currentScreen === this)
412 Oobe.getInstance().updateScreenSize(this);
416 * Updates [Cancel] button state. Allow cancellation of screen only when
417 * user pods can be displayed.
419 updateCancelButtonState: function() {
420 this.cancelAllowed_ = this.isShowUsers_ && $('pod-row').pods.length;
421 $('login-header-bar').allowCancel = this.cancelAllowed_;
422 if (this.isNewGaiaFlow)
423 $('close-button-item').hidden = !this.cancelAllowed_;
427 * Whether the current auth flow is SAML.
430 return this.gaiaAuthHost_.authFlow ==
431 cr.login.GaiaAuthHost.AuthFlow.SAML;
435 * Invoked when the authFlow property is changed no the gaia host.
437 onAuthFlowChange_: function() {
438 var isSAML = this.isSAML();
441 $('saml-notice-message').textContent = loadTimeData.getStringF(
443 this.gaiaAuthHost_.authDomain);
446 this.classList.toggle('no-right-panel', isSAML);
447 this.classList.toggle('full-width', isSAML);
448 $('saml-notice-container').hidden = !isSAML;
450 if (Oobe.getInstance().currentScreen === this) {
451 Oobe.getInstance().updateScreenSize(this);
452 $('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
453 if (this.isNewGaiaFlow)
454 $('close-button-item').hidden = !(isSAML || this.cancelAllowed_);
459 * Invoked when the auth host emits 'ready' event.
462 onAuthReady_: function() {
463 this.loading = false;
464 this.clearLoadingTimer_();
466 // Show deferred error bubble.
467 if (this.errorBubble_) {
468 this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
469 this.errorBubble_ = undefined;
472 chrome.send('loginWebuiReady');
473 chrome.send('loginVisible', ['gaia-signin']);
475 // Warm up the user images screen.
476 Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
480 * Invoked when the auth host emits 'dialogShown' event.
483 onDialogShown_: function() {
484 $('back-button-item').disabled = true;
485 $('close-button-item').disabled = true;
489 * Invoked when the auth host emits 'dialogHidden' event.
492 onDialogHidden_: function() {
493 $('back-button-item').disabled = false;
494 $('close-button-item').disabled = false;
498 * Invoked when the auth host emits 'backButton' event.
501 onBackButton_: function(e) {
502 $('back-button-item').hidden = !e.detail;
506 * Invoked when the auth host emits 'showView' event.
509 onShowView_: function(e) {
510 $('signin-frame').classList.add('show');
514 * Invoked when the user has successfully authenticated via SAML, the
515 * principals API was not used and the auth host needs the user to confirm
516 * the scraped password.
517 * @param {number} passwordCount The number of passwords that were scraped.
520 onAuthConfirmPassword_: function(passwordCount) {
522 Oobe.getInstance().headerHidden = false;
524 if (this.samlPasswordConfirmAttempt_ == 0)
525 chrome.send('scrapedPasswordCount', [passwordCount]);
527 if (this.samlPasswordConfirmAttempt_ < 2) {
528 login.ConfirmPasswordScreen.show(
529 this.samlPasswordConfirmAttempt_,
530 this.onConfirmPasswordCollected_.bind(this));
532 chrome.send('scrapedPasswordVerificationFailed');
533 this.showFatalAuthError(
534 loadTimeData.getString('fatalErrorMessageVerificationFailed'));
539 * Invoked when the confirm password screen is dismissed.
542 onConfirmPasswordCollected_: function(password) {
543 this.samlPasswordConfirmAttempt_++;
544 this.gaiaAuthHost_.verifyConfirmedPassword(password);
546 // Shows signin UI again without changing states.
547 Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
551 * Inovked when the user has successfully authenticated via SAML, the
552 * principals API was not used and no passwords could be scraped.
553 * @param {string} email The authenticated user's e-mail.
555 onAuthNoPassword_: function(email) {
556 this.showFatalAuthError(loadTimeData.getString(
557 'fatalErrorMessageNoPassword'));
558 chrome.send('scrapedPasswordCount', [0]);
562 * Invoked when the authentication flow had to be aborted because content
563 * served over an unencrypted connection was detected. Shows a fatal error.
564 * This method is only called on Chrome OS, where the entire authentication
565 * flow is required to be encrypted.
566 * @param {string} url The URL that was blocked.
568 onInsecureContentBlocked_: function(url) {
569 this.showFatalAuthError(loadTimeData.getStringF(
570 'fatalErrorMessageInsecureURL',
575 * Shows the fatal auth error.
576 * @param {string} message The error message to show.
578 showFatalAuthError: function(message) {
579 login.FatalErrorScreen.show(message, Oobe.showSigninUI);
583 * Show fatal auth error when information is missing from GAIA.
585 missingGaiaInfo_: function() {
586 this.showFatalAuthError(
587 loadTimeData.getString('fatalErrorMessageNoAccountDetails'));
591 * Record that SAML API was used during sign-in.
593 samlApiUsed_: function() {
594 chrome.send('usingSAMLAPI');
598 * Invoked when auth is completed successfully.
599 * @param {!Object} credentials Credentials of the completed authentication.
602 onAuthCompleted_: function(credentials) {
603 if (credentials.useOffline) {
604 this.email = credentials.email;
605 chrome.send('authenticateUser',
607 credentials.password]);
608 } else if (credentials.authCode) {
609 if (credentials.hasOwnProperty('authCodeOnly') &&
610 credentials.authCodeOnly) {
611 chrome.send('completeAuthenticationAuthCodeOnly',
612 [credentials.authCode]);
614 chrome.send('completeAuthentication',
617 credentials.password,
618 credentials.authCode,
619 credentials.usingSAML]);
622 chrome.send('completeLogin',
625 credentials.password,
626 credentials.usingSAML]);
630 this.classList.add('auth-completed');
631 // Now that we're in logged in state header should be hidden.
632 Oobe.getInstance().headerHidden = true;
633 // Clear any error messages that were shown before login.
638 * Invoked when onAuthCompleted message received.
639 * @param {!Object} e Payload of the received HTML5 message.
642 onAuthCompletedMessage_: function(e) {
643 this.onAuthCompleted_(e.detail);
647 * Invoked when onLoadAbort message received.
648 * @param {!Object} e Payload of the received HTML5 message.
651 onLoadAbortMessage_: function(e) {
652 this.onWebviewError(e.detail);
656 * Clears input fields and switches to input mode.
657 * @param {boolean} takeFocus True to take focus.
658 * @param {boolean} forceOnline Whether online sign-in should be forced.
659 * If |forceOnline| is false previously used sign-in type will be used.
661 reset: function(takeFocus, forceOnline) {
662 // Reload and show the sign-in UI if needed.
664 if (!forceOnline && this.isLocal) {
665 // Show 'Cancel' button to allow user to return to the main screen
666 // (e.g. this makes sense when connection is back).
667 Oobe.getInstance().headerHidden = false;
668 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
669 // Do nothing, since offline version is reloaded after an error comes.
677 * Reloads extension frame.
679 doReload: function() {
681 this.gaiaAuthHost_.reload();
683 this.startLoadingTimer_();
687 * Updates localized content of the screen that is not updated via template.
689 updateLocalizedContent: function() {
690 $('createAccount').innerHTML = loadTimeData.getStringF(
692 '<a id="createAccountLink" class="signin-link" href="#">',
694 $('guestSignin').innerHTML = loadTimeData.getStringF(
696 '<a id="guestSigninLink" class="signin-link" href="#">',
698 $('createSupervisedUserLinkPlaceholder').innerHTML =
699 loadTimeData.getStringF(
700 'createSupervisedUser',
701 '<a id="createSupervisedUserLink" class="signin-link" href="#">',
703 $('consumerManagementEnrollment').innerHTML = loadTimeData.getString(
704 'consumerManagementEnrollmentSigninMessage');
705 $('createAccountLink').addEventListener('click', function(e) {
706 chrome.send('createAccount');
709 $('guestSigninLink').addEventListener('click', function(e) {
710 chrome.send('launchIncognito');
713 $('createSupervisedUserLink').addEventListener('click', function(e) {
714 chrome.send('showSupervisedUserCreationScreen');
720 * Shows sign-in error bubble.
721 * @param {number} loginAttempts Number of login attemps tried.
722 * @param {HTMLElement} content Content to show in bubble.
724 showErrorBubble: function(loginAttempts, error) {
726 $('add-user-button').hidden = true;
727 $('cancel-add-user-button').hidden = false;
728 // Reload offline version of the sign-in extension, which will show
730 chrome.send('offlineLogin', [this.email]);
731 } else if (!this.loading) {
732 // We want to show bubble near "Email" field, but we can't calculate
733 // it's position because it is located inside iframe. So we only
734 // can hardcode some constants.
735 /** @const */ var ERROR_BUBBLE_OFFSET = 84;
736 /** @const */ var ERROR_BUBBLE_PADDING = 0;
737 $('bubble').showContentForElement($('login-box'),
738 cr.ui.Bubble.Attachment.LEFT,
741 ERROR_BUBBLE_PADDING);
743 // Defer the bubble until the frame has been loaded.
744 this.errorBubble_ = [loginAttempts, error];
749 * Called when user canceled signin.
752 if (!this.cancelAllowed_) {
753 // In OOBE signin screen, cancel is not allowed because there is
754 // no other screen to show. If user is in middle of a saml flow,
755 // reset signin screen to get out of the saml flow.
757 Oobe.resetSigninUI(true);
762 $('pod-row').loadLastWallpaper();
763 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
764 this.classList.remove('whitelist-error');
765 Oobe.resetSigninUI(true);
769 * Handler for iframe's error notification coming from the outside.
770 * For more info see C++ class 'WebUILoginView' which calls this method.
771 * @param {number} error Error code.
772 * @param {string} url The URL that failed to load.
774 onFrameError: function(error, url) {
776 chrome.send('frameLoadingCompleted', [this.error_]);
780 * Handler for webview error handling.
781 * @param {!Object} data Additional information about error event like:
782 * {string} error Error code such as "ERR_INTERNET_DISCONNECTED".
783 * {string} url The URL that failed to load.
785 onWebviewError: function(data) {
786 chrome.send('webviewLoadAborted', [data.error]);
790 * Show/Hide error when user is not in whitelist. When UI is hidden
792 * @param {boolean} show Show/hide error UI.
793 * @param {!Object} opt_data Optional additional information.
795 showWhitelistCheckFailedError: function(show, opt_data) {
797 $('gaia-whitelist-error').enterpriseManaged =
798 opt_data.enterpriseManaged;
801 this.classList.toggle('whitelist-error', show);
802 this.loading = !show;