| // 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. |
| */ |
| |
| login.createScreen('GaiaSigninScreen', 'gaia-signin', function() { |
| // GAIA animation guard timer. Started when GAIA page is loaded |
| // (Authenticator 'ready' event) and is intended to guard against edge cases |
| // when 'showView' message is not generated/received. |
| /** @const */ var GAIA_ANIMATION_GUARD_MILLISEC = 300; |
| |
| // Maximum Gaia loading time in seconds. |
| /** @const */ var MAX_GAIA_LOADING_TIME_SEC = 60; |
| |
| // The help topic regarding user not being in the whitelist. |
| /** @const */ var HELP_CANT_ACCESS_ACCOUNT = 188036; |
| |
| // Amount of time the user has to be idle for before showing the online login |
| // page. |
| /** @const */ var IDLE_TIME_UNTIL_EXIT_OFFLINE_IN_MILLISECONDS = 180 * 1000; |
| |
| // Approximate amount of time between checks to see if we should go to the |
| // online login page when we're in the offline login page and the device is |
| // online. |
| /** @const */ var IDLE_TIME_CHECK_FREQUENCY = 5 * 1000; |
| |
| // Amount of time allowed for video based SAML logins, to prevent a site |
| // from keeping the camera on indefinitely. This is a hard deadline and |
| // it will not be extended by user activity. |
| /** @const */ var VIDEO_LOGIN_TIMEOUT = 90 * 1000; |
| |
| // Horizontal padding for the error bubble. |
| /** @const */ var BUBBLE_HORIZONTAL_PADDING = 65; |
| |
| // Vertical padding for the error bubble. |
| /** @const */ var BUBBLE_VERTICAL_PADDING = -213; |
| |
| /** |
| * The modes this screen can be in. |
| * @enum {integer} |
| */ |
| var ScreenMode = { |
| DEFAULT: 0, // Default GAIA login flow. |
| OFFLINE: 1, // GAIA offline login. |
| SAML_INTERSTITIAL: 2, // Interstitial page before SAML redirection. |
| AD_AUTH: 3 // Offline Active Directory login flow. |
| }; |
| |
| return { |
| EXTERNAL_API: [ |
| 'loadAuthExtension', |
| 'doReload', |
| 'monitorOfflineIdle', |
| 'updateControlsState', |
| 'showWhitelistCheckFailedError', |
| 'invalidateAd', |
| ], |
| |
| /** |
| * Saved gaia auth host load params. |
| * @type {?string} |
| * @private |
| */ |
| gaiaAuthParams_: null, |
| |
| /** |
| * Current mode of this screen. |
| * @type {integer} |
| * @private |
| */ |
| screenMode_: ScreenMode.DEFAULT, |
| |
| /** |
| * Email of the user, which is logging in using offline mode. |
| * @type {string} |
| */ |
| email: '', |
| |
| /** |
| * Timer id of pending load. |
| * @type {number} |
| * @private |
| */ |
| loadingTimer_: undefined, |
| |
| /** |
| * Timer id of a guard timer that is fired in case 'showView' message |
| * is not received from GAIA. |
| * @type {number} |
| * @private |
| */ |
| loadAnimationGuardTimer_: undefined, |
| |
| /** |
| * Timer id of the video login timer. |
| * @type {number} |
| * @private |
| */ |
| videoTimer_: undefined, |
| |
| /** |
| * Whether we've processed 'showView' message - either from GAIA or from |
| * guard timer. |
| * @type {boolean} |
| * @private |
| */ |
| showViewProcessed_: false, |
| |
| /** |
| * Whether we've processed 'authCompleted' message. |
| * @type {boolean} |
| * @private |
| */ |
| authCompleted_: false, |
| |
| /** |
| * Value contained in the last received 'backButton' event. |
| * @type {boolean} |
| * @private |
| */ |
| lastBackMessageValue_: false, |
| |
| /** |
| * Flag for tests that saml page was loaded. |
| * @type {boolean} |
| */ |
| samlInterstitialPageReady: false, |
| |
| /** |
| * Whether the dialog could be closed. |
| * This is being checked in cancel() when user clicks on signin-back-button |
| * (normal gaia flow) or the buttons in gaia-navigation (used in enterprise |
| * enrollment) etc. |
| * This value also controls the visibility of refresh button and close |
| * button in the gaia-navigation. |
| * @type {boolean} |
| */ |
| get closable() { |
| return Oobe.getInstance().hasUserPods || this.isOffline(); |
| }, |
| |
| /** |
| * Returns true if GAIA is at the begging of flow (i.e. the email page). |
| * @type {boolean} |
| */ |
| isAtTheBeginning: function() { |
| return !this.navigation_.backVisible && !this.isSAML() && |
| !this.classList.contains('whitelist-error') && !this.authCompleted_; |
| }, |
| |
| /** |
| * Updates visibility of navigation buttons. |
| */ |
| updateControlsState: function() { |
| var isWhitelistError = this.classList.contains('whitelist-error'); |
| |
| this.navigation_.backVisible = this.lastBackMessageValue_ && |
| !isWhitelistError && !this.authCompleted_ && !this.loading && |
| !this.isSAML(); |
| |
| this.navigation_.refreshVisible = !this.closable && |
| this.isAtTheBeginning() && |
| this.screenMode_ != ScreenMode.SAML_INTERSTITIAL; |
| |
| this.navigation_.closeVisible = !this.navigation_.refreshVisible && |
| !isWhitelistError && !this.authCompleted_ && |
| this.screenMode_ != ScreenMode.SAML_INTERSTITIAL; |
| |
| let showGuestInOobe = !this.closable && this.isAtTheBeginning(); |
| chrome.send('showGuestInOobe', [showGuestInOobe]); |
| }, |
| |
| /** |
| * SAML password confirmation attempt count. |
| * @type {number} |
| */ |
| samlPasswordConfirmAttempt_: 0, |
| |
| /** |
| * Do we currently have a setTimeout task running that tries to bring us |
| * back to the online login page after the user has idled for awhile? If so, |
| * then this id will be non-negative. |
| */ |
| tryToGoToOnlineLoginPageCallbackId_: -1, |
| |
| /** |
| * The most recent period of time that the user has interacted. This is |
| * only updated when the offline page is active and the device is online. |
| */ |
| mostRecentUserActivity_: Date.now(), |
| |
| /** |
| * An element containg navigation buttons. |
| */ |
| navigation_: undefined, |
| |
| |
| /** |
| * This is a copy of authenticator object attribute. |
| * UI is tied to API version, so we adjust authernticator container |
| * to match the API version. |
| * Note that this cannot be changed after authenticator is created. |
| */ |
| chromeOSApiVersion_: 2, |
| |
| /** @override */ |
| decorate: function() { |
| this.navigation_ = $('gaia-navigation'); |
| |
| this.gaiaAuthHost_ = new cr.login.GaiaAuthHost($('signin-frame')); |
| this.gaiaAuthHost_.addEventListener( |
| 'ready', this.onAuthReady_.bind(this)); |
| |
| var that = this; |
| [this.gaiaAuthHost_, $('offline-gaia'), $('offline-ad-auth')].forEach( |
| function(frame) { |
| // Ignore events from currently inactive frame. |
| var frameFilter = function(callback) { |
| return function(e) { |
| var currentFrame = null; |
| switch (that.screenMode_) { |
| case ScreenMode.DEFAULT: |
| case ScreenMode.SAML_INTERSTITIAL: |
| currentFrame = that.gaiaAuthHost_; |
| break; |
| case ScreenMode.OFFLINE: |
| currentFrame = $('offline-gaia'); |
| break; |
| case ScreenMode.AD_AUTH: |
| currentFrame = $('offline-ad-auth'); |
| break; |
| } |
| if (frame === currentFrame) |
| callback.call(that, e); |
| }; |
| }; |
| |
| frame.addEventListener( |
| 'authCompleted', frameFilter(that.onAuthCompletedMessage_)); |
| frame.addEventListener( |
| 'backButton', frameFilter(that.onBackButton_)); |
| frame.addEventListener( |
| 'dialogShown', frameFilter(that.onDialogShown_)); |
| frame.addEventListener( |
| 'dialogHidden', frameFilter(that.onDialogHidden_)); |
| frame.addEventListener( |
| 'menuItemClicked', frameFilter(that.onMenuItemClicked_)); |
| }); |
| |
| this.gaiaAuthHost_.addEventListener( |
| 'showView', this.onShowView_.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_.getIsSamlUserPasswordlessCallback = |
| this.getIsSamlUserPasswordless_.bind(this); |
| this.gaiaAuthHost_.addEventListener( |
| 'authDomainChange', this.onAuthDomainChange_.bind(this)); |
| this.gaiaAuthHost_.addEventListener( |
| 'authFlowChange', this.onAuthFlowChange_.bind(this)); |
| this.gaiaAuthHost_.addEventListener( |
| 'videoEnabledChange', this.onVideoEnabledChange_.bind(this)); |
| |
| this.gaiaAuthHost_.addEventListener( |
| 'loadAbort', this.onLoadAbortMessage_.bind(this)); |
| this.gaiaAuthHost_.addEventListener( |
| 'identifierEntered', this.onIdentifierEnteredMessage_.bind(this)); |
| |
| this.navigation_.addEventListener( |
| 'back', this.onBackButtonClicked_.bind(this, null)); |
| |
| $('signin-back-button') |
| .addEventListener('tap', this.onBackButtonClicked_.bind(this, true)); |
| $('offline-gaia') |
| .addEventListener('offline-gaia-cancel', this.cancel.bind(this)); |
| |
| this.navigation_.addEventListener('close', function() { |
| this.cancel(); |
| }.bind(this)); |
| this.navigation_.addEventListener('refresh', function() { |
| this.cancel(); |
| }.bind(this)); |
| |
| $('gaia-whitelist-error').addEventListener('buttonclick', function() { |
| this.showWhitelistCheckFailedError(false); |
| }.bind(this)); |
| |
| $('gaia-whitelist-error').addEventListener('linkclick', function() { |
| chrome.send('launchHelpApp', [HELP_CANT_ACCESS_ACCOUNT]); |
| }); |
| |
| // Register handlers for the saml interstitial page events. |
| $('saml-interstitial') |
| .addEventListener('samlPageNextClicked', function() { |
| this.screenMode = ScreenMode.DEFAULT; |
| this.loadGaiaAuthHost_(true /* doSamlRedirect */); |
| }.bind(this)); |
| $('saml-interstitial') |
| .addEventListener('samlPageChangeAccountClicked', function() { |
| // The user requests to change the account. We must clear the email |
| // field of the auth params. |
| this.gaiaAuthParams_.email = ''; |
| this.screenMode = ScreenMode.DEFAULT; |
| this.loadGaiaAuthHost_(false /* doSamlRedirect */); |
| }.bind(this)); |
| |
| $('offline-ad-auth').addEventListener('cancel', function() { |
| this.cancel(); |
| }.bind(this)); |
| }, |
| |
| /** |
| * Handles clicks on "Back" button. |
| * @param {boolean} isDialogButton If event comes from gaia-dialog. |
| */ |
| onBackButtonClicked_: function(isDialogButton) { |
| if (isDialogButton && !this.navigation_.backVisible) { |
| this.cancel(); |
| } else { |
| this.navigation_.backVisible = false; |
| this.getSigninFrame_().back(); |
| } |
| }, |
| |
| /** |
| * Loads the authenticator and updates the UI to reflect the loading state. |
| * @param {boolean} doSamlRedirect If the authenticator should do |
| * authentication by automatic redirection to the SAML-based enrollment |
| * enterprise domain IdP. |
| */ |
| loadGaiaAuthHost_: function(doSamlRedirect) { |
| this.loading = true; |
| this.startLoadingTimer_(); |
| |
| this.gaiaAuthParams_.doSamlRedirect = doSamlRedirect; |
| this.gaiaAuthHost_.load( |
| cr.login.GaiaAuthHost.AuthMode.DEFAULT, this.gaiaAuthParams_); |
| }, |
| |
| /** |
| * Header text of the screen. |
| * @type {string} |
| */ |
| get header() { |
| return loadTimeData.getString('signinScreenTitle'); |
| }, |
| |
| /** |
| * Returns true if offline version of Gaia is used. |
| * @type {boolean} |
| */ |
| isOffline: function() { |
| return this.screenMode_ == ScreenMode.OFFLINE; |
| }, |
| |
| /** |
| * Sets the current screen mode and updates the visible elements |
| * accordingly. |
| * @param {integer} value The screen mode. |
| */ |
| set screenMode(value) { |
| this.screenMode_ = value; |
| switch (this.screenMode_) { |
| case ScreenMode.DEFAULT: |
| $('signin-frame-dialog').hidden = false; |
| $('signin-frame').hidden = false; |
| $('offline-gaia').hidden = true; |
| $('saml-interstitial').hidden = true; |
| $('offline-ad-auth').hidden = true; |
| break; |
| case ScreenMode.OFFLINE: |
| $('signin-frame-dialog').hidden = true; |
| $('signin-frame').hidden = true; |
| $('offline-gaia').hidden = false; |
| $('saml-interstitial').hidden = true; |
| $('offline-ad-auth').hidden = true; |
| break; |
| case ScreenMode.AD_AUTH: |
| $('signin-frame-dialog').hidden = true; |
| $('signin-frame').hidden = true; |
| $('offline-gaia').hidden = true; |
| $('saml-interstitial').hidden = true; |
| $('offline-ad-auth').hidden = false; |
| break; |
| case ScreenMode.SAML_INTERSTITIAL: |
| $('signin-frame-dialog').hidden = true; |
| $('signin-frame').hidden = true; |
| $('offline-gaia').hidden = true; |
| $('saml-interstitial').hidden = false; |
| $('offline-ad-auth').hidden = true; |
| break; |
| } |
| this.updateSigninFrameContainers_(); |
| chrome.send('updateOfflineLogin', [this.isOffline()]); |
| this.updateControlsState(); |
| }, |
| |
| /** |
| * This enables or disables trying to go back to the online login page |
| * after the user is idle for a few minutes, assuming that we're currently |
| * in the offline one. This is only applicable when the offline page is |
| * currently active. It is intended that when the device goes online, this |
| * gets called with true; when it goes offline, this gets called with |
| * false. |
| */ |
| monitorOfflineIdle: function(shouldMonitor) { |
| var ACTIVITY_EVENTS = ['click', 'mousemove', 'keypress']; |
| var self = this; |
| |
| // updateActivityTime_ is used as a callback for addEventListener, so we |
| // need the exact reference for removeEventListener. Because the callback |
| // needs to access the |this| as scoped inside of this function, we create |
| // a closure that uses the appropriate |this|. |
| // |
| // Unfourtantely, we cannot define this function inside of the JSON object |
| // as then we have to no way to create to capture the correct |this| |
| // reference. We define it here instead. |
| if (!self.updateActivityTime_) { |
| self.updateActivityTime_ = function() { |
| self.mostRecentUserActivity_ = Date.now(); |
| }; |
| } |
| |
| // Begin monitoring. |
| if (shouldMonitor) { |
| // If we're not using the offline login page or we're already |
| // monitoring, then we don't need to do anything. |
| if (!self.isOffline() || |
| self.tryToGoToOnlineLoginPageCallbackId_ !== -1) { |
| return; |
| } |
| |
| self.mostRecentUserActivity_ = Date.now(); |
| ACTIVITY_EVENTS.forEach(function(event) { |
| document.addEventListener(event, self.updateActivityTime_); |
| }); |
| |
| self.tryToGoToOnlineLoginPageCallbackId_ = setInterval(function() { |
| // If we're not in the offline page or the signin page, then we want |
| // to terminate monitoring. |
| if (!self.isOffline() || |
| Oobe.getInstance().currentScreen.id != 'gaia-signin') { |
| self.monitorOfflineIdle(false); |
| return; |
| } |
| |
| var idleDuration = Date.now() - self.mostRecentUserActivity_; |
| if (idleDuration > IDLE_TIME_UNTIL_EXIT_OFFLINE_IN_MILLISECONDS) { |
| self.monitorOfflineIdle(false); |
| Oobe.resetSigninUI(true); |
| } |
| }, IDLE_TIME_CHECK_FREQUENCY); |
| } |
| |
| // Stop monitoring. |
| else { |
| // We're not monitoring, so we don't need to do anything. |
| if (self.tryToGoToOnlineLoginPageCallbackId_ === -1) |
| return; |
| |
| ACTIVITY_EVENTS.forEach(function(event) { |
| document.removeEventListener(event, self.updateActivityTime_); |
| }); |
| clearInterval(self.tryToGoToOnlineLoginPageCallbackId_); |
| self.tryToGoToOnlineLoginPageCallbackId_ = -1; |
| } |
| }, |
| |
| /** |
| * Shows/hides loading UI. |
| * @param {boolean} show True to show loading UI. |
| * @private |
| */ |
| showLoadingUI_: function(show) { |
| $('gaia-loading').hidden = !show; |
| |
| // Only set hidden for offline-gaia or saml-interstitial and not set it on |
| // the 'sign-frame' webview element. Setting it on webview not only hides |
| // but also affects its loading events. |
| if (this.screenMode_ != ScreenMode.DEFAULT) |
| this.getSigninFrame_().hidden = show; |
| this.getSigninFrame_().classList.toggle('show', !show); |
| this.classList.toggle('loading', show); |
| this.updateControlsState(); |
| }, |
| |
| /** |
| * Handler for Gaia loading timeout. |
| * @private |
| */ |
| onLoadingTimeOut_: function() { |
| if (this != Oobe.getInstance().currentScreen) |
| return; |
| this.loadingTimer_ = undefined; |
| chrome.send('showLoadingTimeoutError'); |
| }, |
| |
| /** |
| * Clears loading timer. |
| * @private |
| */ |
| clearLoadingTimer_: function() { |
| if (this.loadingTimer_) { |
| clearTimeout(this.loadingTimer_); |
| this.loadingTimer_ = undefined; |
| } |
| }, |
| |
| /** |
| * Sets up loading timer. |
| * @private |
| */ |
| startLoadingTimer_: function() { |
| this.clearLoadingTimer_(); |
| this.loadingTimer_ = setTimeout( |
| this.onLoadingTimeOut_.bind(this), MAX_GAIA_LOADING_TIME_SEC * 1000); |
| }, |
| |
| /** |
| * Handler for GAIA animation guard timer. |
| * @private |
| */ |
| onLoadAnimationGuardTimer_: function() { |
| this.loadAnimationGuardTimer_ = undefined; |
| this.onShowView_(); |
| }, |
| |
| /** |
| * Clears GAIA animation guard timer. |
| * @private |
| */ |
| clearLoadAnimationGuardTimer_: function() { |
| if (this.loadAnimationGuardTimer_) { |
| clearTimeout(this.loadAnimationGuardTimer_); |
| this.loadAnimationGuardTimer_ = undefined; |
| } |
| }, |
| |
| /** |
| * Sets up GAIA animation guard timer. |
| * @private |
| */ |
| startLoadAnimationGuardTimer_: function() { |
| this.clearLoadAnimationGuardTimer_(); |
| this.loadAnimationGuardTimer_ = setTimeout( |
| this.onLoadAnimationGuardTimer_.bind(this), |
| GAIA_ANIMATION_GUARD_MILLISEC); |
| }, |
| |
| /** |
| * 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) { |
| this.screenMode = ScreenMode.DEFAULT; |
| this.loading = true; |
| chrome.send('loginUIStateChanged', ['gaia-signin', true]); |
| Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.GAIA_SIGNIN); |
| |
| // Ensure that GAIA signin (or loading UI) is actually visible. |
| window.requestAnimationFrame(function() { |
| chrome.send('loginVisible', ['gaia-loading']); |
| }); |
| |
| // Re-enable navigation in case it was disabled before refresh. |
| this.navigationDisabled_ = false; |
| |
| this.lastBackMessageValue_ = false; |
| this.updateControlsState(); |
| |
| $('offline-ad-auth').onBeforeShow(); |
| return $('signin-frame-dialog').onBeforeShow(); |
| }, |
| |
| get navigationDisabled_() { |
| return this.navigation_.disabled; |
| }, |
| |
| set navigationDisabled_(value) { |
| this.navigation_.disabled = value; |
| |
| if (value) |
| $('gaia-screen-buttons-overlay').removeAttribute('hidden'); |
| else |
| $('gaia-screen-buttons-overlay').setAttribute('hidden', null); |
| |
| if ($('signin-back-button')) |
| $('signin-back-button').disabled = value; |
| }, |
| |
| getSigninFrame_: function() { |
| switch (this.screenMode_) { |
| case ScreenMode.DEFAULT: |
| return $('signin-frame'); |
| case ScreenMode.OFFLINE: |
| return $('offline-gaia'); |
| case ScreenMode.AD_AUTH: |
| return $('offline-ad-auth'); |
| case ScreenMode.SAML_INTERSTITIAL: |
| return $('saml-interstitial'); |
| } |
| }, |
| |
| focusSigninFrame: function() { |
| this.getSigninFrame_().focus(); |
| }, |
| |
| onAfterShow: function() { |
| if (!this.loading) |
| this.focusSigninFrame(); |
| }, |
| |
| /** |
| * Event handler that is invoked just before the screen is hidden. |
| */ |
| onBeforeHide: function() { |
| chrome.send('loginUIStateChanged', ['gaia-signin', false]); |
| Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.HIDDEN); |
| $('offline-gaia').switchToEmailCard(false /* animated */); |
| }, |
| |
| /** |
| * Copies attributes between nodes. |
| * @param {!Object} fromNode source to copy attributes from |
| * @param {!Object} toNode target to copy attributes to |
| * @param {!Set<string>} skipAttributes specifies attributes to be skipped |
| * @private |
| */ |
| copyAttributes_: function(fromNode, toNode, skipAttributes) { |
| for (var i = 0; i < fromNode.attributes.length; ++i) { |
| var attribute = fromNode.attributes[i]; |
| if (!skipAttributes.has(attribute.nodeName)) |
| toNode.setAttribute(attribute.nodeName, attribute.nodeValue); |
| } |
| }, |
| |
| /** |
| * Changes the 'partition' attribute of the sign-in frame. If the sign-in |
| * frame has already navigated, this function re-creates it. |
| * @param {string} newWebviewPartitionName the new partition |
| * @private |
| */ |
| setSigninFramePartition_: function(newWebviewPartitionName) { |
| var signinFrame = $('signin-frame'); |
| |
| if (!signinFrame.src) { |
| // We have not navigated anywhere yet. Note that a webview's src |
| // attribute does not allow a change back to "". |
| signinFrame.partition = newWebviewPartitionName; |
| } else if (signinFrame.partition != newWebviewPartitionName) { |
| // The webview has already navigated. We have to re-create it. |
| var signinFrameParent = signinFrame.parentElement; |
| |
| // Copy all attributes except for partition and src from the previous |
| // webview. Use the specified |newWebviewPartitionName|. |
| var newSigninFrame = document.createElement('webview'); |
| this.copyAttributes_( |
| signinFrame, newSigninFrame, new Set(['src', 'partition'])); |
| newSigninFrame.partition = newWebviewPartitionName; |
| |
| signinFrameParent.replaceChild(newSigninFrame, signinFrame); |
| |
| // Make sure the auth host uses the new webview from now on. |
| this.gaiaAuthHost_.rebindWebview($('signin-frame')); |
| } |
| }, |
| |
| /** |
| * Loads the authentication extension into the iframe. |
| * @param {Object} data Extension parameters bag. |
| * @private |
| */ |
| loadAuthExtension: function(data) { |
| // Redirect the webview to the blank page in order to stop the SAML IdP |
| // page from working in a background (see crbug.com/613245). |
| if (this.screenMode_ == ScreenMode.DEFAULT && |
| data.screenMode != ScreenMode.DEFAULT) { |
| this.gaiaAuthHost_.resetWebview(); |
| } |
| |
| this.setSigninFramePartition_(data.webviewPartitionName); |
| |
| // Must be set before calling updateSigninFrameContainers_() |
| this.chromeOSApiVersion_ = data.chromeOSApiVersion; |
| // This triggers updateSigninFrameContainers_() |
| this.screenMode = data.screenMode; |
| this.email = ''; |
| this.authCompleted_ = false; |
| this.lastBackMessageValue_ = false; |
| |
| // Reset SAML |
| this.classList.toggle('full-width', false); |
| $('saml-notice-container').hidden = true; |
| this.samlPasswordConfirmAttempt_ = 0; |
| |
| if (this.chromeOSApiVersion_ == 2) { |
| $('signin-frame-container-v2').appendChild($('signin-frame')); |
| $('gaia-signin') |
| .insertBefore($('offline-gaia'), $('gaia-step-contents')); |
| $('offline-gaia').removeAttribute('not-a-dialog'); |
| $('offline-gaia').classList.toggle('fit', false); |
| $('gaia-signin') |
| .insertBefore($('offline-ad-auth'), $('gaia-step-contents')); |
| $('offline-ad-auth').removeAttribute('not-a-dialog'); |
| $('offline-ad-auth').classList.toggle('fit', false); |
| } else { |
| $('gaia-signin-form-container').appendChild($('signin-frame')); |
| $('gaia-signin-form-container') |
| .appendChild($('offline-gaia'), $('gaia-step-contents')); |
| $('offline-gaia').setAttribute('not-a-dialog', true); |
| $('offline-gaia').classList.toggle('fit', true); |
| } |
| |
| this.updateSigninFrameContainers_(); |
| |
| // Screen size could have been changed because of 'full-width' classes. |
| if (Oobe.getInstance().currentScreen === this) |
| Oobe.getInstance().updateScreenSize(this); |
| |
| 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]; |
| } |
| |
| params.isNewGaiaFlow = true; |
| params.doSamlRedirect = |
| (this.screenMode_ == ScreenMode.SAML_INTERSTITIAL); |
| params.menuGuestMode = data.guestSignin; |
| params.menuKeyboardOptions = false; |
| params.menuEnterpriseEnrollment = |
| !(data.enterpriseManagedDevice || data.hasDeviceOwner); |
| params.isFirstUser = |
| !(data.enterpriseManagedDevice || data.hasDeviceOwner); |
| params.obfuscatedOwnerId = data.obfuscatedOwnerId; |
| |
| this.gaiaAuthParams_ = params; |
| |
| switch (this.screenMode_) { |
| case ScreenMode.DEFAULT: |
| this.loadGaiaAuthHost_(false /* doSamlRedirect */); |
| break; |
| |
| case ScreenMode.OFFLINE: |
| this.loadOffline(params); |
| break; |
| |
| case ScreenMode.AD_AUTH: |
| this.loadAdAuth(params); |
| break; |
| |
| case ScreenMode.SAML_INTERSTITIAL: |
| $('saml-interstitial').domain = data.enterpriseDisplayDomain; |
| if (this.loading) |
| this.loading = false; |
| // This event is for the browser tests. |
| this.samlInterstitialPageReady = true; |
| $('saml-interstitial').fire('samlInterstitialPageReady'); |
| break; |
| } |
| this.updateControlsState(); |
| chrome.send('authExtensionLoaded'); |
| }, |
| |
| /** |
| * Displays correct screen container for given mode and APi version. |
| */ |
| updateSigninFrameContainers_: function() { |
| let oldState = this.classList.contains('v2'); |
| this.classList.toggle('v2', false); |
| if ((this.screenMode_ == ScreenMode.DEFAULT || |
| this.screenMode_ == ScreenMode.OFFLINE || |
| this.screenMode_ == ScreenMode.AD_AUTH) && |
| this.chromeOSApiVersion_ == 2) { |
| this.classList.toggle('v2', true); |
| } |
| if (this != Oobe.getInstance().currentScreen) |
| return; |
| // Switching between signin-frame-dialog and gaia-step-contents |
| // updates screen size. |
| if (oldState != this.classList.contains('v2')) |
| Oobe.getInstance().updateScreenSize(this); |
| }, |
| |
| /** |
| * Whether the current auth flow is SAML. |
| */ |
| isSAML: function() { |
| return this.gaiaAuthHost_.authFlow == cr.login.GaiaAuthHost.AuthFlow.SAML; |
| }, |
| |
| /** |
| * Helper function to update the title bar. |
| */ |
| updateSamlNotice_: function() { |
| if (this.gaiaAuthHost_.videoEnabled) { |
| $('saml-notice-message').textContent = loadTimeData.getStringF( |
| 'samlNoticeWithVideo', this.gaiaAuthHost_.authDomain); |
| $('saml-notice-recording-indicator').hidden = false; |
| $('saml-notice-container').style.justifyContent = 'flex-start'; |
| } else { |
| $('saml-notice-message').textContent = loadTimeData.getStringF( |
| 'samlNotice', this.gaiaAuthHost_.authDomain); |
| $('saml-notice-recording-indicator').hidden = true; |
| $('saml-notice-container').style.justifyContent = 'center'; |
| } |
| }, |
| |
| /** |
| * Clean up from a video-enabled SAML flow. |
| */ |
| clearVideoTimer_: function() { |
| if (this.videoTimer_ !== undefined) { |
| clearTimeout(this.videoTimer_); |
| this.videoTimer_ = undefined; |
| } |
| }, |
| |
| /** |
| * Invoked when the authDomain property is changed on the GAIA host. |
| */ |
| onAuthDomainChange_: function() { |
| this.updateSamlNotice_(); |
| }, |
| |
| /** |
| * Invoked when the videoEnabled property is changed on the GAIA host. |
| */ |
| onVideoEnabledChange_: function() { |
| this.updateSamlNotice_(); |
| if (this.gaiaAuthHost_.videoEnabled && this.videoTimer_ === undefined) { |
| this.videoTimer_ = |
| setTimeout(this.cancel.bind(this), VIDEO_LOGIN_TIMEOUT); |
| } else { |
| this.clearVideoTimer_(); |
| } |
| }, |
| |
| /** |
| * Invoked when the authFlow property is changed on the GAIA host. |
| */ |
| onAuthFlowChange_: function() { |
| var isSAML = this.isSAML(); |
| |
| this.classList.toggle('full-width', isSAML); |
| $('saml-notice-container').hidden = !isSAML; |
| this.classList.toggle('saml', isSAML); |
| |
| if (Oobe.getInstance().currentScreen === this) { |
| Oobe.getInstance().updateScreenSize(this); |
| } |
| |
| this.updateControlsState(); |
| }, |
| |
| /** |
| * Invoked when the auth host emits 'ready' event. |
| * @private |
| */ |
| onAuthReady_: function() { |
| this.showViewProcessed_ = false; |
| this.startLoadAnimationGuardTimer_(); |
| this.clearLoadingTimer_(); |
| this.loading = false; |
| |
| if (!$('offline-gaia').hidden) |
| $('offline-gaia').focus(); |
| |
| // Warm up the user images screen. |
| Oobe.getInstance().preloadScreen({id: SCREEN_USER_IMAGE_PICKER}); |
| }, |
| |
| /** |
| * Invoked when the auth host emits 'dialogShown' event. |
| * @private |
| */ |
| onDialogShown_: function() { |
| this.navigationDisabled_ = true; |
| }, |
| |
| /** |
| * Invoked when the auth host emits 'dialogHidden' event. |
| * @private |
| */ |
| onDialogHidden_: function() { |
| this.navigationDisabled_ = false; |
| }, |
| |
| /** |
| * Invoked when user activates menu item. |
| * @private |
| */ |
| onMenuItemClicked_: function(e) { |
| if (e.detail == 'gm') { |
| Oobe.disableSigninUI(); |
| chrome.send('launchIncognito'); |
| } else if (e.detail == 'ee') { |
| cr.ui.Oobe.handleAccelerator(ACCELERATOR_ENROLLMENT); |
| } |
| }, |
| |
| /** |
| * Invoked when the the GAIA host requests whether the specified user is a |
| * user without a password (neither a manually entered one nor one provided |
| * via Credentials Passing API). |
| * @param {string} email |
| * @param {string} gaiaId |
| * @param {function(boolean)} callback |
| * @private |
| */ |
| getIsSamlUserPasswordless_: function(email, gaiaId, callback) { |
| cr.sendWithPromise('getIsSamlUserPasswordless', email, gaiaId) |
| .then(callback); |
| }, |
| |
| /** |
| * Invoked when the auth host emits 'backButton' event. |
| * @private |
| */ |
| onBackButton_: function(e) { |
| this.getSigninFrame_().focus(); |
| this.lastBackMessageValue_ = !!e.detail; |
| this.updateControlsState(); |
| }, |
| |
| /** |
| * Invoked when the auth host emits 'showView' event or when corresponding |
| * guard time fires. |
| * @private |
| */ |
| onShowView_: function(e) { |
| if (this.showViewProcessed_) |
| return; |
| |
| this.showViewProcessed_ = true; |
| this.clearLoadAnimationGuardTimer_(); |
| this.getSigninFrame_().classList.toggle('show', true); |
| this.onLoginUIVisible_(); |
| }, |
| |
| /** |
| * Called when UI is shown. |
| * @private |
| */ |
| onLoginUIVisible_: function() { |
| // 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']); |
| }, |
| |
| /** |
| * 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 {string} email The authenticated user's e-mail. |
| * @param {number} passwordCount The number of passwords that were scraped. |
| * @private |
| */ |
| onAuthConfirmPassword_: function(email, passwordCount) { |
| this.loading = true; |
| |
| if (this.samlPasswordConfirmAttempt_ == 0) |
| chrome.send('scrapedPasswordCount', [passwordCount]); |
| |
| if (this.samlPasswordConfirmAttempt_ < 2) { |
| login.ConfirmPasswordScreen.show( |
| email, false /* manual password entry */, |
| this.samlPasswordConfirmAttempt_, |
| this.onConfirmPasswordCollected_.bind(this)); |
| } else { |
| chrome.send('scrapedPasswordVerificationFailed'); |
| this.showFatalAuthError( |
| loadTimeData.getString('fatalErrorMessageVerificationFailed'), |
| loadTimeData.getString('fatalErrorTryAgainButton')); |
| } |
| this.classList.toggle('full-width', false); |
| }, |
| |
| /** |
| * 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. |
| * The user will be asked to pick a manual password for the device. |
| * @param {string} email The authenticated user's e-mail. |
| */ |
| onAuthNoPassword_: function(email) { |
| chrome.send('scrapedPasswordCount', [0]); |
| login.ConfirmPasswordScreen.show( |
| email, true /* manual password entry */, |
| this.samlPasswordConfirmAttempt_, |
| this.onManualPasswordCollected_.bind(this)); |
| }, |
| |
| /** |
| * Invoked when the dialog where the user enters a manual password for the |
| * device, when password scraping fails. |
| * @param {string} password The password the user entered. Not necessarily |
| * the same as their SAML password. |
| */ |
| onManualPasswordCollected_: function(password) { |
| this.gaiaAuthHost_.completeAuthWithManualPassword(password); |
| }, |
| |
| /** |
| * 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), |
| loadTimeData.getString('fatalErrorDoneButton')); |
| }, |
| |
| /** |
| * Shows the fatal auth error. |
| * @param {string} message The error message to show. |
| * @param {string} buttonLabel The label to display on dismiss button. |
| */ |
| showFatalAuthError: function(message, buttonLabel) { |
| login.FatalErrorScreen.show(message, buttonLabel, Oobe.showSigninUI); |
| }, |
| |
| /** |
| * Show fatal auth error when information is missing from GAIA. |
| */ |
| missingGaiaInfo_: function() { |
| this.showFatalAuthError( |
| loadTimeData.getString('fatalErrorMessageNoAccountDetails'), |
| loadTimeData.getString('fatalErrorTryAgainButton')); |
| }, |
| |
| /** |
| * 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 (this.screenMode_ == ScreenMode.AD_AUTH) { |
| this.email = credentials.username; |
| chrome.send( |
| 'completeAdAuthentication', |
| [credentials.username, credentials.password]); |
| } else if (credentials.useOffline) { |
| this.email = credentials.email; |
| chrome.send( |
| 'completeOfflineAuthentication', |
| [credentials.email, credentials.password]); |
| } else { |
| chrome.send('completeAuthentication', [ |
| credentials.gaiaId, credentials.email, credentials.password, |
| credentials.usingSAML, credentials.services |
| ]); |
| } |
| |
| this.loading = true; |
| // Hide the back button and the border line as they are not useful |
| // when the loading screen is shown. |
| $('signin-back-button').hidden = true; |
| $('signin-frame-dialog').setAttribute('hide-shadow', true); |
| |
| // Clear any error messages that were shown before login. |
| Oobe.clearErrors(); |
| |
| this.clearVideoTimer_(); |
| this.authCompleted_ = true; |
| this.updateControlsState(); |
| }, |
| |
| /** |
| * Invoked when onAuthCompleted message received. |
| * @param {!Object} e Payload of the received HTML5 message. |
| * @private |
| */ |
| onAuthCompletedMessage_: function(e) { |
| this.onAuthCompleted_(e.detail); |
| }, |
| |
| /** |
| * Invoked when onLoadAbort message received. |
| * @param {!Object} e Payload of the received HTML5 message. |
| * @private |
| */ |
| onLoadAbortMessage_: function(e) { |
| this.onWebviewError(e.detail); |
| }, |
| |
| /** |
| * Invoked when identifierEntered message received. |
| * @param {!Object} e Payload of the received HTML5 message. |
| * @private |
| */ |
| onIdentifierEnteredMessage_: function(e) { |
| this.onIdentifierEntered(e.detail); |
| }, |
| |
| /** |
| * 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. |
| this.gaiaAuthHost_.resetStates(); |
| if (takeFocus) { |
| if (!forceOnline && this.isOffline()) { |
| Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.GAIA_SIGNIN); |
| // Do nothing, since offline version is reloaded after an error comes. |
| } else { |
| Oobe.showSigninUI(); |
| } |
| } |
| }, |
| |
| /** |
| * Reloads extension frame. |
| */ |
| doReload: function() { |
| if (this.screenMode_ != ScreenMode.DEFAULT) |
| return; |
| this.gaiaAuthHost_.reload(); |
| this.loading = true; |
| this.startLoadingTimer_(); |
| this.lastBackMessageValue_ = false; |
| this.authCompleted_ = false; |
| this.updateControlsState(); |
| }, |
| |
| /** |
| * 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.isOffline()) { |
| // Reload offline version of the sign-in extension, which will show |
| // error itself. |
| chrome.send('offlineLogin', [this.email]); |
| } else if (!this.loading) { |
| // TODO(dzhioev): investigate if this branch ever get hit. |
| $('bubble').showContentForElement( |
| $('gaia-signin-form-container'), cr.ui.Bubble.Attachment.BOTTOM, |
| error, BUBBLE_HORIZONTAL_PADDING, BUBBLE_VERTICAL_PADDING); |
| } else { |
| // Defer the bubble until the frame has been loaded. |
| this.errorBubble_ = [loginAttempts, error]; |
| } |
| }, |
| |
| /** |
| * Called when user canceled signin. |
| */ |
| cancel: function() { |
| this.clearVideoTimer_(); |
| if (!this.navigation_.refreshVisible && !this.navigation_.closeVisible) |
| return; |
| |
| if (this.screenMode_ == ScreenMode.AD_AUTH) |
| chrome.send('cancelAdAuthentication'); |
| |
| if (this.closable) |
| Oobe.showUserPods(); |
| else |
| Oobe.resetSigninUI(true); |
| }, |
| |
| /** |
| * Handler for webview error handling. |
| * @param {!Object} data Additional information about error event like: |
| * {string} error Error code such as "ERR_INTERNET_DISCONNECTED". |
| * {string} url The URL that failed to load. |
| */ |
| onWebviewError: function(data) { |
| chrome.send('webviewLoadAborted', [data.error]); |
| }, |
| |
| /** |
| * Handler for identifierEntered event. |
| * @param {!Object} data The identifier entered by user: |
| * {string} accountIdentifier User identifier. |
| */ |
| onIdentifierEntered: function(data) { |
| chrome.send('identifierEntered', [data.accountIdentifier]); |
| }, |
| |
| /** |
| * Sets enterprise info strings for offline gaia. |
| * Also sets callback and sends message whether we already have email and |
| * should switch to the password screen with error. |
| */ |
| loadOffline: function(params) { |
| this.loading = true; |
| this.startLoadingTimer_(); |
| var offlineLogin = $('offline-gaia'); |
| if ('enterpriseDisplayDomain' in params) |
| offlineLogin.domain = params['enterpriseDisplayDomain']; |
| if ('emailDomain' in params) |
| offlineLogin.emailDomain = '@' + params['emailDomain']; |
| offlineLogin.setEmail(params.email); |
| this.onAuthReady_(); |
| }, |
| |
| loadAdAuth: function(params) { |
| this.loading = true; |
| this.startLoadingTimer_(); |
| var adAuthUI = this.getSigninFrame_(); |
| adAuthUI.realm = params['realm']; |
| |
| if ('emailDomain' in params) |
| adAuthUI.userRealm = '@' + params['emailDomain']; |
| |
| adAuthUI.userName = params['email']; |
| adAuthUI.focus(); |
| this.onAuthReady_(); |
| }, |
| |
| /** |
| * Show/Hide error when user is not in whitelist. When UI is hidden |
| * GAIA is reloaded. |
| * @param {boolean} show Show/hide error UI. |
| * @param {!Object} opt_data Optional additional information. |
| */ |
| showWhitelistCheckFailedError: function(show, opt_data) { |
| if (show) { |
| var isManaged = opt_data && opt_data.enterpriseManaged; |
| $('gaia-whitelist-error').textContent = loadTimeData.getValue( |
| isManaged ? 'whitelistErrorEnterprise' : 'whitelistErrorConsumer'); |
| // To make animations correct, we need to make sure Gaia is completely |
| // reloaded. Otherwise ChromeOS overlays hide and Gaia page is shown |
| // somewhere in the middle of animations. |
| if (this.screenMode_ == ScreenMode.DEFAULT) |
| this.gaiaAuthHost_.resetWebview(); |
| } |
| |
| this.classList.toggle('whitelist-error', show); |
| this.loading = !show; |
| |
| if (show) |
| $('gaia-whitelist-error').submitButton.focus(); |
| else |
| Oobe.showSigninUI(); |
| |
| this.updateControlsState(); |
| }, |
| |
| invalidateAd: function(username, errorState) { |
| if (this.screenMode_ != ScreenMode.AD_AUTH) |
| return; |
| var adAuthUI = this.getSigninFrame_(); |
| adAuthUI.userName = username; |
| adAuthUI.errorState = errorState; |
| this.authCompleted_ = false; |
| this.loading = false; |
| } |
| }; |
| }); |