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',
25 'updateCancelButtonState',
30 * Frame loading error code (0 - no error).
37 * Saved gaia auth host load params.
41 gaiaAuthParams_: null,
44 * Whether local version of Gaia page is used.
51 * Whether MinuteMaid flow is active.
57 * Email of the user, which is logging in using offline mode.
63 * Whether consumer management enrollment is in progress.
67 isEnrollingConsumerManagement_: false,
70 * Timer id of pending load.
74 loadingTimer_: undefined,
77 * Whether user can cancel Gaia screen.
81 cancelAllowed_: undefined,
84 * Whether we should show user pods on the login screen.
88 isShowUsers_: undefined,
91 * SAML password confirmation attempt count.
94 samlPasswordConfirmAttempt_: 0,
97 * Whether we should show webview based signin.
101 isWebviewSignin: false,
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);
115 this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
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]);
143 $('close-button-item').addEventListener('click', function(e) {
148 this.updateLocalizedContent();
152 * Header text of the screen.
156 return loadTimeData.getString('signinScreenTitle');
160 * Returns true if local version of Gaia is used.
164 return this.isLocal_;
168 * Sets whether local version of Gaia is used.
169 * @param {boolean} value Whether local version of Gaia is used.
172 this.isLocal_ = value;
173 chrome.send('updateOfflineLogin', [value]);
177 * Shows/hides loading UI.
178 * @param {boolean} show True to show loading UI.
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);
191 * Handler for Gaia loading suspiciously long timeout.
194 onLoadingSuspiciouslyLong_: function() {
195 if (this != Oobe.getInstance().currentScreen)
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) *
205 * Handler for Gaia loading timeout.
208 onLoadingTimeOut_: function() {
209 this.loadingTimer_ = undefined;
210 chrome.send('showLoadingTimeoutError');
214 * Clears loading timer.
217 clearLoadingTimer_: function() {
218 if (this.loadingTimer_) {
219 window.clearTimeout(this.loadingTimer_);
220 this.loadingTimer_ = undefined;
225 * Sets up loading timer.
228 startLoadingTimer_: function() {
229 this.clearLoadingTimer_();
230 this.loadingTimer_ = window.setTimeout(
231 this.onLoadingSuspiciouslyLong_.bind(this),
232 GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
236 * Whether Gaia is loading.
240 return !$('gaia-loading').hidden;
242 set loading(loading) {
243 if (loading == this.loading)
246 this.showLoadingUI_(loading);
250 * Event handler that is invoked just before the frame is shown.
251 * @param {string} data Screen init payload. Url of auth extension start
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']);
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;
273 onAfterShow: function(data) {
274 if (!this.loading && this.isWebviewSignin)
275 $('signin-frame').focus();
279 * Event handler that is invoked just before the screen is hidden.
281 onBeforeHide: function() {
282 chrome.send('loginUIStateChanged', ['gaia-signin', false]);
283 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
287 * Loads the authentication extension into the iframe.
288 * @param {Object} data Extension parameters bag.
291 loadAuthExtension: function(data) {
292 this.isLocal = data.isLocal;
294 this.isMinuteMaid = data.useMinuteMaid;
297 this.classList.toggle('full-width', false);
298 this.samlPasswordConfirmAttempt_ = 0;
300 this.updateAuthExtension(data);
303 for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
304 var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
306 params[name] = data[name];
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;
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)) {
335 var authMode = cr.login.GaiaAuthHost.AuthMode.DEFAULT;
337 authMode = cr.login.GaiaAuthHost.AuthMode.OFFLINE;
338 else if (data.useEmbedded)
339 authMode = cr.login.GaiaAuthHost.AuthMode.DESKTOP;
341 this.gaiaAuthHost_.load(authMode,
343 this.onAuthCompleted_.bind(this));
344 this.gaiaAuthParams_ = params;
347 this.startLoadingTimer_();
348 } else if (this.loading && this.error_) {
349 // An error has occurred, so trying to reload.
355 * Updates the authentication extension with new parameters, if needed.
356 * @param {Object} data New extension parameters bag.
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;
366 reasonLabel.hidden = true;
369 if (this.isMinuteMaid) {
370 $('login-header-bar').showCreateSupervisedButton =
371 data.supervisedUsersCanCreate;
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;
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);
406 * Updates [Cancel] button state. Allow cancellation of screen only when
407 * user pods can be displayed.
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_;
416 switchToFullTab: function() {
417 this.classList.toggle('no-right-panel', true);
418 this.classList.toggle('full-width', true);
422 * Whether the current auth flow is SAML.
425 return this.gaiaAuthHost_.authFlow ==
426 cr.login.GaiaAuthHost.AuthFlow.SAML;
430 * Invoked when the authFlow property is changed no the gaia host.
431 * @param {Event} e Property change event.
433 onAuthFlowChange_: function(e) {
434 var isSAML = this.isSAML();
437 $('saml-notice-message').textContent = loadTimeData.getStringF(
439 this.gaiaAuthHost_.authDomain);
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_);
455 * Invoked when the auth host emits 'ready' event.
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;
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});
476 * Invoked when the auth host emits 'dialogShown' event.
479 onDialogShown_: function() {
480 $('close-button-item').disabled = true;
484 * Invoked when the auth host emits 'dialogHidden' event.
487 onDialogHidden_: function() {
488 $('close-button-item').disabled = false;
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.
498 onAuthConfirmPassword_: function(passwordCount) {
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));
510 chrome.send('scrapedPasswordVerificationFailed');
511 this.showFatalAuthError(
512 loadTimeData.getString('fatalErrorMessageVerificationFailed'));
517 * Invoked when the confirm password screen is dismissed.
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});
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.
533 onAuthNoPassword_: function(email) {
534 this.showFatalAuthError(loadTimeData.getString(
535 'fatalErrorMessageNoPassword'));
536 chrome.send('scrapedPasswordCount', [0]);
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.
546 onInsecureContentBlocked_: function(url) {
547 this.showFatalAuthError(loadTimeData.getStringF(
548 'fatalErrorMessageInsecureURL',
553 * Shows the fatal auth error.
554 * @param {string} message The error message to show.
556 showFatalAuthError: function(message) {
557 login.FatalErrorScreen.show(message, Oobe.showSigninUI);
561 * Show fatal auth error when information is missing from GAIA.
563 missingGaiaInfo_: function() {
564 this.showFatalAuthError(
565 loadTimeData.getString('fatalErrorMessageNoAccountDetails'));
569 * Record that SAML API was used during sign-in.
571 samlApiUsed_: function() {
572 chrome.send('usingSAMLAPI');
576 * Invoked when auth is completed successfully.
577 * @param {!Object} credentials Credentials of the completed authentication.
580 onAuthCompleted_: function(credentials) {
581 if (credentials.useOffline) {
582 this.email = credentials.email;
583 chrome.send('authenticateUser',
585 credentials.password]);
586 } else if (credentials.authCode) {
587 if (credentials.hasOwnProperty('authCodeOnly') &&
588 credentials.authCodeOnly) {
589 chrome.send('completeAuthenticationAuthCodeOnly',
590 [credentials.authCode]);
592 chrome.send('completeAuthentication',
595 credentials.password,
596 credentials.authCode]);
599 chrome.send('completeLogin',
602 credentials.password,
603 credentials.usingSAML]);
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.
614 * Invoked when onAuthCompleted message received.
615 * @param {!Object} e Payload of the received HTML5 message.
618 onAuthCompletedMessage_: function(e) {
619 this.onAuthCompleted_(e.detail);
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.
628 reset: function(takeFocus, forceOnline) {
629 // Reload and show the sign-in UI if needed.
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.
644 * Reloads extension frame.
646 doReload: function() {
648 this.gaiaAuthHost_.reload();
650 this.startLoadingTimer_();
654 * Updates localized content of the screen that is not updated via template.
656 updateLocalizedContent: function() {
657 $('createAccount').innerHTML = loadTimeData.getStringF(
659 '<a id="createAccountLink" class="signin-link" href="#">',
661 $('guestSignin').innerHTML = loadTimeData.getStringF(
663 '<a id="guestSigninLink" class="signin-link" href="#">',
665 $('createSupervisedUserLinkPlaceholder').innerHTML =
666 loadTimeData.getStringF(
667 'createSupervisedUser',
668 '<a id="createSupervisedUserLink" class="signin-link" href="#">',
670 $('consumerManagementEnrollment').innerHTML = loadTimeData.getString(
671 'consumerManagementEnrollmentSigninMessage');
672 $('createAccountLink').addEventListener('click', function(e) {
673 chrome.send('createAccount');
676 $('guestSigninLink').addEventListener('click', function(e) {
677 chrome.send('launchIncognito');
680 $('createSupervisedUserLink').addEventListener('click', function(e) {
681 chrome.send('showSupervisedUserCreationScreen');
687 * Shows sign-in error bubble.
688 * @param {number} loginAttempts Number of login attemps tried.
689 * @param {HTMLElement} content Content to show in bubble.
691 showErrorBubble: function(loginAttempts, error) {
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
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,
708 ERROR_BUBBLE_PADDING);
710 // Defer the bubble until the frame has been loaded.
711 this.errorBubble_ = [loginAttempts, error];
716 * Called when user canceled signin.
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.
724 Oobe.resetSigninUI(true);
729 $('pod-row').loadLastWallpaper();
730 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
731 Oobe.resetSigninUI(true);
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.
740 onFrameError: function(error, url) {
742 chrome.send('frameLoadingCompleted', [this.error_]);