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 <include src="../../gaia_auth_host/gaia_auth_host.js">
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;
16 // Maximum Gaia loading time in seconds.
17 /** @const */ var MAX_GAIA_LOADING_TIME_SEC = 60;
19 /** @const */ var HELP_TOPIC_ENTERPRISE_REPORTING = 2535613;
24 'updateAuthExtension',
27 'updateCancelButtonState'
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 * Email of the user, which is logging in using offline mode.
58 * Whether consumer management enrollment is in progress.
62 isEnrollingConsumerManagement_: false,
65 * Timer id of pending load.
69 loadingTimer_: undefined,
72 * Whether user can cancel Gaia screen.
76 cancelAllowed_: undefined,
79 * Whether we should show user pods on the login screen.
83 isShowUsers_: undefined,
86 * SAML password confirmation attempt count.
89 samlPasswordConfirmAttempt_: 0,
92 decorate: function() {
93 this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
94 this.gaiaAuthHost_.addEventListener(
95 'ready', this.onAuthReady_.bind(this));
96 this.gaiaAuthHost_.confirmPasswordCallback =
97 this.onAuthConfirmPassword_.bind(this);
98 this.gaiaAuthHost_.noPasswordCallback =
99 this.onAuthNoPassword_.bind(this);
100 this.gaiaAuthHost_.insecureContentBlockedCallback =
101 this.onInsecureContentBlocked_.bind(this);
102 this.gaiaAuthHost_.missingGaiaInfoCallback =
103 this.missingGaiaInfo_.bind(this);
104 this.gaiaAuthHost_.samlApiUsedCallback =
105 this.samlApiUsed_.bind(this);
106 this.gaiaAuthHost_.addEventListener('authFlowChange',
107 this.onAuthFlowChange_.bind(this));
109 $('enterprise-info-hint-link').addEventListener('click', function(e) {
110 chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
115 this.updateLocalizedContent();
119 * Header text of the screen.
123 return loadTimeData.getString('signinScreenTitle');
127 * Returns true if local version of Gaia is used.
131 return this.isLocal_;
135 * Sets whether local version of Gaia is used.
136 * @param {boolean} value Whether local version of Gaia is used.
139 this.isLocal_ = value;
140 chrome.send('updateOfflineLogin', [value]);
144 * Shows/hides loading UI.
145 * @param {boolean} show True to show loading UI.
148 showLoadingUI_: function(show) {
149 $('gaia-loading').hidden = !show;
150 this.gaiaAuthHost_.frame.hidden = show;
151 $('signin-right').hidden = show;
152 $('enterprise-info-container').hidden = show;
153 $('gaia-signin-divider').hidden = show;
157 * Handler for Gaia loading suspiciously long timeout.
160 onLoadingSuspiciouslyLong_: function() {
161 if (this != Oobe.getInstance().currentScreen)
163 chrome.send('showLoadingTimeoutError');
164 this.loadingTimer_ = window.setTimeout(
165 this.onLoadingTimeOut_.bind(this),
166 (MAX_GAIA_LOADING_TIME_SEC - GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC) *
171 * Handler for Gaia loading timeout.
174 onLoadingTimeOut_: function() {
175 this.loadingTimer_ = undefined;
176 chrome.send('showLoadingTimeoutError');
180 * Clears loading timer.
183 clearLoadingTimer_: function() {
184 if (this.loadingTimer_) {
185 window.clearTimeout(this.loadingTimer_);
186 this.loadingTimer_ = undefined;
191 * Sets up loading timer.
194 startLoadingTimer_: function() {
195 this.clearLoadingTimer_();
196 this.loadingTimer_ = window.setTimeout(
197 this.onLoadingSuspiciouslyLong_.bind(this),
198 GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
202 * Whether Gaia is loading.
206 return !$('gaia-loading').hidden;
208 set loading(loading) {
209 if (loading == this.loading)
212 this.showLoadingUI_(loading);
216 * Event handler that is invoked just before the frame is shown.
217 * @param {string} data Screen init payload. Url of auth extension start
220 onBeforeShow: function(data) {
221 chrome.send('loginUIStateChanged', ['gaia-signin', true]);
222 $('login-header-bar').signinUIState =
223 this.isEnrollingConsumerManagement_ ?
224 SIGNIN_UI_STATE.CONSUMER_MANAGEMENT_ENROLLMENT :
225 SIGNIN_UI_STATE.GAIA_SIGNIN;
227 // Ensure that GAIA signin (or loading UI) is actually visible.
228 window.requestAnimationFrame(function() {
229 chrome.send('loginVisible', ['gaia-loading']);
232 // Button header is always visible when sign in is presented.
233 // Header is hidden once GAIA reports on successful sign in.
234 Oobe.getInstance().headerHidden = false;
238 * Event handler that is invoked just before the screen is hidden.
240 onBeforeHide: function() {
241 chrome.send('loginUIStateChanged', ['gaia-signin', false]);
242 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
246 * Loads the authentication extension into the iframe.
247 * @param {Object} data Extension parameters bag.
250 loadAuthExtension: function(data) {
251 this.isLocal = data.isLocal;
255 this.classList.toggle('saml', false);
256 this.samlPasswordConfirmAttempt_ = 0;
258 this.updateAuthExtension(data);
261 for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
262 var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
264 params[name] = data[name];
267 if (data.localizedStrings)
268 params.localizedStrings = data.localizedStrings;
270 if (data.useEmbedded)
271 params.gaiaPath = 'EmbeddedSignIn';
273 if (data.forceReload ||
274 JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
277 var authMode = cr.login.GaiaAuthHost.AuthMode.DEFAULT;
279 authMode = cr.login.GaiaAuthHost.AuthMode.OFFLINE;
280 else if (data.useEmbedded)
281 authMode = cr.login.GaiaAuthHost.AuthMode.DESKTOP;
283 this.gaiaAuthHost_.load(authMode,
285 this.onAuthCompleted_.bind(this));
286 this.gaiaAuthParams_ = params;
289 this.startLoadingTimer_();
290 } else if (this.loading && this.error_) {
291 // An error has occurred, so trying to reload.
297 * Updates the authentication extension with new parameters, if needed.
298 * @param {Object} data New extension parameters bag.
301 updateAuthExtension: function(data) {
302 var reasonLabel = $('gaia-signin-reason');
303 if (data.passwordChanged) {
304 reasonLabel.textContent =
305 loadTimeData.getString('signinScreenPasswordChanged');
306 reasonLabel.hidden = false;
308 reasonLabel.hidden = true;
311 $('createAccount').hidden = !data.createAccount;
312 $('guestSignin').hidden = !data.guestSignin;
313 $('createSupervisedUserPane').hidden = !data.supervisedUsersEnabled;
315 $('createSupervisedUserLinkPlaceholder').hidden =
316 !data.supervisedUsersCanCreate;
317 $('createSupervisedUserNoManagerText').hidden =
318 data.supervisedUsersCanCreate;
319 $('createSupervisedUserNoManagerText').textContent =
320 data.supervisedUsersRestrictionReason;
322 var isEnrollingConsumerManagement = data.isEnrollingConsumerManagement;
323 $('consumerManagementEnrollment').hidden = !isEnrollingConsumerManagement;
325 this.isShowUsers_ = data.isShowUsers;
326 this.updateCancelButtonState();
328 this.isEnrollingConsumerManagement_ = isEnrollingConsumerManagement;
330 // Sign-in right panel is hidden if all of its items are hidden.
331 var noRightPanel = $('gaia-signin-reason').hidden &&
332 $('createAccount').hidden &&
333 $('guestSignin').hidden &&
334 $('createSupervisedUserPane').hidden &&
335 $('consumerManagementEnrollment').hidden;
336 this.classList.toggle('no-right-panel', noRightPanel);
337 if (Oobe.getInstance().currentScreen === this)
338 Oobe.getInstance().updateScreenSize(this);
342 * Updates [Cancel] button state. Allow cancellation of screen only when
343 * user pods can be displayed.
345 updateCancelButtonState: function() {
346 this.cancelAllowed_ = this.isShowUsers_ && $('pod-row').pods.length;
347 $('login-header-bar').allowCancel = this.cancelAllowed_;
351 * Whether the current auth flow is SAML.
354 return this.gaiaAuthHost_.authFlow ==
355 cr.login.GaiaAuthHost.AuthFlow.SAML;
359 * Invoked when the authFlow property is changed no the gaia host.
360 * @param {Event} e Property change event.
362 onAuthFlowChange_: function(e) {
363 var isSAML = this.isSAML();
366 $('saml-notice-message').textContent = loadTimeData.getStringF(
368 this.gaiaAuthHost_.authDomain);
371 this.classList.toggle('saml', isSAML);
372 $('saml-notice-container').hidden = !isSAML;
374 if (Oobe.getInstance().currentScreen === this) {
375 Oobe.getInstance().updateScreenSize(this);
376 $('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
381 * Invoked when the auth host emits 'ready' event.
384 onAuthReady_: function() {
385 this.loading = false;
386 this.clearLoadingTimer_();
388 // Show deferred error bubble.
389 if (this.errorBubble_) {
390 this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
391 this.errorBubble_ = undefined;
394 chrome.send('loginWebuiReady');
395 chrome.send('loginVisible', ['gaia-signin']);
397 // Warm up the user images screen.
398 Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
402 * Invoked when the user has successfully authenticated via SAML, the
403 * principals API was not used and the auth host needs the user to confirm
404 * the scraped password.
405 * @param {number} passwordCount The number of passwords that were scraped.
408 onAuthConfirmPassword_: function(passwordCount) {
410 Oobe.getInstance().headerHidden = false;
412 if (this.samlPasswordConfirmAttempt_ == 0)
413 chrome.send('scrapedPasswordCount', [passwordCount]);
415 if (this.samlPasswordConfirmAttempt_ < 2) {
416 login.ConfirmPasswordScreen.show(
417 this.samlPasswordConfirmAttempt_,
418 this.onConfirmPasswordCollected_.bind(this));
420 chrome.send('scrapedPasswordVerificationFailed');
421 this.showFatalAuthError(
422 loadTimeData.getString('fatalErrorMessageVerificationFailed'));
427 * Invoked when the confirm password screen is dismissed.
430 onConfirmPasswordCollected_: function(password) {
431 this.samlPasswordConfirmAttempt_++;
432 this.gaiaAuthHost_.verifyConfirmedPassword(password);
434 // Shows signin UI again without changing states.
435 Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
439 * Inovked when the user has successfully authenticated via SAML, the
440 * principals API was not used and no passwords could be scraped.
441 * @param {string} email The authenticated user's e-mail.
443 onAuthNoPassword_: function(email) {
444 this.showFatalAuthError(loadTimeData.getString(
445 'fatalErrorMessageNoPassword'));
446 chrome.send('scrapedPasswordCount', [0]);
450 * Invoked when the authentication flow had to be aborted because content
451 * served over an unencrypted connection was detected. Shows a fatal error.
452 * This method is only called on Chrome OS, where the entire authentication
453 * flow is required to be encrypted.
454 * @param {string} url The URL that was blocked.
456 onInsecureContentBlocked_: function(url) {
457 this.showFatalAuthError(loadTimeData.getStringF(
458 'fatalErrorMessageInsecureURL',
463 * Shows the fatal auth error.
464 * @param {string} message The error message to show.
466 showFatalAuthError: function(message) {
467 login.FatalErrorScreen.show(message, Oobe.showSigninUI);
471 * Show fatal auth error when information is missing from GAIA.
473 missingGaiaInfo_: function() {
474 this.showFatalAuthError(
475 loadTimeData.getString('fatalErrorMessageNoAccountDetails'));
479 * Record that SAML API was used during sign-in.
481 samlApiUsed_: function() {
482 chrome.send('usingSAMLAPI');
486 * Invoked when auth is completed successfully.
487 * @param {!Object} credentials Credentials of the completed authentication.
490 onAuthCompleted_: function(credentials) {
491 if (credentials.useOffline) {
492 this.email = credentials.email;
493 chrome.send('authenticateUser',
496 credentials.password]);
497 } else if (credentials.authCode) {
498 chrome.send('completeAuthentication',
501 credentials.password,
502 credentials.authCode]);
504 chrome.send('completeLogin',
507 credentials.password,
508 credentials.usingSAML]);
512 // Now that we're in logged in state header should be hidden.
513 Oobe.getInstance().headerHidden = true;
514 // Clear any error messages that were shown before login.
519 * Clears input fields and switches to input mode.
520 * @param {boolean} takeFocus True to take focus.
521 * @param {boolean} forceOnline Whether online sign-in should be forced.
522 * If |forceOnline| is false previously used sign-in type will be used.
524 reset: function(takeFocus, forceOnline) {
525 // Reload and show the sign-in UI if needed.
527 if (!forceOnline && this.isLocal) {
528 // Show 'Cancel' button to allow user to return to the main screen
529 // (e.g. this makes sense when connection is back).
530 Oobe.getInstance().headerHidden = false;
531 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
532 // Do nothing, since offline version is reloaded after an error comes.
540 * Reloads extension frame.
542 doReload: function() {
544 this.gaiaAuthHost_.reload();
546 this.startLoadingTimer_();
550 * Updates localized content of the screen that is not updated via template.
552 updateLocalizedContent: function() {
553 $('createAccount').innerHTML = loadTimeData.getStringF(
555 '<a id="createAccountLink" class="signin-link" href="#">',
557 $('guestSignin').innerHTML = loadTimeData.getStringF(
559 '<a id="guestSigninLink" class="signin-link" href="#">',
561 $('createSupervisedUserLinkPlaceholder').innerHTML =
562 loadTimeData.getStringF(
563 'createSupervisedUser',
564 '<a id="createSupervisedUserLink" class="signin-link" href="#">',
566 $('consumerManagementEnrollment').innerHTML = loadTimeData.getString(
567 'consumerManagementEnrollmentSigninMessage');
568 $('createAccountLink').addEventListener('click', function(e) {
569 chrome.send('createAccount');
572 $('guestSigninLink').addEventListener('click', function(e) {
573 chrome.send('launchIncognito');
576 $('createSupervisedUserLink').addEventListener('click', function(e) {
577 chrome.send('showSupervisedUserCreationScreen');
583 * Shows sign-in error bubble.
584 * @param {number} loginAttempts Number of login attemps tried.
585 * @param {HTMLElement} content Content to show in bubble.
587 showErrorBubble: function(loginAttempts, error) {
589 $('add-user-button').hidden = true;
590 $('cancel-add-user-button').hidden = false;
591 // Reload offline version of the sign-in extension, which will show
593 chrome.send('offlineLogin', [this.email]);
594 } else if (!this.loading) {
595 // We want to show bubble near "Email" field, but we can't calculate
596 // it's position because it is located inside iframe. So we only
597 // can hardcode some constants.
598 /** @const */ var ERROR_BUBBLE_OFFSET = 84;
599 /** @const */ var ERROR_BUBBLE_PADDING = 0;
600 $('bubble').showContentForElement($('login-box'),
601 cr.ui.Bubble.Attachment.LEFT,
604 ERROR_BUBBLE_PADDING);
606 // Defer the bubble until the frame has been loaded.
607 this.errorBubble_ = [loginAttempts, error];
612 * Called when user canceled signin.
615 if (!this.cancelAllowed_) {
616 // In OOBE signin screen, cancel is not allowed because there is
617 // no other screen to show. If user is in middle of a saml flow,
618 // reset signin screen to get out of the saml flow.
620 Oobe.resetSigninUI(true);
625 $('pod-row').loadLastWallpaper();
626 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
627 Oobe.resetSigninUI(true);
631 * Handler for iframe's error notification coming from the outside.
632 * For more info see C++ class 'WebUILoginView' which calls this method.
633 * @param {number} error Error code.
634 * @param {string} url The URL that failed to load.
636 onFrameError: function(error, url) {
638 chrome.send('frameLoadingCompleted', [this.error_]);