blob: cca50da1a66823c7fc83c1f315a33bcc489128bb [file] [log] [blame]
// Copyright 2015 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 Polymer element for Enterprise Enrollment screen.
*/
/* Code which is embedded inside of the webview. See below for details.
/** @const */
var INJECTED_WEBVIEW_SCRIPT = String.raw`
(function() {
// <include src="../keyboard/keyboard_utils.js">
keyboard.initializeKeyboardFlow(true);
})();`;
/** @const */ var ENROLLMENT_STEP = {
SIGNIN: 'signin',
AD_JOIN: 'ad-join',
LICENSE_TYPE: 'license',
WORKING: 'working',
ATTRIBUTE_PROMPT: 'attribute-prompt',
ERROR: 'error',
SUCCESS: 'success',
/* TODO(dzhioev): define this step on C++ side.
*/
ATTRIBUTE_PROMPT_ERROR: 'attribute-prompt-error',
ACTIVE_DIRECTORY_JOIN_ERROR: 'active-directory-join-error',
};
Polymer({
is: 'enterprise-enrollment',
behaviors: [I18nBehavior, OobeDialogHostBehavior],
properties: {
/**
* Reference to OOBE screen object.
* @type {!{
* onAuthFrameLoaded_: function(),
* onAuthCompleted_: function(string),
* onAdCompleteLogin_: function(string, string, string, string, string),
* onAdUnlockConfiguration_: function(string),
* onLicenseTypeSelected_: function(string),
* closeEnrollment_: function(string),
* onAttributesEntered_: function(string, string),
* }}
*/
screen: {
type: Object,
},
},
/**
* Authenticator object that wraps GAIA webview.
*/
authenticator_: null,
/**
* The current step. This is the last value passed to showStep().
*/
currentStep_: null,
/**
* We block esc, back button and cancel button until gaia is loaded to
* prevent multiple cancel events.
*/
isCancelDisabled_: null,
get isCancelDisabled() {
return this.isCancelDisabled_;
},
set isCancelDisabled(disabled) {
this.isCancelDisabled_ = disabled;
},
isManualEnrollment_: undefined,
/**
* An element containing UI for picking license type.
* @type {EnrollmentLicenseCard}
* @private
*/
licenseUi_: undefined,
/**
* An element containing navigation buttons.
*/
navigation_: undefined,
/**
* An element containing UI to join an AD domain.
* @type {OfflineAdLoginElement}
* @private
*/
offlineAdUi_: undefined,
/**
* Value contained in the last received 'backButton' event.
* @type {boolean}
* @private
*/
lastBackMessageValue_: false,
ready: function() {
this.navigation_ = this.$['oauth-enroll-navigation'];
this.offlineAdUi_ = this.$['oauth-enroll-ad-join-ui'];
this.licenseUi_ = this.$['oauth-enroll-license-ui'];
let authView = this.$['oauth-enroll-auth-view'];
this.authenticator_ = new cr.login.Authenticator(authView);
// Establish an initial messaging between content script and
// host script so that content script can message back.
authView.addEventListener('loadstop', function(e) {
e.target.contentWindow.postMessage(
'initialMessage', authView.src);
});
// When we get the advancing focus command message from injected content
// script, we can execute it on host script context.
window.addEventListener('message', function(e) {
if (e.data == 'forwardFocus')
keyboard.onAdvanceFocus(false);
else if (e.data == 'backwardFocus')
keyboard.onAdvanceFocus(true);
});
this.authenticator_.addEventListener(
'ready', (function() {
if (this.currentStep_ != ENROLLMENT_STEP.SIGNIN)
return;
this.isCancelDisabled = false;
this.screen.onAuthFrameLoaded_();
}).bind(this));
this.authenticator_.addEventListener(
'authCompleted',
(function(e) {
var detail = e.detail;
if (!detail.email) {
this.showError(
loadTimeData.getString('fatalEnrollmentError'), false);
return;
}
this.screen.onAuthCompleted_(detail.email);
}).bind(this));
this.offlineAdUi_.addEventListener('authCompleted', function(e) {
this.offlineAdUi_.disabled = true;
this.offlineAdUi_.loading = true;
this.screen.onAdCompleteLogin_(
e.detail.machine_name,
e.detail.distinguished_name,
e.detail.encryption_types,
e.detail.username,
e.detail.password);
}.bind(this));
this.offlineAdUi_.addEventListener('unlockPasswordEntered', function(e) {
this.offlineAdUi_.disabled = true;
this.screen.onAdUnlockConfiguration_(e.detail.unlock_password);
}.bind(this));
this.authenticator_.addEventListener(
'authFlowChange', (function(e) {
var isSAML = this.authenticator_.authFlow ==
cr.login.Authenticator.AuthFlow.SAML;
if (isSAML) {
this.$['oauth-saml-notice-message'].textContent =
loadTimeData.getStringF(
'samlNotice',
this.authenticator_.authDomain);
}
this.classList.toggle('saml', isSAML);
if (Oobe.getInstance().currentScreen == this)
Oobe.getInstance().updateScreenSize(this);
this.lastBackMessageValue_ = false;
this.updateControlsState();
}).bind(this));
this.authenticator_.addEventListener(
'backButton', (function(e) {
this.lastBackMessageValue_ = !!e.detail;
this.$['oauth-enroll-auth-view'].focus();
this.updateControlsState();
}).bind(this));
this.authenticator_.addEventListener(
'dialogShown', (function(e) {
this.navigation_.disabled = true;
this.$['oobe-signin-back-button'].disabled = true;
// TODO(alemate): update the visual style.
}).bind(this));
this.authenticator_.addEventListener(
'dialogHidden', (function(e) {
this.navigation_.disabled = false;
this.$['oobe-signin-back-button'].disabled = false;
// TODO(alemate): update the visual style.
}).bind(this));
this.authenticator_.insecureContentBlockedCallback =
(function(url) {
this.showError(
loadTimeData.getStringF('insecureURLEnrollmentError', url),
false);
}).bind(this);
this.authenticator_.missingGaiaInfoCallback =
(function() {
this.showError(
loadTimeData.getString('fatalEnrollmentError'), false);
}).bind(this);
this.$['oauth-enroll-error-card']
.addEventListener('buttonclick', this.doRetry_.bind(this));
this.$['oauth-enroll-attribute-prompt-error-card']
.addEventListener(
'buttonclick', this.onEnrollmentFinished_.bind(this));
this.$['enroll-success-done-button']
.addEventListener('tap', this.onEnrollmentFinished_.bind(this));
this.$['enroll-attributes-skip-button']
.addEventListener('tap', this.onSkipButtonClicked.bind(this));
this.$['enroll-attributes-submit-button']
.addEventListener('tap', this.onAttributesSubmitted.bind(this));
this.$['oauth-enroll-active-directory-join-error-card']
.addEventListener('buttonclick', function() {
this.showStep(ENROLLMENT_STEP.AD_JOIN);
}.bind(this));
this.navigation_.addEventListener('close', this.cancel.bind(this));
this.navigation_.addEventListener('refresh', this.cancel.bind(this));
this.$['oobe-signin-back-button']
.addEventListener('tap', this.onBackButtonClicked_.bind(this));
this.$['oauth-enroll-learn-more-link']
.addEventListener('click', function(event) {
chrome.send('oauthEnrollOnLearnMore');
});
this.licenseUi_.addEventListener('buttonclick', function() {
this.screen.onLicenseTypeSelected_(this.licenseUi_.selected);
}.bind(this));
},
/**
* Event handler that is invoked just before the frame is shown.
* @param {Object} data Screen init payload, contains the signin frame
* URL.
*/
onBeforeShow: function(data) {
if (Oobe.getInstance().forceKeyboardFlow) {
// We run the tab remapping logic inside of the webview so that the
// simulated tab events will use the webview tab-stops. Simulated tab
// events created from the webui treat the entire webview as one tab
// stop. Real tab events do not do this. See crbug.com/543865.
this.$['oauth-enroll-auth-view'].addContentScripts([{
name: 'injectedTabHandler',
matches: ['http://*/*', 'https://*/*'],
js: {code: INJECTED_WEBVIEW_SCRIPT},
run_at: 'document_start'
}]);
}
this.$['oauth-enroll-auth-view'].partition = data.webviewPartitionName;
Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.ENROLLMENT);
this.classList.remove('saml');
var gaiaParams = {};
gaiaParams.gaiaUrl = data.gaiaUrl;
gaiaParams.clientId = data.clientId;
gaiaParams.needPassword = false;
gaiaParams.hl = data.hl;
if (data.management_domain) {
gaiaParams.enterpriseEnrollmentDomain = data.management_domain;
gaiaParams.emailDomain = data.management_domain;
}
gaiaParams.flow = data.flow;
this.authenticator_.load(
cr.login.Authenticator.AuthMode.DEFAULT, gaiaParams);
var modes = ['manual', 'forced', 'recovery'];
for (var i = 0; i < modes.length; ++i) {
this.classList.toggle(
'mode-' + modes[i], data.enrollment_mode == modes[i]);
}
this.isManualEnrollment_ = data.enrollment_mode === 'manual';
this.navigation_.disabled = false;
this.offlineAdUi_.onBeforeShow();
if (!this.currentStep_) {
this.showStep(data.attestationBased ?
ENROLLMENT_STEP.WORKING : ENROLLMENT_STEP.SIGNIN);
}
this.behaviors.forEach((behavior) => {
if (behavior.onBeforeShow)
behavior.onBeforeShow.call(this);
});
},
onBeforeHide: function() {
Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.HIDDEN);
},
/**
* Shows attribute-prompt step with pre-filled asset ID and
* location.
*/
showAttributePromptStep: function(annotatedAssetId, annotatedLocation) {
this.$['oauth-enroll-asset-id'].value = annotatedAssetId;
this.$['oauth-enroll-location'].value = annotatedLocation;
this.showStep(ENROLLMENT_STEP.ATTRIBUTE_PROMPT);
},
/**
* Shows a success card for attestation-based enrollment that shows
* which domain the device was enrolled into.
*/
showAttestationBasedEnrollmentSuccess: function(
device, enterpriseEnrollmentDomain) {
this.$['oauth-enroll-success-subtitle'].deviceName = device;
this.$['oauth-enroll-success-subtitle'].enrollmentDomain =
enterpriseEnrollmentDomain;
this.showStep(ENROLLMENT_STEP.SUCCESS);
},
/**
* Cancels the current authentication and drops the user back to the next
* screen (either the next authentication or the login screen).
*/
cancel: function() {
if (this.isCancelDisabled)
return;
this.isCancelDisabled = true;
this.screen.closeEnrollment_('cancel');
},
/**
* Updates the list of available license types in license selection dialog.
*/
setAvailableLicenseTypes: function(licenseTypes) {
var licenses = [
{
id: 'perpetual',
label: 'perpetualLicenseTypeTitle',
},
{
id: 'annual',
label: 'annualLicenseTypeTitle',
},
{
id: 'kiosk',
label: 'kioskLicenseTypeTitle',
}
];
for (var i = 0, item; item = licenses[i]; ++i) {
if (item.id in licenseTypes) {
item.count = parseInt(licenseTypes[item.id]);
item.disabled = item.count == 0;
item.hidden = false;
} else {
item.count = 0;
item.disabled = true;
item.hidden = true;
}
}
this.licenseUi_.disabled = false;
this.licenseUi_.licenses = licenses;
},
/**
* Switches between the different steps in the enrollment flow.
* @param {string} step the steps to show, one of "signin", "working",
* "attribute-prompt", "error", "success".
*/
showStep: function(step) {
let classList = this.$['oauth-enroll-step-contents'].classList;
classList.toggle('oauth-enroll-state-' + this.currentStep_, false);
classList.toggle('oauth-enroll-state-' + step, true);
this.isCancelDisabled =
(step == ENROLLMENT_STEP.SIGNIN && !this.isManualEnrollment_) ||
step == ENROLLMENT_STEP.AD_JOIN || step == ENROLLMENT_STEP.WORKING;
if (step == ENROLLMENT_STEP.SIGNIN) {
this.$['oauth-enroll-auth-view'].focus();
} else if (step == ENROLLMENT_STEP.LICENSE_TYPE) {
this.$['oauth-enroll-license-ui'].show();
} else if (step == ENROLLMENT_STEP.ERROR) {
this.$['oauth-enroll-error-card'].submitButton.focus();
} else if (step == ENROLLMENT_STEP.SUCCESS) {
this.$['oauth-enroll-success-card'].show();
} else if (step == ENROLLMENT_STEP.ATTRIBUTE_PROMPT) {
this.$['oauth-enroll-attribute-prompt-card'].show();
} else if (step == ENROLLMENT_STEP.ATTRIBUTE_PROMPT_ERROR) {
this.$['oauth-enroll-attribute-prompt-error-card'].submitButton.focus();
} else if (step == ENROLLMENT_STEP.ACTIVE_DIRECTORY_JOIN_ERROR) {
this.$['oauth-enroll-active-directory-join-error-card'].submitButton
.focus();
} else if (step == ENROLLMENT_STEP.AD_JOIN) {
this.offlineAdUi_.disabled = false;
this.offlineAdUi_.loading = false;
this.offlineAdUi_.focus();
}
this.currentStep_ = step;
this.lastBackMessageValue_ = false;
this.updateControlsState();
},
/**
* Sets an error message and switches to the error screen.
* @param {string} message the error message.
* @param {boolean} retry whether the retry link should be shown.
*/
showError: function(message, retry) {
if (this.currentStep_ == ENROLLMENT_STEP.ATTRIBUTE_PROMPT) {
this.$['oauth-enroll-attribute-prompt-error-card'].textContent = message;
this.showStep(ENROLLMENT_STEP.ATTRIBUTE_PROMPT_ERROR);
return;
}
if (this.currentStep_ == ENROLLMENT_STEP.AD_JOIN) {
this.$['oauth-enroll-active-directory-join-error-card'].textContent =
message;
this.showStep(ENROLLMENT_STEP.ACTIVE_DIRECTORY_JOIN_ERROR);
return;
}
this.$['oauth-enroll-error-card'].textContent = message;
this.$['oauth-enroll-error-card'].buttonLabel =
retry ? loadTimeData.getString('oauthEnrollRetry') : '';
this.showStep(ENROLLMENT_STEP.ERROR);
},
doReload: function() {
this.lastBackMessageValue_ = false;
this.authenticator_.reload();
this.updateControlsState();
},
/**
* Sets Active Directory join screen params.
* @param {string} machineName
* @param {string} userName
* @param {ACTIVE_DIRECTORY_ERROR_STATE} errorState
* @param {boolean} showUnlockConfig true if there is an encrypted
* configuration (and not unlocked yet).
*/
setAdJoinParams: function(
machineName, userName, errorState, showUnlockConfig) {
this.offlineAdUi_.disabled = false;
this.offlineAdUi_.machineName = machineName;
this.offlineAdUi_.userName = userName;
this.offlineAdUi_.errorState = errorState;
this.offlineAdUi_.unlockPasswordStep = showUnlockConfig;
},
/**
* Sets Active Directory join screen with the unlocked configuration.
* @param {Array<JoinConfigType>} options
*/
setAdJoinConfiguration: function(options) {
this.offlineAdUi_.disabled = false;
this.offlineAdUi_.setJoinConfigurationOptions(options);
this.offlineAdUi_.unlockPasswordStep = false;
},
/**
* Retries the enrollment process after an error occurred in a previous
* attempt. This goes to the C++ side through |chrome| first to clean up the
* profile, so that the next attempt is performed with a clean state.
*/
doRetry_: function() {
chrome.send('oauthEnrollRetry');
},
/**
* Skips the device attribute update,
* shows the successful enrollment step.
*/
onSkipButtonClicked: function() {
this.showStep(ENROLLMENT_STEP.SUCCESS);
},
/**
* Skips the device attribute update,
* shows the successful enrollment step.
*/
onBackButtonClicked_: function() {
if (this.currentStep_ == ENROLLMENT_STEP.SIGNIN) {
if (this.lastBackMessageValue_) {
this.lastBackMessageValue_ = false;
this.$['oauth-enroll-auth-view'].back();
} else {
this.cancel();
}
}
},
/**
* Uploads the device attributes to server. This goes to C++ side through
* |chrome| and launches the device attribute update negotiation.
*/
onAttributesSubmitted: function() {
this.screen.onAttributesEntered_(this.$['oauth-enroll-asset-id'].value,
this.$['oauth-enroll-location'].value);
},
/**
* Returns true if we are at the begging of enrollment flow (i.e. the email
* page).
*
* @type {boolean}
*/
isAtTheBeginning: function() {
return !this.lastBackMessageValue_ &&
this.currentStep_ == ENROLLMENT_STEP.SIGNIN;
},
/**
* Updates visibility of navigation buttons.
*/
updateControlsState: function() {
this.navigation_.refreshVisible = this.isAtTheBeginning() &&
this.isManualEnrollment_ === false;
this.navigation_.closeVisible =
(this.currentStep_ == ENROLLMENT_STEP.ERROR
&& !this.navigation_.refreshVisible) ||
this.currentStep_ == ENROLLMENT_STEP.LICENSE_TYPE;
},
/**
* Notifies chrome that enrollment have finished.
*/
onEnrollmentFinished_: function() {
this.screen.closeEnrollment_('done');
},
updateLocalizedContent: function() {
this.offlineAdUi_.i18nUpdateLocale();
}
});