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'
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 new Gaia 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_
.addEventListener(
124 'backButton', this.onBackButton_
.bind(this));
125 this.gaiaAuthHost_
.confirmPasswordCallback
=
126 this.onAuthConfirmPassword_
.bind(this);
127 this.gaiaAuthHost_
.noPasswordCallback
=
128 this.onAuthNoPassword_
.bind(this);
129 this.gaiaAuthHost_
.insecureContentBlockedCallback
=
130 this.onInsecureContentBlocked_
.bind(this);
131 this.gaiaAuthHost_
.missingGaiaInfoCallback
=
132 this.missingGaiaInfo_
.bind(this);
133 this.gaiaAuthHost_
.samlApiUsedCallback
=
134 this.samlApiUsed_
.bind(this);
135 this.gaiaAuthHost_
.addEventListener('authFlowChange',
136 this.onAuthFlowChange_
.bind(this));
137 this.gaiaAuthHost_
.addEventListener('authCompleted',
138 this.onAuthCompletedMessage_
.bind(this));
139 this.gaiaAuthHost_
.addEventListener('loadAbort',
140 this.onLoadAbortMessage_
.bind(this));
142 $('enterprise-info-hint-link').addEventListener('click', function(e
) {
143 chrome
.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING
]);
147 $('back-button-item').addEventListener('click', function(e
) {
148 $('back-button-item').hidden
= true;
149 $('signin-frame').back();
152 $('close-button-item').addEventListener('click', function(e
) {
157 this.updateLocalizedContent();
161 * Header text of the screen.
165 return loadTimeData
.getString('signinScreenTitle');
169 * Returns true if local version of Gaia is used.
173 return this.isLocal_
;
177 * Sets whether local version of Gaia is used.
178 * @param {boolean} value Whether local version of Gaia is used.
181 this.isLocal_
= value
;
182 chrome
.send('updateOfflineLogin', [value
]);
186 * Shows/hides loading UI.
187 * @param {boolean} show True to show loading UI.
190 showLoadingUI_: function(show
) {
191 $('gaia-loading').hidden
= !show
;
192 $('signin-frame').hidden
= show
;
193 $('signin-right').hidden
= show
;
194 $('enterprise-info-container').hidden
= show
;
195 $('gaia-signin-divider').hidden
= show
;
196 this.classList
.toggle('loading', show
);
200 * Handler for Gaia loading suspiciously long timeout.
203 onLoadingSuspiciouslyLong_: function() {
204 if (this != Oobe
.getInstance().currentScreen
)
206 chrome
.send('showLoadingTimeoutError');
207 this.loadingTimer_
= window
.setTimeout(
208 this.onLoadingTimeOut_
.bind(this),
209 (MAX_GAIA_LOADING_TIME_SEC
- GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC
) *
214 * Handler for Gaia loading timeout.
217 onLoadingTimeOut_: function() {
218 this.loadingTimer_
= undefined;
219 chrome
.send('showLoadingTimeoutError');
223 * Clears loading timer.
226 clearLoadingTimer_: function() {
227 if (this.loadingTimer_
) {
228 window
.clearTimeout(this.loadingTimer_
);
229 this.loadingTimer_
= undefined;
234 * Sets up loading timer.
237 startLoadingTimer_: function() {
238 this.clearLoadingTimer_();
239 this.loadingTimer_
= window
.setTimeout(
240 this.onLoadingSuspiciouslyLong_
.bind(this),
241 GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC
* 1000);
245 * Whether Gaia is loading.
249 return !$('gaia-loading').hidden
;
251 set loading(loading
) {
252 if (loading
== this.loading
)
255 this.showLoadingUI_(loading
);
259 * Event handler that is invoked just before the frame is shown.
260 * @param {string} data Screen init payload. Url of auth extension start
263 onBeforeShow: function(data
) {
264 chrome
.send('loginUIStateChanged', ['gaia-signin', true]);
265 $('login-header-bar').signinUIState
=
266 this.isEnrollingConsumerManagement_
?
267 SIGNIN_UI_STATE
.CONSUMER_MANAGEMENT_ENROLLMENT
:
268 SIGNIN_UI_STATE
.GAIA_SIGNIN
;
270 // Ensure that GAIA signin (or loading UI) is actually visible.
271 window
.requestAnimationFrame(function() {
272 chrome
.send('loginVisible', ['gaia-loading']);
274 $('back-button-item').disabled
= false;
275 $('back-button-item').hidden
= true;
276 $('close-button-item').disabled
= false;
277 this.classList
.toggle('loading', this.loading
);
279 // Button header is always visible when sign in is presented.
280 // Header is hidden once GAIA reports on successful sign in.
281 Oobe
.getInstance().headerHidden
= false;
284 onAfterShow: function(data
) {
285 if (!this.loading
&& this.isWebviewSignin
)
286 $('signin-frame').focus();
290 * Event handler that is invoked just before the screen is hidden.
292 onBeforeHide: function() {
293 chrome
.send('loginUIStateChanged', ['gaia-signin', false]);
294 $('login-header-bar').signinUIState
= SIGNIN_UI_STATE
.HIDDEN
;
298 * Loads the authentication extension into the iframe.
299 * @param {Object} data Extension parameters bag.
302 loadAuthExtension: function(data
) {
303 this.isLocal
= data
.isLocal
;
305 this.isNewGaiaFlow
= data
.useNewGaiaFlow
;
308 this.classList
.toggle('full-width', false);
309 this.samlPasswordConfirmAttempt_
= 0;
311 this.updateAuthExtension(data
);
314 for (var i
in cr
.login
.GaiaAuthHost
.SUPPORTED_PARAMS
) {
315 var name
= cr
.login
.GaiaAuthHost
.SUPPORTED_PARAMS
[i
];
317 params
[name
] = data
[name
];
320 if (data
.localizedStrings
)
321 params
.localizedStrings
= data
.localizedStrings
;
323 if (this.isNewGaiaFlow
) {
324 $('inner-container').classList
.add('new-gaia-flow');
325 $('progress-dots').hidden
= true;
326 if (data
.enterpriseDomain
)
327 params
.enterpriseDomain
= data
.enterpriseDomain
;
328 params
.chromeType
= data
.chromeType
;
329 params
.isNewGaiaFlowChromeOS
= true;
330 $('login-header-bar').showGuestButton
= true;
333 if (data
.gaiaEndpoint
)
334 params
.gaiaPath
= data
.gaiaEndpoint
;
336 $('login-header-bar').newGaiaFlow
= this.isNewGaiaFlow
;
338 if (data
.forceReload
||
339 JSON
.stringify(this.gaiaAuthParams_
) != JSON
.stringify(params
)) {
342 var authMode
= cr
.login
.GaiaAuthHost
.AuthMode
.DEFAULT
;
344 authMode
= cr
.login
.GaiaAuthHost
.AuthMode
.OFFLINE
;
346 this.gaiaAuthHost_
.load(authMode
,
348 this.onAuthCompleted_
.bind(this));
349 this.gaiaAuthParams_
= params
;
352 this.startLoadingTimer_();
353 } else if (this.loading
&& this.error_
) {
354 // An error has occurred, so trying to reload.
360 * Updates the authentication extension with new parameters, if needed.
361 * @param {Object} data New extension parameters bag.
364 updateAuthExtension: function(data
) {
365 var reasonLabel
= $('gaia-signin-reason');
366 if (data
.passwordChanged
) {
367 reasonLabel
.textContent
=
368 loadTimeData
.getString('signinScreenPasswordChanged');
369 reasonLabel
.hidden
= false;
371 reasonLabel
.hidden
= true;
374 if (this.isNewGaiaFlow
) {
375 $('login-header-bar').showCreateSupervisedButton
=
376 data
.supervisedUsersCanCreate
;
378 $('createAccount').hidden
= !data
.createAccount
;
379 $('guestSignin').hidden
= !data
.guestSignin
;
380 $('createSupervisedUserPane').hidden
= !data
.supervisedUsersEnabled
;
382 $('createSupervisedUserLinkPlaceholder').hidden
=
383 !data
.supervisedUsersCanCreate
;
384 $('createSupervisedUserNoManagerText').hidden
=
385 data
.supervisedUsersCanCreate
;
386 $('createSupervisedUserNoManagerText').textContent
=
387 data
.supervisedUsersRestrictionReason
;
390 var isEnrollingConsumerManagement
= data
.isEnrollingConsumerManagement
;
391 $('consumerManagementEnrollment').hidden
= !isEnrollingConsumerManagement
;
393 this.isShowUsers_
= data
.isShowUsers
;
394 this.updateCancelButtonState();
396 this.isEnrollingConsumerManagement_
= isEnrollingConsumerManagement
;
398 // Sign-in right panel is hidden if all of its items are hidden.
399 var noRightPanel
= $('gaia-signin-reason').hidden
&&
400 $('createAccount').hidden
&&
401 $('guestSignin').hidden
&&
402 $('createSupervisedUserPane').hidden
&&
403 $('consumerManagementEnrollment').hidden
;
404 this.classList
.toggle('no-right-panel', noRightPanel
);
405 this.classList
.toggle('full-width', false);
406 if (Oobe
.getInstance().currentScreen
=== this)
407 Oobe
.getInstance().updateScreenSize(this);
411 * Updates [Cancel] button state. Allow cancellation of screen only when
412 * user pods can be displayed.
414 updateCancelButtonState: function() {
415 this.cancelAllowed_
= this.isShowUsers_
&& $('pod-row').pods
.length
;
416 $('login-header-bar').allowCancel
= this.cancelAllowed_
;
417 if (this.isNewGaiaFlow
)
418 $('close-button-item').hidden
= !this.cancelAllowed_
;
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.
432 onAuthFlowChange_: function() {
433 var isSAML
= this.isSAML();
436 $('saml-notice-message').textContent
= loadTimeData
.getStringF(
438 this.gaiaAuthHost_
.authDomain
);
441 this.classList
.toggle('no-right-panel', isSAML
);
442 this.classList
.toggle('full-width', isSAML
);
443 $('saml-notice-container').hidden
= !isSAML
;
445 if (Oobe
.getInstance().currentScreen
=== this) {
446 Oobe
.getInstance().updateScreenSize(this);
447 $('login-header-bar').allowCancel
= isSAML
|| this.cancelAllowed_
;
448 if (this.isNewGaiaFlow
)
449 $('close-button-item').hidden
= !(isSAML
|| this.cancelAllowed_
);
454 * Invoked when the auth host emits 'ready' event.
457 onAuthReady_: function() {
458 this.loading
= false;
459 this.clearLoadingTimer_();
461 // Show deferred error bubble.
462 if (this.errorBubble_
) {
463 this.showErrorBubble(this.errorBubble_
[0], this.errorBubble_
[1]);
464 this.errorBubble_
= undefined;
467 chrome
.send('loginWebuiReady');
468 chrome
.send('loginVisible', ['gaia-signin']);
470 // Warm up the user images screen.
471 Oobe
.getInstance().preloadScreen({id
: SCREEN_USER_IMAGE_PICKER
});
475 * Invoked when the auth host emits 'dialogShown' event.
478 onDialogShown_: function() {
479 $('back-button-item').disabled
= true;
480 $('close-button-item').disabled
= true;
484 * Invoked when the auth host emits 'dialogHidden' event.
487 onDialogHidden_: function() {
488 $('back-button-item').disabled
= false;
489 $('close-button-item').disabled
= false;
493 * Invoked when the auth host emits 'backButton' event.
496 onBackButton_: function(e
) {
497 $('back-button-item').hidden
= !e
.detail
;
501 * Invoked when the user has successfully authenticated via SAML, the
502 * principals API was not used and the auth host needs the user to confirm
503 * the scraped password.
504 * @param {number} passwordCount The number of passwords that were scraped.
507 onAuthConfirmPassword_: function(passwordCount
) {
509 Oobe
.getInstance().headerHidden
= false;
511 if (this.samlPasswordConfirmAttempt_
== 0)
512 chrome
.send('scrapedPasswordCount', [passwordCount
]);
514 if (this.samlPasswordConfirmAttempt_
< 2) {
515 login
.ConfirmPasswordScreen
.show(
516 this.samlPasswordConfirmAttempt_
,
517 this.onConfirmPasswordCollected_
.bind(this));
519 chrome
.send('scrapedPasswordVerificationFailed');
520 this.showFatalAuthError(
521 loadTimeData
.getString('fatalErrorMessageVerificationFailed'));
526 * Invoked when the confirm password screen is dismissed.
529 onConfirmPasswordCollected_: function(password
) {
530 this.samlPasswordConfirmAttempt_
++;
531 this.gaiaAuthHost_
.verifyConfirmedPassword(password
);
533 // Shows signin UI again without changing states.
534 Oobe
.showScreen({id
: SCREEN_GAIA_SIGNIN
});
538 * Inovked when the user has successfully authenticated via SAML, the
539 * principals API was not used and no passwords could be scraped.
540 * @param {string} email The authenticated user's e-mail.
542 onAuthNoPassword_: function(email
) {
543 this.showFatalAuthError(loadTimeData
.getString(
544 'fatalErrorMessageNoPassword'));
545 chrome
.send('scrapedPasswordCount', [0]);
549 * Invoked when the authentication flow had to be aborted because content
550 * served over an unencrypted connection was detected. Shows a fatal error.
551 * This method is only called on Chrome OS, where the entire authentication
552 * flow is required to be encrypted.
553 * @param {string} url The URL that was blocked.
555 onInsecureContentBlocked_: function(url
) {
556 this.showFatalAuthError(loadTimeData
.getStringF(
557 'fatalErrorMessageInsecureURL',
562 * Shows the fatal auth error.
563 * @param {string} message The error message to show.
565 showFatalAuthError: function(message
) {
566 login
.FatalErrorScreen
.show(message
, Oobe
.showSigninUI
);
570 * Show fatal auth error when information is missing from GAIA.
572 missingGaiaInfo_: function() {
573 this.showFatalAuthError(
574 loadTimeData
.getString('fatalErrorMessageNoAccountDetails'));
578 * Record that SAML API was used during sign-in.
580 samlApiUsed_: function() {
581 chrome
.send('usingSAMLAPI');
585 * Invoked when auth is completed successfully.
586 * @param {!Object} credentials Credentials of the completed authentication.
589 onAuthCompleted_: function(credentials
) {
590 if (credentials
.useOffline
) {
591 this.email
= credentials
.email
;
592 chrome
.send('authenticateUser',
594 credentials
.password
]);
595 } else if (credentials
.authCode
) {
596 if (credentials
.hasOwnProperty('authCodeOnly') &&
597 credentials
.authCodeOnly
) {
598 chrome
.send('completeAuthenticationAuthCodeOnly',
599 [credentials
.authCode
]);
601 chrome
.send('completeAuthentication',
604 credentials
.password
,
605 credentials
.authCode
,
606 credentials
.usingSAML
]);
609 chrome
.send('completeLogin',
612 credentials
.password
,
613 credentials
.usingSAML
]);
617 // Now that we're in logged in state header should be hidden.
618 Oobe
.getInstance().headerHidden
= true;
619 // Clear any error messages that were shown before login.
624 * Invoked when onAuthCompleted message received.
625 * @param {!Object} e Payload of the received HTML5 message.
628 onAuthCompletedMessage_: function(e
) {
629 this.onAuthCompleted_(e
.detail
);
633 * Invoked when onLoadAbort message received.
634 * @param {!Object} e Payload of the received HTML5 message.
637 onLoadAbortMessage_: function(e
) {
638 this.onWebviewError(e
.detail
);
642 * Clears input fields and switches to input mode.
643 * @param {boolean} takeFocus True to take focus.
644 * @param {boolean} forceOnline Whether online sign-in should be forced.
645 * If |forceOnline| is false previously used sign-in type will be used.
647 reset: function(takeFocus
, forceOnline
) {
648 // Reload and show the sign-in UI if needed.
650 if (!forceOnline
&& this.isLocal
) {
651 // Show 'Cancel' button to allow user to return to the main screen
652 // (e.g. this makes sense when connection is back).
653 Oobe
.getInstance().headerHidden
= false;
654 $('login-header-bar').signinUIState
= SIGNIN_UI_STATE
.GAIA_SIGNIN
;
655 // Do nothing, since offline version is reloaded after an error comes.
663 * Reloads extension frame.
665 doReload: function() {
667 this.gaiaAuthHost_
.reload();
669 this.startLoadingTimer_();
673 * Updates localized content of the screen that is not updated via template.
675 updateLocalizedContent: function() {
676 $('createAccount').innerHTML
= loadTimeData
.getStringF(
678 '<a id="createAccountLink" class="signin-link" href="#">',
680 $('guestSignin').innerHTML
= loadTimeData
.getStringF(
682 '<a id="guestSigninLink" class="signin-link" href="#">',
684 $('createSupervisedUserLinkPlaceholder').innerHTML
=
685 loadTimeData
.getStringF(
686 'createSupervisedUser',
687 '<a id="createSupervisedUserLink" class="signin-link" href="#">',
689 $('consumerManagementEnrollment').innerHTML
= loadTimeData
.getString(
690 'consumerManagementEnrollmentSigninMessage');
691 $('createAccountLink').addEventListener('click', function(e
) {
692 chrome
.send('createAccount');
695 $('guestSigninLink').addEventListener('click', function(e
) {
696 chrome
.send('launchIncognito');
699 $('createSupervisedUserLink').addEventListener('click', function(e
) {
700 chrome
.send('showSupervisedUserCreationScreen');
706 * Shows sign-in error bubble.
707 * @param {number} loginAttempts Number of login attemps tried.
708 * @param {HTMLElement} content Content to show in bubble.
710 showErrorBubble: function(loginAttempts
, error
) {
712 $('add-user-button').hidden
= true;
713 $('cancel-add-user-button').hidden
= false;
714 // Reload offline version of the sign-in extension, which will show
716 chrome
.send('offlineLogin', [this.email
]);
717 } else if (!this.loading
) {
718 // We want to show bubble near "Email" field, but we can't calculate
719 // it's position because it is located inside iframe. So we only
720 // can hardcode some constants.
721 /** @const */ var ERROR_BUBBLE_OFFSET
= 84;
722 /** @const */ var ERROR_BUBBLE_PADDING
= 0;
723 $('bubble').showContentForElement($('login-box'),
724 cr
.ui
.Bubble
.Attachment
.LEFT
,
727 ERROR_BUBBLE_PADDING
);
729 // Defer the bubble until the frame has been loaded.
730 this.errorBubble_
= [loginAttempts
, error
];
735 * Called when user canceled signin.
738 if (!this.cancelAllowed_
) {
739 // In OOBE signin screen, cancel is not allowed because there is
740 // no other screen to show. If user is in middle of a saml flow,
741 // reset signin screen to get out of the saml flow.
743 Oobe
.resetSigninUI(true);
748 $('pod-row').loadLastWallpaper();
749 Oobe
.showScreen({id
: SCREEN_ACCOUNT_PICKER
});
750 Oobe
.resetSigninUI(true);
754 * Handler for iframe's error notification coming from the outside.
755 * For more info see C++ class 'WebUILoginView' which calls this method.
756 * @param {number} error Error code.
757 * @param {string} url The URL that failed to load.
759 onFrameError: function(error
, url
) {
761 chrome
.send('frameLoadingCompleted', [this.error_
]);
765 * Handler for webview error handling.
766 * @param {!Object} data Additional information about error event like:
767 * {string} error Error code such as "ERR_INTERNET_DISCONNECTED".
768 * {string} url The URL that failed to load.
770 onWebviewError: function(data
) {
771 chrome
.send('webviewLoadAborted', [data
.error
]);