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"></include>
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',
25 'setAuthenticatedUserEmail',
28 'updateCancelButtonState'
32 * Frame loading error code (0 - no error).
39 * Saved gaia auth host load params.
43 gaiaAuthParams_: null,
46 * Whether local version of Gaia page is used.
53 * Email of the user, which is logging in using offline mode.
59 * Timer id of pending load.
63 loadingTimer_: undefined,
66 * Whether user can cancel Gaia screen.
70 cancelAllowed_: undefined,
73 * Whether we should show user pods on the login screen.
77 isShowUsers_: undefined,
80 * SAML password confirmation attempt count.
83 samlPasswordConfirmAttempt_: 0,
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));
101 $('enterprise-info-hint-link').addEventListener('click', function(e) {
102 chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
107 this.updateLocalizedContent();
111 * Header text of the screen.
115 return loadTimeData.getString('signinScreenTitle');
119 * Returns true if local version of Gaia is used.
123 return this.isLocal_;
127 * Sets whether local version of Gaia is used.
128 * @param {boolean} value Whether local version of Gaia is used.
131 this.isLocal_ = value;
132 chrome.send('updateOfflineLogin', [value]);
136 * Shows/hides loading UI.
137 * @param {boolean} show True to show loading UI.
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;
149 * Handler for Gaia loading suspiciously long timeout.
152 onLoadingSuspiciouslyLong_: function() {
153 if (this != Oobe.getInstance().currentScreen)
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) *
163 * Handler for Gaia loading timeout.
166 onLoadingTimeOut_: function() {
167 this.loadingTimer_ = undefined;
168 chrome.send('showLoadingTimeoutError');
172 * Clears loading timer.
175 clearLoadingTimer_: function() {
176 if (this.loadingTimer_) {
177 window.clearTimeout(this.loadingTimer_);
178 this.loadingTimer_ = undefined;
183 * Sets up loading timer.
186 startLoadingTimer_: function() {
187 this.clearLoadingTimer_();
188 this.loadingTimer_ = window.setTimeout(
189 this.onLoadingSuspiciouslyLong_.bind(this),
190 GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
194 * Whether Gaia is loading.
198 return !$('gaia-loading').hidden;
200 set loading(loading) {
201 if (loading == this.loading)
204 this.showLoadingUI_(loading);
208 * Event handler that is invoked just before the frame is shown.
209 * @param {string} data Screen init payload. Url of auth extension start
212 onBeforeShow: function(data) {
213 chrome.send('loginUIStateChanged', ['gaia-signin', true]);
214 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
216 // Ensure that GAIA signin (or loading UI) is actually visible.
217 window.webkitRequestAnimationFrame(function() {
218 chrome.send('loginVisible', ['gaia-loading']);
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;
227 * Event handler that is invoked just before the screen is hidden.
229 onBeforeHide: function() {
230 chrome.send('loginUIStateChanged', ['gaia-signin', false]);
231 $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
235 * Loads the authentication extension into the iframe.
236 * @param {Object} data Extension parameters bag.
239 loadAuthExtension: function(data) {
240 this.isLocal = data.isLocal;
244 this.classList.toggle('saml', false);
245 this.samlPasswordConfirmAttempt_ = 0;
247 this.updateAuthExtension(data);
250 for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
251 var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
253 params[name] = data[name];
256 if (data.localizedStrings)
257 params.localizedStrings = data.localizedStrings;
259 if (data.forceReload ||
260 JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
262 this.gaiaAuthHost_.load(data.useOffline ?
263 cr.login.GaiaAuthHost.AuthMode.OFFLINE :
264 cr.login.GaiaAuthHost.AuthMode.DEFAULT,
266 this.onAuthCompleted_.bind(this));
267 this.gaiaAuthParams_ = params;
270 this.startLoadingTimer_();
271 } else if (this.loading && this.error_) {
272 // An error has occurred, so trying to reload.
278 * Updates the authentication extension with new parameters, if needed.
279 * @param {Object} data New extension parameters bag.
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;
289 reasonLabel.hidden = true;
292 $('createAccount').hidden = !data.createAccount;
293 $('guestSignin').hidden = !data.guestSignin;
294 $('createManagedUserPane').hidden = !data.managedUsersEnabled;
296 $('createManagedUserLinkPlaceholder').hidden =
297 !data.managedUsersCanCreate;
298 $('createManagedUserNoManagerText').hidden = data.managedUsersCanCreate;
299 $('createManagedUserNoManagerText').textContent =
300 data.managedUsersRestrictionReason;
302 this.isShowUsers_ = data.isShowUsers;
303 this.updateCancelButtonState();
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);
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.
321 setAuthenticatedUserEmail: function(attemptToken, email) {
323 this.showFatalAuthError();
325 this.gaiaAuthHost_.setAuthenticatedUserEmail(attemptToken, email);
329 * Updates [Cancel] button state. Allow cancellation of screen only when
330 * user pods can be displayed.
332 updateCancelButtonState: function() {
333 this.cancelAllowed_ = this.isShowUsers_ && $('pod-row').pods.length;
334 $('login-header-bar').allowCancel = this.cancelAllowed_;
338 * Whether the current auth flow is SAML.
341 return this.gaiaAuthHost_.authFlow ==
342 cr.login.GaiaAuthHost.AuthFlow.SAML;
346 * Invoked when the authFlow property is changed no the gaia host.
347 * @param {Event} e Property change event.
349 onAuthFlowChange_: function(e) {
350 var isSAML = this.isSAML();
353 $('saml-notice-message').textContent = loadTimeData.getStringF(
355 this.gaiaAuthHost_.authDomain);
358 this.classList.toggle('saml', isSAML);
359 $('saml-notice-container').hidden = !isSAML;
361 if (Oobe.getInstance().currentScreen === this) {
362 Oobe.getInstance().updateScreenSize(this);
363 $('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
368 * Invoked when the auth host emits 'ready' event.
371 onAuthReady_: function() {
372 this.loading = false;
373 this.clearLoadingTimer_();
375 // Show deferred error bubble.
376 if (this.errorBubble_) {
377 this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
378 this.errorBubble_ = undefined;
381 chrome.send('loginWebuiReady');
382 chrome.send('loginVisible', ['gaia-signin']);
384 // Warm up the user images screen.
385 Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
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
397 onRetrieveAuthenticatedUserEmail_: function(attemptToken, 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');
406 chrome.send('retrieveAuthenticatedUserEmail', [attemptToken]);
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.
416 onAuthConfirmPassword_: function(passwordCount) {
418 Oobe.getInstance().headerHidden = false;
420 if (this.samlPasswordConfirmAttempt_ == 0)
421 chrome.send('scrapedPasswordCount', [passwordCount]);
423 if (this.samlPasswordConfirmAttempt_ < 2) {
424 login.ConfirmPasswordScreen.show(
425 this.samlPasswordConfirmAttempt_,
426 this.onConfirmPasswordCollected_.bind(this));
428 chrome.send('scrapedPasswordVerificationFailed');
429 this.showFatalAuthError();
434 * Invoked when the confirm password screen is dismissed.
437 onConfirmPasswordCollected_: function(password) {
438 this.samlPasswordConfirmAttempt_++;
439 this.gaiaAuthHost_.verifyConfirmedPassword(password);
441 // Shows signin UI again without changing states.
442 Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
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.
450 onAuthNoPassword_: function(email) {
451 this.showFatalAuthError();
452 chrome.send('scrapedPasswordCount', [0]);
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.
462 onInsecureContentBlocked_: function(url) {
463 this.showFatalAuthError(loadTimeData.getStringF(
464 'fatalErrorMessageInsecureURL',
469 * Shows the fatal auth error.
470 * @param {string} message The error message to show.
472 showFatalAuthError: function(message) {
474 message = loadTimeData.getString('fatalErrorMessageGeneric');
475 login.FatalErrorScreen.show(message, Oobe.showSigninUI);
479 * Invoked when auth is completed successfully.
480 * @param {!Object} credentials Credentials of the completed authentication.
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',
491 credentials.password,
492 credentials.authCode]);
494 chrome.send('completeLogin',
496 credentials.password,
497 credentials.usingSAML]);
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.
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.
513 reset: function(takeFocus, forceOnline) {
514 // Reload and show the sign-in UI if needed.
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.
529 * Reloads extension frame.
531 doReload: function() {
533 this.gaiaAuthHost_.reload();
535 this.startLoadingTimer_();
539 * Updates localized content of the screen that is not updated via template.
541 updateLocalizedContent: function() {
542 $('createAccount').innerHTML = loadTimeData.getStringF(
544 '<a id="createAccountLink" class="signin-link" href="#">',
546 $('guestSignin').innerHTML = loadTimeData.getStringF(
548 '<a id="guestSigninLink" class="signin-link" href="#">',
550 $('createManagedUserLinkPlaceholder').innerHTML = loadTimeData.getStringF(
551 'createLocallyManagedUser',
552 '<a id="createManagedUserLink" class="signin-link" href="#">',
554 $('createAccountLink').addEventListener('click', function(e) {
555 chrome.send('createAccount');
558 $('guestSigninLink').addEventListener('click', function(e) {
559 chrome.send('launchIncognito');
562 $('createManagedUserLink').addEventListener('click', function(e) {
563 chrome.send('showLocallyManagedUserCreationScreen');
569 * Shows sign-in error bubble.
570 * @param {number} loginAttempts Number of login attemps tried.
571 * @param {HTMLElement} content Content to show in bubble.
573 showErrorBubble: function(loginAttempts, error) {
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
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,
590 ERROR_BUBBLE_PADDING);
592 // Defer the bubble until the frame has been loaded.
593 this.errorBubble_ = [loginAttempts, error];
598 * Called when user canceled signin.
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.
606 Oobe.resetSigninUI(true);
611 $('pod-row').loadLastWallpaper();
612 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
613 Oobe.resetSigninUI(true);
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.
622 onFrameError: function(error, url) {
624 chrome.send('frameLoadingCompleted', [this.error_]);