blob: ff044033a191c9c7d89fa6f710c292196609d1d6 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Oobe signin screen implementation.
*/
<include src="../../gaia_auth_host/gaia_auth_host.js">
login.createScreen('GaiaSigninScreen', 'gaia-signin', function() {
// Gaia loading time after which error message must be displayed and
// lazy portal check should be fired.
/** @const */ var GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC = 7;
// Maximum Gaia loading time in seconds.
/** @const */ var MAX_GAIA_LOADING_TIME_SEC = 60;
/** @const */ var HELP_TOPIC_ENTERPRISE_REPORTING = 2535613;
return {
EXTERNAL_API: [
'loadAuthExtension',
'updateAuthExtension',
'doReload',
'onFrameError',
'updateCancelButtonState'
],
/**
* Frame loading error code (0 - no error).
* @type {number}
* @private
*/
error_: 0,
/**
* Saved gaia auth host load params.
* @type {?string}
* @private
*/
gaiaAuthParams_: null,
/**
* Whether local version of Gaia page is used.
* @type {boolean}
* @private
*/
isLocal_: false,
/**
* Email of the user, which is logging in using offline mode.
* @type {string}
*/
email: '',
/**
* Whether consumer management enrollment is in progress.
* @type {boolean}
* @private
*/
isEnrollingConsumerManagement_: false,
/**
* Timer id of pending load.
* @type {number}
* @private
*/
loadingTimer_: undefined,
/**
* Whether user can cancel Gaia screen.
* @type {boolean}
* @private
*/
cancelAllowed_: undefined,
/**
* Whether we should show user pods on the login screen.
* @type {boolean}
* @private
*/
isShowUsers_: undefined,
/**
* SAML password confirmation attempt count.
* @type {number}
*/
samlPasswordConfirmAttempt_: 0,
/** @override */
decorate: function() {
this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame'));
this.gaiaAuthHost_.addEventListener(
'ready', this.onAuthReady_.bind(this));
this.gaiaAuthHost_.confirmPasswordCallback =
this.onAuthConfirmPassword_.bind(this);
this.gaiaAuthHost_.noPasswordCallback =
this.onAuthNoPassword_.bind(this);
this.gaiaAuthHost_.insecureContentBlockedCallback =
this.onInsecureContentBlocked_.bind(this);
this.gaiaAuthHost_.missingGaiaInfoCallback =
this.missingGaiaInfo_.bind(this);
this.gaiaAuthHost_.samlApiUsedCallback =
this.samlApiUsed_.bind(this);
this.gaiaAuthHost_.addEventListener('authFlowChange',
this.onAuthFlowChange_.bind(this));
$('enterprise-info-hint-link').addEventListener('click', function(e) {
chrome.send('launchHelpApp', [HELP_TOPIC_ENTERPRISE_REPORTING]);
e.preventDefault();
});
this.updateLocalizedContent();
},
/**
* Header text of the screen.
* @type {string}
*/
get header() {
return loadTimeData.getString('signinScreenTitle');
},
/**
* Returns true if local version of Gaia is used.
* @type {boolean}
*/
get isLocal() {
return this.isLocal_;
},
/**
* Sets whether local version of Gaia is used.
* @param {boolean} value Whether local version of Gaia is used.
*/
set isLocal(value) {
this.isLocal_ = value;
chrome.send('updateOfflineLogin', [value]);
},
/**
* Shows/hides loading UI.
* @param {boolean} show True to show loading UI.
* @private
*/
showLoadingUI_: function(show) {
$('gaia-loading').hidden = !show;
this.gaiaAuthHost_.frame.hidden = show;
$('signin-right').hidden = show;
$('enterprise-info-container').hidden = show;
$('gaia-signin-divider').hidden = show;
},
/**
* Handler for Gaia loading suspiciously long timeout.
* @private
*/
onLoadingSuspiciouslyLong_: function() {
if (this != Oobe.getInstance().currentScreen)
return;
chrome.send('showLoadingTimeoutError');
this.loadingTimer_ = window.setTimeout(
this.onLoadingTimeOut_.bind(this),
(MAX_GAIA_LOADING_TIME_SEC - GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC) *
1000);
},
/**
* Handler for Gaia loading timeout.
* @private
*/
onLoadingTimeOut_: function() {
this.loadingTimer_ = undefined;
chrome.send('showLoadingTimeoutError');
},
/**
* Clears loading timer.
* @private
*/
clearLoadingTimer_: function() {
if (this.loadingTimer_) {
window.clearTimeout(this.loadingTimer_);
this.loadingTimer_ = undefined;
}
},
/**
* Sets up loading timer.
* @private
*/
startLoadingTimer_: function() {
this.clearLoadingTimer_();
this.loadingTimer_ = window.setTimeout(
this.onLoadingSuspiciouslyLong_.bind(this),
GAIA_LOADING_PORTAL_SUSSPECT_TIME_SEC * 1000);
},
/**
* Whether Gaia is loading.
* @type {boolean}
*/
get loading() {
return !$('gaia-loading').hidden;
},
set loading(loading) {
if (loading == this.loading)
return;
this.showLoadingUI_(loading);
},
/**
* Event handler that is invoked just before the frame is shown.
* @param {string} data Screen init payload. Url of auth extension start
* page.
*/
onBeforeShow: function(data) {
chrome.send('loginUIStateChanged', ['gaia-signin', true]);
$('login-header-bar').signinUIState =
this.isEnrollingConsumerManagement_ ?
SIGNIN_UI_STATE.CONSUMER_MANAGEMENT_ENROLLMENT :
SIGNIN_UI_STATE.GAIA_SIGNIN;
// Ensure that GAIA signin (or loading UI) is actually visible.
window.requestAnimationFrame(function() {
chrome.send('loginVisible', ['gaia-loading']);
});
// Button header is always visible when sign in is presented.
// Header is hidden once GAIA reports on successful sign in.
Oobe.getInstance().headerHidden = false;
},
/**
* Event handler that is invoked just before the screen is hidden.
*/
onBeforeHide: function() {
chrome.send('loginUIStateChanged', ['gaia-signin', false]);
$('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
},
/**
* Loads the authentication extension into the iframe.
* @param {Object} data Extension parameters bag.
* @private
*/
loadAuthExtension: function(data) {
this.isLocal = data.isLocal;
this.email = '';
// Reset SAML
this.classList.toggle('saml', false);
this.samlPasswordConfirmAttempt_ = 0;
this.updateAuthExtension(data);
var params = {};
for (var i in cr.login.GaiaAuthHost.SUPPORTED_PARAMS) {
var name = cr.login.GaiaAuthHost.SUPPORTED_PARAMS[i];
if (data[name])
params[name] = data[name];
}
if (data.localizedStrings)
params.localizedStrings = data.localizedStrings;
if (data.useEmbedded)
params.gaiaPath = 'EmbeddedSignIn';
if (data.forceReload ||
JSON.stringify(this.gaiaAuthParams_) != JSON.stringify(params)) {
this.error_ = 0;
var authMode = cr.login.GaiaAuthHost.AuthMode.DEFAULT;
if (data.useOffline)
authMode = cr.login.GaiaAuthHost.AuthMode.OFFLINE;
else if (data.useEmbedded)
authMode = cr.login.GaiaAuthHost.AuthMode.DESKTOP;
this.gaiaAuthHost_.load(authMode,
params,
this.onAuthCompleted_.bind(this));
this.gaiaAuthParams_ = params;
this.loading = true;
this.startLoadingTimer_();
} else if (this.loading && this.error_) {
// An error has occurred, so trying to reload.
this.doReload();
}
},
/**
* Updates the authentication extension with new parameters, if needed.
* @param {Object} data New extension parameters bag.
* @private
*/
updateAuthExtension: function(data) {
var reasonLabel = $('gaia-signin-reason');
if (data.passwordChanged) {
reasonLabel.textContent =
loadTimeData.getString('signinScreenPasswordChanged');
reasonLabel.hidden = false;
} else {
reasonLabel.hidden = true;
}
$('createAccount').hidden = !data.createAccount;
$('guestSignin').hidden = !data.guestSignin;
$('createSupervisedUserPane').hidden = !data.supervisedUsersEnabled;
$('createSupervisedUserLinkPlaceholder').hidden =
!data.supervisedUsersCanCreate;
$('createSupervisedUserNoManagerText').hidden =
data.supervisedUsersCanCreate;
$('createSupervisedUserNoManagerText').textContent =
data.supervisedUsersRestrictionReason;
var isEnrollingConsumerManagement = data.isEnrollingConsumerManagement;
$('consumerManagementEnrollment').hidden = !isEnrollingConsumerManagement;
this.isShowUsers_ = data.isShowUsers;
this.updateCancelButtonState();
this.isEnrollingConsumerManagement_ = isEnrollingConsumerManagement;
// Sign-in right panel is hidden if all of its items are hidden.
var noRightPanel = $('gaia-signin-reason').hidden &&
$('createAccount').hidden &&
$('guestSignin').hidden &&
$('createSupervisedUserPane').hidden &&
$('consumerManagementEnrollment').hidden;
this.classList.toggle('no-right-panel', noRightPanel);
if (Oobe.getInstance().currentScreen === this)
Oobe.getInstance().updateScreenSize(this);
},
/**
* Updates [Cancel] button state. Allow cancellation of screen only when
* user pods can be displayed.
*/
updateCancelButtonState: function() {
this.cancelAllowed_ = this.isShowUsers_ && $('pod-row').pods.length;
$('login-header-bar').allowCancel = this.cancelAllowed_;
},
/**
* Whether the current auth flow is SAML.
*/
isSAML: function() {
return this.gaiaAuthHost_.authFlow ==
cr.login.GaiaAuthHost.AuthFlow.SAML;
},
/**
* Invoked when the authFlow property is changed no the gaia host.
* @param {Event} e Property change event.
*/
onAuthFlowChange_: function(e) {
var isSAML = this.isSAML();
if (isSAML) {
$('saml-notice-message').textContent = loadTimeData.getStringF(
'samlNotice',
this.gaiaAuthHost_.authDomain);
}
this.classList.toggle('saml', isSAML);
$('saml-notice-container').hidden = !isSAML;
if (Oobe.getInstance().currentScreen === this) {
Oobe.getInstance().updateScreenSize(this);
$('login-header-bar').allowCancel = isSAML || this.cancelAllowed_;
}
},
/**
* Invoked when the auth host emits 'ready' event.
* @private
*/
onAuthReady_: function() {
this.loading = false;
this.clearLoadingTimer_();
// Show deferred error bubble.
if (this.errorBubble_) {
this.showErrorBubble(this.errorBubble_[0], this.errorBubble_[1]);
this.errorBubble_ = undefined;
}
chrome.send('loginWebuiReady');
chrome.send('loginVisible', ['gaia-signin']);
// Warm up the user images screen.
Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER});
},
/**
* Invoked when the user has successfully authenticated via SAML, the
* principals API was not used and the auth host needs the user to confirm
* the scraped password.
* @param {number} passwordCount The number of passwords that were scraped.
* @private
*/
onAuthConfirmPassword_: function(passwordCount) {
this.loading = true;
Oobe.getInstance().headerHidden = false;
if (this.samlPasswordConfirmAttempt_ == 0)
chrome.send('scrapedPasswordCount', [passwordCount]);
if (this.samlPasswordConfirmAttempt_ < 2) {
login.ConfirmPasswordScreen.show(
this.samlPasswordConfirmAttempt_,
this.onConfirmPasswordCollected_.bind(this));
} else {
chrome.send('scrapedPasswordVerificationFailed');
this.showFatalAuthError(
loadTimeData.getString('fatalErrorMessageVerificationFailed'));
}
},
/**
* Invoked when the confirm password screen is dismissed.
* @private
*/
onConfirmPasswordCollected_: function(password) {
this.samlPasswordConfirmAttempt_++;
this.gaiaAuthHost_.verifyConfirmedPassword(password);
// Shows signin UI again without changing states.
Oobe.showScreen({id: SCREEN_GAIA_SIGNIN});
},
/**
* Inovked when the user has successfully authenticated via SAML, the
* principals API was not used and no passwords could be scraped.
* @param {string} email The authenticated user's e-mail.
*/
onAuthNoPassword_: function(email) {
this.showFatalAuthError(loadTimeData.getString(
'fatalErrorMessageNoPassword'));
chrome.send('scrapedPasswordCount', [0]);
},
/**
* Invoked when the authentication flow had to be aborted because content
* served over an unencrypted connection was detected. Shows a fatal error.
* This method is only called on Chrome OS, where the entire authentication
* flow is required to be encrypted.
* @param {string} url The URL that was blocked.
*/
onInsecureContentBlocked_: function(url) {
this.showFatalAuthError(loadTimeData.getStringF(
'fatalErrorMessageInsecureURL',
url));
},
/**
* Shows the fatal auth error.
* @param {string} message The error message to show.
*/
showFatalAuthError: function(message) {
login.FatalErrorScreen.show(message, Oobe.showSigninUI);
},
/**
* Show fatal auth error when information is missing from GAIA.
*/
missingGaiaInfo_: function() {
this.showFatalAuthError(
loadTimeData.getString('fatalErrorMessageNoAccountDetails'));
},
/**
* Record that SAML API was used during sign-in.
*/
samlApiUsed_: function() {
chrome.send('usingSAMLAPI');
},
/**
* Invoked when auth is completed successfully.
* @param {!Object} credentials Credentials of the completed authentication.
* @private
*/
onAuthCompleted_: function(credentials) {
if (credentials.useOffline) {
this.email = credentials.email;
chrome.send('authenticateUser',
[credentials.gaiaId,
credentials.email,
credentials.password]);
} else if (credentials.authCode) {
chrome.send('completeAuthentication',
[credentials.gaiaId,
credentials.email,
credentials.password,
credentials.authCode]);
} else {
chrome.send('completeLogin',
[credentials.gaiaId,
credentials.email,
credentials.password,
credentials.usingSAML]);
}
this.loading = true;
// Now that we're in logged in state header should be hidden.
Oobe.getInstance().headerHidden = true;
// Clear any error messages that were shown before login.
Oobe.clearErrors();
},
/**
* Clears input fields and switches to input mode.
* @param {boolean} takeFocus True to take focus.
* @param {boolean} forceOnline Whether online sign-in should be forced.
* If |forceOnline| is false previously used sign-in type will be used.
*/
reset: function(takeFocus, forceOnline) {
// Reload and show the sign-in UI if needed.
if (takeFocus) {
if (!forceOnline && this.isLocal) {
// Show 'Cancel' button to allow user to return to the main screen
// (e.g. this makes sense when connection is back).
Oobe.getInstance().headerHidden = false;
$('login-header-bar').signinUIState = SIGNIN_UI_STATE.GAIA_SIGNIN;
// Do nothing, since offline version is reloaded after an error comes.
} else {
Oobe.showSigninUI();
}
}
},
/**
* Reloads extension frame.
*/
doReload: function() {
this.error_ = 0;
this.gaiaAuthHost_.reload();
this.loading = true;
this.startLoadingTimer_();
},
/**
* Updates localized content of the screen that is not updated via template.
*/
updateLocalizedContent: function() {
$('createAccount').innerHTML = loadTimeData.getStringF(
'createAccount',
'<a id="createAccountLink" class="signin-link" href="#">',
'</a>');
$('guestSignin').innerHTML = loadTimeData.getStringF(
'guestSignin',
'<a id="guestSigninLink" class="signin-link" href="#">',
'</a>');
$('createSupervisedUserLinkPlaceholder').innerHTML =
loadTimeData.getStringF(
'createSupervisedUser',
'<a id="createSupervisedUserLink" class="signin-link" href="#">',
'</a>');
$('consumerManagementEnrollment').innerHTML = loadTimeData.getString(
'consumerManagementEnrollmentSigninMessage');
$('createAccountLink').addEventListener('click', function(e) {
chrome.send('createAccount');
e.preventDefault();
});
$('guestSigninLink').addEventListener('click', function(e) {
chrome.send('launchIncognito');
e.preventDefault();
});
$('createSupervisedUserLink').addEventListener('click', function(e) {
chrome.send('showSupervisedUserCreationScreen');
e.preventDefault();
});
},
/**
* Shows sign-in error bubble.
* @param {number} loginAttempts Number of login attemps tried.
* @param {HTMLElement} content Content to show in bubble.
*/
showErrorBubble: function(loginAttempts, error) {
if (this.isLocal) {
$('add-user-button').hidden = true;
$('cancel-add-user-button').hidden = false;
// Reload offline version of the sign-in extension, which will show
// error itself.
chrome.send('offlineLogin', [this.email]);
} else if (!this.loading) {
// We want to show bubble near "Email" field, but we can't calculate
// it's position because it is located inside iframe. So we only
// can hardcode some constants.
/** @const */ var ERROR_BUBBLE_OFFSET = 84;
/** @const */ var ERROR_BUBBLE_PADDING = 0;
$('bubble').showContentForElement($('login-box'),
cr.ui.Bubble.Attachment.LEFT,
error,
ERROR_BUBBLE_OFFSET,
ERROR_BUBBLE_PADDING);
} else {
// Defer the bubble until the frame has been loaded.
this.errorBubble_ = [loginAttempts, error];
}
},
/**
* Called when user canceled signin.
*/
cancel: function() {
if (!this.cancelAllowed_) {
// In OOBE signin screen, cancel is not allowed because there is
// no other screen to show. If user is in middle of a saml flow,
// reset signin screen to get out of the saml flow.
if (this.isSAML())
Oobe.resetSigninUI(true);
return;
}
$('pod-row').loadLastWallpaper();
Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
Oobe.resetSigninUI(true);
},
/**
* Handler for iframe's error notification coming from the outside.
* For more info see C++ class 'WebUILoginView' which calls this method.
* @param {number} error Error code.
* @param {string} url The URL that failed to load.
*/
onFrameError: function(error, url) {
this.error_ = error;
chrome.send('frameLoadingCompleted', [this.error_]);
},
};
});