Commit ce3e48b2 authored by Roman Sorokin's avatar Roman Sorokin Committed by Commit Bot

Chromad: Implement streamline enrollment flow

* Adds new step to decrypt configuration seed which comes from DM Server.
* Adds configuration selection UI.

BUG=chromium:829361
TEST=TBD

Cq-Include-Trybots: luci.chromium.try:closure_compilation
Change-Id: I299cdf7b8994fd929a7f15303bfb21ebf78996ba
Reviewed-on: https://chromium-review.googlesource.com/1090714
Commit-Queue: Roman Sorokin <rsorokin@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Reviewed-by: default avatarLutz Justen <ljusten@chromium.org>
Reviewed-by: default avatarAlexander Alekseev <alemate@chromium.org>
Cr-Commit-Position: refs/heads/master@{#568879}
parent e33c6c17
......@@ -4156,21 +4156,45 @@
Device Location
</message>
<!-- Administrator facing strings for Active Directory screens. -->
<message name="IDS_AD_MACHINE_NAME_INPUT_LABEL" desc="Admin-facing. Label for device name input field on the AD domain join screen. User should tell us the name of his device.">
<message name="IDS_AD_DEVICE_NAME_INPUT_LABEL" desc="Admin-facing. Label for device name input field on the Active Directory domain join screen. User should tell us the name of their device.">
Chromebook device name
</message>
<message name="IDS_AD_DOMAIN_JOIN_WELCOME_MESSAGE" desc="Admin-facing. Welcome message on the AD domain join screen.">
<message name="IDS_AD_DEVICE_NAME_REGEX_INPUT_LABEL" desc="Admin-facing. Label for device name input field on the Active Directory domain join screen. User should tell us the name of their device.">
Chromebook device name (<ph name="REGEX">$1<ex>^DEVICE_\d+$</ex></ph>)
</message>
<message name="IDS_AD_DOMAIN_JOIN_WELCOME_MESSAGE" desc="Admin-facing. Welcome message on the Active Directory domain join screen.">
Join device to domain
</message>
<message name="IDS_AD_MORE_OPTIONS_BUTTON" desc="Admin-facing. Text on the 'More options' button on the Active Directory domain join screen.">
More options
</message>
<message name="IDS_AD_UNLOCK_CONFIG" desc="Admin-facing. Text on the button which opens 'Enter unlocking password' dialog on the Active Directory domain join screen.">
Unlock configuration
</message>
<message name="IDS_AD_UNLOCK_CONFIG_UNLOCK_BUTTON" desc="Admin-facing. Text on the button to use entered password to unlock domain join configuration.">
Unlock
</message>
<message name="IDS_AD_UNLOCK_CONFIG_PASSWORD" desc="Admin-facing. Label for unlocking password input field on the Active Directory domain join screen.">
Unlocking password
</message>
<message name="IDS_AD_UNLOCK_INCORRECT_PASSWORD" desc="Admin-facing. Error that is shown when incorrect unlocking password was entered.">
Incorrect password
</message>
<message name="IDS_AD_UNLOCK_PASSWORD_SKIP" desc="Admin-facing. Text on the button which skips entering unlocking password step.">
Skip
</message>
<message name="IDS_AD_ORG_UNIT_HINT" desc="Admin-facing. Hint for the Active Directory Organizational units input on the 'More options' dialog.">
Device OU (e.g. OU=Chromebooks,DC=example,DC=com)
</message>
<message name="IDS_AD_ENCRYPTION_SELECTION_SELECT" desc="Admin-facing. Title for kerberos encryption types selection.">
Select encryption types
</message>
<message name="IDS_AD_CONFIG_SELECTION_SELECT" desc="Admin-facing. Title for configuration selection.">
Select configuration
</message>
<message name="IDS_AD_CONFIG_SELECTION_CUSTOM" desc="Admin-facing. Title for a custom option of domain join config.">
Custom
</message>
<message name="IDS_AD_ENCRYPTION_STRONG_TITLE" desc="Admin-facing. Title for 'Strong' kerberos types option.">
Strong
</message>
......@@ -4192,13 +4216,16 @@
<message name="IDS_AD_DOMAIN_JOIN_UNKNOWN_ERROR" desc="Admin-facing. Default error text on the Active Directory join screen">
Oops! Something went wrong when trying to join the domain. Please try again.
</message>
<message name="IDS_AD_MACHINENAME_INVALID" desc="Admin-facing alert message. Admin entered a bad device name.">
<message name="IDS_AD_DEVICE_NAME_INVALID" desc="Admin-facing alert message. Admin entered a bad device name.">
The device name is invalid. Enter a valid device name to try again.
</message>
<message name="IDS_AD_MACHINENAME_TOO_LONG" desc="Admin-facing alert message. Admin entered a device name that was too long.">
<message name="IDS_AD_DEVICE_NAME_TOO_LONG" desc="Admin-facing alert message. Admin entered a device name that was too long.">
The device name is too long. Enter a shorter name to try again.
</message>
<message name="IDS_AD_USER_DENIED_TO_JOIN_MACHINE" desc="Admin-facing alert message. Admin doesn’t have privileges to add devices to the domain. 'Devices' refers to Chromebook computers. 'Domain' is the Windows domain that accounts are registered to.">
<message name="IDS_AD_DEVICE_NAME_DOESNT_MATCH_REGEX" desc="Admin-facing. Alert message about device name does not match regular expression">
The device name must match regular expression <ph name="REGEX">$1<ex>^DEVICE_\d+$</ex></ph>.
</message>
<message name="IDS_AD_USER_DENIED_TO_JOIN_DEVICE" desc="Admin-facing alert message. Admin doesn’t have privileges to add devices to the domain. 'Devices' refers to Chromebook computers. 'Domain' is the Windows domain that accounts are registered to.">
Can't join the domain. Check your account to see if you have sufficient privileges to add devices.
</message>
<message name="IDS_AD_USER_HIT_JOIN_QUOTA" desc="Admin-facing alert message. Administrator has reached the limit of devices that can be added.">
......@@ -4220,16 +4247,16 @@
Chrome <ph name="MS_AD_NAME">Microsoft® Active Directory®</ph> integration is only supported on x86_64 platforms. Chromebooks built on top of an ARM or x86 platform do not support this functionality.
</message>
<!-- User facing strings for Active Directory screens. -->
<message name="IDS_AD_DOMAIN_AUTH_WELCOME_MESSAGE" desc="Welcome message on the AD Authentication user screen. Include the domain (realm) the device joined to.">
<message name="IDS_AD_DOMAIN_AUTH_WELCOME_MESSAGE" desc="Welcome message on the Active Directory Authentication user screen. Include the domain (realm) the device joined to.">
Sign in to <ph name="REALM">$1<ex>example.com</ex></ph>
</message>
<message name="IDS_AD_ENROLLMENT_LOGIN_USER" desc="Label for userPrincipalName input field on the AD domain join user screen. Looks like user@example.com where example.com is the realm.">
<message name="IDS_AD_ENROLLMENT_LOGIN_USER" desc="Label for userPrincipalName input field on the Active Directory domain join user screen. Looks like user@example.com where example.com is the realm.">
Username (e.g. user@example.com)
</message>
<message name="IDS_AD_AUTH_LOGIN_USER" desc="Label for userPrincipalName input field on the AD authentication user screen.">
<message name="IDS_AD_AUTH_LOGIN_USER" desc="Label for userPrincipalName input field on the Active Directory authentication user screen.">
Username
</message>
<message name="IDS_AD_LOGIN_PASSWORD" desc="Label for Password input field on both AD domain join and AD Authorization user screens.">
<message name="IDS_AD_LOGIN_PASSWORD" desc="Label for Password input field on both Active Directory domain join and Active Directory Authorization user screens.">
Password
</message>
<message name="IDS_AD_PASSWORD_CHANGE_OLD_PASSWORD_HINT" desc="Old password field hint on the Active Directory password change dialog.">
......@@ -4274,6 +4301,9 @@
<message name="IDS_AD_AUTH_NETWORK_ERROR" desc="Error message to show when there is a problem contacting the logon server when authenticating for Active Directory enrollment.">
Oops! There was a problem contacting the logon server. Please check your network connection and the domain name, then try again.
</message>
<message name="IDS_AD_JOIN_CONFIG_NOT_PARSED" desc="Alert message to say unlocked configuration could not be parsed.">
Oops! Can't parse domain join configuration. Please contact your administrator.
</message>
<message name="IDS_AD_AUTH_UNKNOWN_ERROR" desc="Error message to show that occurs something a user could not fix.">
Oops! An unknown error occurred. Please try again later or contact your administrator if the issue persists.
</message>
......
......@@ -54,7 +54,8 @@ constexpr char kAdMachineInput[] = "machineNameInput";
constexpr char kAdMoreOptionsButton[] = "moreOptionsBtn";
constexpr char kAdUserInput[] = "userInput";
constexpr char kAdPasswordInput[] = "passwordInput";
constexpr char kAdButton[] = "button";
constexpr char kAdCredsButton[] = "adCreds /deep/ #button";
constexpr char kAdPasswordChangeButton[] = "button";
constexpr char kAdWelcomMessage[] = "welcomeMsg";
constexpr char kAdAutocompleteRealm[] = "userInput /deep/ #domainLabel";
......@@ -288,7 +289,7 @@ class ActiveDirectoryLoginTest : public LoginManagerTest {
".value='" + username + "'");
js_checker().ExecuteAsync(JSElement(kAdOfflineAuthId, kAdPasswordInput) +
".value='" + password + "'");
js_checker().Evaluate(JSElement(kAdOfflineAuthId, kAdButton) +
js_checker().Evaluate(JSElement(kAdOfflineAuthId, kAdCredsButton) +
".fire('tap')");
}
......@@ -306,8 +307,9 @@ class ActiveDirectoryLoginTest : public LoginManagerTest {
js_checker().ExecuteAsync(
JSElement(kAdPasswordChangeId, kAdNewPassword2Input) + ".value='" +
new_password2 + "'");
js_checker().Evaluate(JSElement(kAdPasswordChangeId, kAdButton) +
".fire('tap')");
js_checker().Evaluate(
JSElement(kAdPasswordChangeId, kAdPasswordChangeButton) +
".fire('tap')");
}
void SetupActiveDirectoryJSNotifications() {
......
......@@ -439,13 +439,15 @@ void EnrollmentScreen::RecordEnrollmentErrorMetrics() {
}
void EnrollmentScreen::JoinDomain(const std::string& dm_token,
const std::string& domain_join_config,
OnDomainJoinedCallback on_joined_callback) {
if (!authpolicy_login_helper_)
authpolicy_login_helper_ = std::make_unique<AuthPolicyLoginHelper>();
authpolicy_login_helper_->set_dm_token(dm_token);
on_joined_callback_ = std::move(on_joined_callback);
view_->ShowActiveDirectoryScreen(std::string(), std::string(),
authpolicy::ERROR_NONE);
view_->ShowActiveDirectoryScreen(
domain_join_config, std::string() /* machine_name */,
std::string() /* username */, authpolicy::ERROR_NONE);
}
void EnrollmentScreen::OnActiveDirectoryJoined(
......@@ -458,7 +460,8 @@ void EnrollmentScreen::OnActiveDirectoryJoined(
std::move(on_joined_callback_).Run(machine_domain);
return;
}
view_->ShowActiveDirectoryScreen(machine_name, username, error);
view_->ShowActiveDirectoryScreen(std::string() /* domain_join_config */,
machine_name, username, error);
}
} // namespace chromeos
......@@ -89,6 +89,7 @@ class EnrollmentScreen
// ActiveDirectoryJoinDelegate implementation:
void JoinDomain(const std::string& dm_token,
const std::string& domain_join_config,
OnDomainJoinedCallback on_joined_callback) override;
// Used for testing.
......
......@@ -69,7 +69,8 @@ class EnrollmentScreenView {
const base::DictionaryValue& license_types) = 0;
// Shows the Active Directory domain joining screen.
virtual void ShowActiveDirectoryScreen(const std::string& machine_name,
virtual void ShowActiveDirectoryScreen(const std::string& domain_join_config,
const std::string& machine_name,
const std::string& username,
authpolicy::ErrorType error) = 0;
......
......@@ -33,8 +33,9 @@ class MockEnrollmentScreenView : public EnrollmentScreenView {
MOCK_METHOD0(ShowSigninScreen, void());
MOCK_METHOD1(ShowLicenseTypeSelectionScreen,
void(const base::DictionaryValue&));
MOCK_METHOD3(ShowActiveDirectoryScreen,
void(const std::string& machine_name,
MOCK_METHOD4(ShowActiveDirectoryScreen,
void(const std::string& domain_join_config,
const std::string& machine_name,
const std::string& username,
authpolicy::ErrorType error));
MOCK_METHOD2(ShowAttributePromptScreen,
......
......@@ -162,8 +162,8 @@ class EnterpriseEnrollmentTest : public LoginManagerTest {
js_checker().ExecuteAsync(set_encryption_types);
}
js_checker().ExecuteAsync(
"document.querySelector('#oauth-enroll-ad-join-ui /deep/ "
"#button').fire('tap')");
"document.querySelector('#oauth-enroll-ad-join-ui /deep/ #adCreds"
" /deep/ #button').fire('tap')");
ExecutePendingJavaScript();
}
......@@ -235,12 +235,13 @@ class EnterpriseEnrollmentTest : public LoginManagerTest {
EnrollUsingAuthCode("test_auth_code", _))
.WillOnce(InvokeWithoutArgs([this, expected_domain]() {
this->enrollment_screen()->JoinDomain(
kDMToken, base::BindOnce(
[](const std::string& expected_domain,
const std::string& domain) {
ASSERT_EQ(expected_domain, domain);
},
expected_domain));
kDMToken, std::string() /* domain_join_config */,
base::BindOnce(
[](const std::string& expected_domain,
const std::string& domain) {
ASSERT_EQ(expected_domain, domain);
},
expected_domain));
}));
});
}
......@@ -259,23 +260,25 @@ class EnterpriseEnrollmentTest : public LoginManagerTest {
void SetupActiveDirectoryJSNotifications() {
js_checker().ExecuteAsync(
"var testShowStep = login.OAuthEnrollmentScreen.showStep;\n"
"var originalShowStep = login.OAuthEnrollmentScreen.showStep;\n"
"login.OAuthEnrollmentScreen.showStep = function(step) {\n"
" testShowStep(step);\n"
" originalShowStep(step);\n"
" if (step == 'working') {\n"
" window.domAutomationController.send('ShowSpinnerScreen');\n"
" }"
"}\n"
"var testShowError = login.OAuthEnrollmentScreen.showError;\n"
"var originalShowError = login.OAuthEnrollmentScreen.showError;\n"
"login.OAuthEnrollmentScreen.showError = function(message, retry) {\n"
" testShowError(message, retry);\n"
" originalShowError(message, retry);\n"
" window.domAutomationController.send('ShowADJoinError');\n"
"}\n");
js_checker().ExecuteAsync(
"var testInvalidateAd = login.OAuthEnrollmentScreen.invalidateAd;"
"login.OAuthEnrollmentScreen.invalidateAd = function(machineName, "
"user, errorState) {"
" testInvalidateAd(machineName, user, errorState);"
"var originalSetAdJoinParams ="
" login.OAuthEnrollmentScreen.setAdJoinParams;"
"login.OAuthEnrollmentScreen.setAdJoinParams = function("
" machineName, user, errorState, showUnlockConfig) {"
" originalSetAdJoinParams("
" machineName, user, errorState, showUnlockConfig);"
" window.domAutomationController.send('ShowJoinDomainError');"
"}");
}
......@@ -432,13 +435,11 @@ IN_PROC_BROWSER_TEST_F(EnterpriseEnrollmentTest,
content::DOMMessageQueue message_queue;
SetupActiveDirectoryJSNotifications();
authpolicy::KerberosEncryptionTypes enc_types =
authpolicy::KerberosEncryptionTypes::ENC_TYPES_ALL;
SetExpectedJoinRequest("machine_name", "" /* machine_domain */, enc_types,
SetExpectedJoinRequest("machine_name", "" /* machine_domain */,
authpolicy::KerberosEncryptionTypes::ENC_TYPES_ALL,
{} /* machine_ou */, kAdTestUser, kDMToken);
SubmitActiveDirectoryCredentials("machine_name", "" /* machine_dn */,
std::to_string(enc_types), kAdTestUser,
"password");
SubmitActiveDirectoryCredentials("machine_name", "" /* machine_dn */, "all",
kAdTestUser, "password");
WaitForMessage(&message_queue, "\"ShowSpinnerScreen\"");
EXPECT_FALSE(IsStepDisplayed("ad-join"));
......@@ -554,11 +555,8 @@ IN_PROC_BROWSER_TEST_F(EnterpriseEnrollmentTest,
content::DOMMessageQueue message_queue;
SetupActiveDirectoryJSNotifications();
// Legacy type triggers error card.
authpolicy::KerberosEncryptionTypes enc_types =
authpolicy::KerberosEncryptionTypes::ENC_TYPES_LEGACY;
SubmitActiveDirectoryCredentials("machine_name", "" /* machine_dn */,
std::to_string(enc_types), "test_user",
"password");
"legacy", "test_user", "password");
WaitForMessage(&message_queue, "\"ShowADJoinError\"");
EXPECT_TRUE(IsStepDisplayed("active-directory-join-error"));
ClickRetry();
......
......@@ -24,8 +24,10 @@ class ActiveDirectoryJoinDelegate {
public:
ActiveDirectoryJoinDelegate() = default;
// Start the Active Directory domain join flow. |dm_token| will be stored in
// the device policy.
// the device policy. |domain_join_config| could be used to streamline the
// flow.
virtual void JoinDomain(const std::string& dm_token,
const std::string& domain_join_config,
OnDomainJoinedCallback on_joined_callback) = 0;
protected:
......
......@@ -6,6 +6,7 @@
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
......@@ -124,6 +125,24 @@ base::Optional<std::string> ReadFileToOptionalString(
return result;
}
// Returns binary config which is encrypted by a password that the joining user
// has to enter.
std::string GetActiveDirectoryDomainJoinConfig(
const base::DictionaryValue* config) {
if (!config)
return std::string();
const base::Value* base64_value = config->FindKeyOfType(
"active_directory_domain_join_config", base::Value::Type::STRING);
if (!base64_value)
return std::string();
std::string result;
if (!base::Base64Decode(base64_value->GetString(), &result)) {
LOG(ERROR) << "Active Directory config is not base64";
return std::string();
}
return result;
}
} // namespace
EnrollmentHandlerChromeOS::EnrollmentHandlerChromeOS(
......@@ -622,6 +641,7 @@ void EnrollmentHandlerChromeOS::StartJoinAdDomain() {
DCHECK(ad_join_delegate_);
ad_join_delegate_->JoinDomain(
client_->dm_token(),
GetActiveDirectoryDomainJoinConfig(client_->configuration_seed()),
base::BindOnce(&EnrollmentHandlerChromeOS::OnAdDomainJoined,
weak_ptr_factory_.GetWeakPtr()));
}
......
......@@ -14,6 +14,7 @@
Attributes:
'isDomainJoin' - Whether the screen is for domain join. For the join it
contains some specific elements (e.g. machine name input).
'unlockPasswordStep' - Whether the unlock password step should be shown.
'realm' - The AD realm the device is managed by.
'userRealm' - Autocomplete realm for the user input.
'userNameLabel' - Label for the user input.
......@@ -31,11 +32,16 @@
chosen encryption types
'username': <username> (UPN),
'password': <typed password> }
'unlockPasswordEntered' - Fired when user enters password to unlock
configuration. Argument
{'unlock_password': <password>}
Methods:
'focus' - Focuses current input (user input or password input).
'setUserMachine' - Accepts arguments |user| and |machineName|. Both are
optional. If user passed, the password input would be
invalidated.
TODO(rsorokin): Switch to the new UI (see https://crbug.com/811556)
-->
<dom-module id="offline-ad-login">
<link rel="stylesheet" href="offline_gaia.css">
......@@ -48,11 +54,35 @@
<h1 id="welcomeMsg" class="welcome-message">[[adWelcomeMessage]]
</h1>
</div>
<div slot="footer" class="flex vertical layout justified">
<gaia-input-form on-submit="onSubmit_"
<div slot="footer" class="flex vertical layout">
<gaia-input-form on-submit="onUnlockPasswordEntered_"
disabled="[[disabled]]"
i18n-values="button-text:offlineLoginNextBtn">
<gaia-input slot="inputs" id="machineNameInput" required
i18n-values="button-text:adUnlockButton"
hidden="[[!unlockPasswordStep]]">
<gaia-input id="unlockPasswordInput" type="password" slot="inputs"
i18n-values="error:adUnlockIncorrectPassword;
label:adUnlockPassword"
required>
</gaia-input>
<gaia-button id="skipButton" on-tap="onSkipClicked_">
$i18n{adUnlockPasswordSkip}
</gaia-button>
</gaia-input-form>
<div class="layout horizontal justified" hidden id="joinConfig">
<div>
$i18n{selectConfiguration}
</div>
<div class="md-select-wrapper">
<select id="joinConfigSelect" class="md-select">
</select>
<span class="md-select-underline"></span>
</div>
</div>
<gaia-input-form on-submit="onSubmit_" id="adCreds"
disabled="[[disabled]]"
i18n-values="button-text:offlineLoginNextBtn"
hidden="[[unlockPasswordStep]]">
<gaia-input id="machineNameInput" required slot="inputs"
hidden="[[!isDomainJoin]]" error="[[machineNameError]]"
i18n-values="label:oauthEnrollAdMachineNameInput">
</gaia-input>
......@@ -64,6 +94,10 @@
i18n-values="error:adLoginInvalidPassword;
label:adLoginPassword">
</gaia-input>
<gaia-button id="backToUnlockButton" on-tap="onBackToUnlock_"
type="link" hidden>
$i18n{adUnlockPassword}
</gaia-button>
<gaia-button id="moreOptionsBtn" type="link"
on-tap="onMoreOptionsClicked_" hidden="[[!isDomainJoin]]">
$i18n{adJoinMoreOptions}
......
......@@ -13,13 +13,22 @@
MACHINE_NAME_INVALID: 1,
MACHINE_NAME_TOO_LONG: 2,
BAD_USERNAME: 3,
BAD_PASSWORD: 4,
BAD_AUTH_PASSWORD: 4,
BAD_UNLOCK_PASSWORD: 5,
};
var DEFAULT_ENCRYPTION_TYPES = 'strong';
/** @typedef {Iterable<{value: string, title: string, selected: boolean,
* subtitle: string}>} */
var EncryptionSelectListType;
/** @typedef {{name: string, ad_username: ?string, ad_password: ?string,
* computer_ou: ?string, encryption_types: ?string,
* computer_name_validation_regex: ?string}}
*/
var JoinConfigType;
Polymer({
is: 'offline-ad-login',
......@@ -32,6 +41,10 @@ Polymer({
* Whether the screen is for domain join.
*/
isDomainJoin: {type: Boolean, value: false},
/**
* Whether the unlock option should be shown.
*/
unlockPasswordStep: {type: Boolean, value: false},
/**
* The kerberos realm (AD Domain), the machine is part of.
*/
......@@ -73,6 +86,12 @@ Polymer({
* */
defaultEncryption: String,
/**
* List of domain join configuration options.
* @private {!Array<JoinConfigType>|undefined}
*/
joinConfigOptions_: undefined,
/** @private */
realmChanged_: function() {
this.adWelcomeMessage =
......@@ -100,6 +119,8 @@ Polymer({
this.$.encryptionList, list, this.onEncryptionSelected_.bind(this));
this.defaultEncryption = /** @type {!string} */ (getSelectedValue(list));
this.onEncryptionSelected_(this.defaultEncryption);
this.machineNameError =
loadTimeData.getString('adJoinErrorMachineNameInvalid');
},
focus: function() {
......@@ -122,7 +143,6 @@ Polymer({
user = user.replace(this.userRealm, '');
this.$.userInput.value = user || '';
this.$.machineNameInput.value = machineName || '';
this.$.passwordInput.value = '';
this.focus();
},
......@@ -130,9 +150,7 @@ Polymer({
* @param {ACTIVE_DIRECTORY_ERROR_STATE} error_state
*/
setInvalid: function(error_state) {
this.$.machineNameInput.isInvalid = false;
this.$.userInput.isInvalid = false;
this.$.passwordInput.isInvalid = false;
this.resetValidity_();
switch (error_state) {
case ACTIVE_DIRECTORY_ERROR_STATE.NONE:
break;
......@@ -149,12 +167,36 @@ Polymer({
case ACTIVE_DIRECTORY_ERROR_STATE.BAD_USERNAME:
this.$.userInput.isInvalid = true;
break;
case ACTIVE_DIRECTORY_ERROR_STATE.BAD_PASSWORD:
case ACTIVE_DIRECTORY_ERROR_STATE.BAD_AUTH_PASSWORD:
this.$.passwordInput.isInvalid = true;
break;
case ACTIVE_DIRECTORY_ERROR_STATE.BAD_UNLOCK_PASSWORD:
this.$.unlockPasswordInput.isInvalid = true;
break;
}
},
/**
* @param {Array<JoinConfigType>} options
*/
setJoinConfigurationOptions: function(options) {
this.$.backToUnlockButton.hidden = true;
if (!options || options.length < 1) {
this.$.joinConfig.hidden = true;
return;
}
this.joinConfigOptions_ = options;
var selectList = [];
for (var i = 0; i < options.length; ++i) {
selectList.push({title: options[i].name, value: i});
}
setupSelect(
this.$.joinConfigSelect, selectList,
this.onJoinConfigSelected_.bind(this));
this.onJoinConfigSelected_(this.$.joinConfigSelect.value);
this.$.joinConfig.hidden = false;
},
/** @private */
onSubmit_: function() {
if (this.isDomainJoin && !this.$.machineNameInput.checkValidity())
......@@ -173,8 +215,7 @@ Polymer({
'password': this.$.passwordInput.value
};
if (this.isDomainJoin)
msg['encryption_types'] = parseInt(this.$.encryptionList.value, 10);
this.$.passwordInput.value = '';
msg['encryption_types'] = this.$.encryptionList.value;
this.fire('authCompleted', msg);
},
......@@ -211,6 +252,25 @@ Polymer({
this.$$('#gaiaCard').classList.remove('full-disabled');
},
/** @private */
onUnlockPasswordEntered_: function() {
var msg = {
'unlock_password': this.$.unlockPasswordInput.value,
};
this.fire('unlockPasswordEntered', msg);
},
/** @private */
onSkipClicked_: function() {
this.$.backToUnlockButton.hidden = false;
this.unlockPasswordStep = false;
},
/** @private */
onBackToUnlock_: function() {
this.unlockPasswordStep = true;
},
/**
* @private
* @param {!string} value
......@@ -220,4 +280,43 @@ Polymer({
this.encryptionValueToSubtitleMap[value];
this.$.encryptionWarningIcon.hidden = (value == this.defaultEncryption);
},
/** @private */
onJoinConfigSelected_: function(value) {
this.resetValidity_();
var option = this.joinConfigOptions_[value];
this.$.userInput.value = option['ad_username'] || '';
this.$.passwordInput.value = option['ad_password'] || '';
this.$.orgUnitInput.value = option['computer_ou'] || '';
var encryptionTypes =
option['encryption_types'] || DEFAULT_ENCRYPTION_TYPES;
if (!(encryptionTypes in this.encryptionValueToSubtitleMap)) {
encryptionTypes = DEFAULT_ENCRYPTION_TYPES;
}
this.$.encryptionList.value = encryptionTypes;
this.onEncryptionSelected_(encryptionTypes);
var pattern = option['computer_name_validation_regex'];
this.$.machineNameInput.pattern = pattern;
if (pattern) {
this.$.machineNameInput.label = loadTimeData.getStringF(
'oauthEnrollAdMachineNameInputRegex', pattern);
this.machineNameError = loadTimeData.getStringF(
'adJoinErrorMachineNameDoesntMatchRegex', pattern);
} else {
this.$.machineNameInput.label =
loadTimeData.getString('oauthEnrollAdMachineNameInput');
this.machineNameError =
loadTimeData.getString('adJoinErrorMachineNameInvalid');
}
},
/** @private */
resetValidity_: function() {
this.$.machineNameInput.isInvalid = false;
this.$.userInput.isInvalid = false;
this.$.passwordInput.isInvalid = false;
this.$.unlockPasswordInput.isInvalid = false;
},
});
......@@ -32,7 +32,8 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
'setAvailableLicenseTypes',
'showAttributePromptStep',
'showAttestationBasedEnrollmentSuccess',
'invalidateAd',
'setAdJoinParams',
'setAdJoinConfiguration',
],
/**
......@@ -139,6 +140,11 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
e.detail.encryption_types, e.detail.username, e.detail.password
]);
}.bind(this));
this.offlineAdUi_.addEventListener('unlockPasswordEntered', function(e) {
this.offlineAdUi_.disabled = true;
chrome.send(
'oauthEnrollAdUnlockConfiguration', [e.detail.unlock_password]);
}.bind(this));
this.authenticator_.addEventListener(
'authFlowChange', (function(e) {
......@@ -427,10 +433,30 @@ login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
this.updateControlsState();
},
invalidateAd: function(machineName, user, errorState) {
/**
* 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_.setUser(user, machineName);
this.offlineAdUi_.setUser(userName, machineName);
this.offlineAdUi_.setInvalid(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_.unlockPasswordStep = false;
this.offlineAdUi_.setJoinConfigurationOptions(options);
},
/**
......
......@@ -29,7 +29,8 @@ enum class ActiveDirectoryErrorState {
MACHINE_NAME_INVALID = 1,
MACHINE_NAME_TOO_LONG = 2,
BAD_USERNAME = 3,
BAD_PASSWORD = 4,
BAD_AUTH_PASSWORD = 4,
BAD_UNLOCK_PASSWORD = 5,
};
// WebUIMessageHandler implementation which handles events occurring on the
......@@ -55,7 +56,8 @@ class EnrollmentScreenHandler
void ShowSigninScreen() override;
void ShowLicenseTypeSelectionScreen(
const base::DictionaryValue& license_types) override;
void ShowActiveDirectoryScreen(const std::string& machine_name,
void ShowActiveDirectoryScreen(const std::string& domain_join_config,
const std::string& machine_name,
const std::string& username,
authpolicy::ErrorType error) override;
void ShowAttributePromptScreen(const std::string& asset_id,
......@@ -85,9 +87,10 @@ class EnrollmentScreenHandler
const std::string& auth_code);
void HandleAdCompleteLogin(const std::string& machine_name,
const std::string& distinguished_name,
int encryption_types,
const std::string& encryption_types,
const std::string& user_name,
const std::string& password);
void HandleAdUnlockConfiguration(const std::string& password);
void HandleRetry();
void HandleFrameLoadingCompleted();
void HandleDeviceAttributesProvided(const std::string& asset_id,
......@@ -130,6 +133,9 @@ class EnrollmentScreenHandler
// enrollment sign-in page.
bool IsEnrollmentScreenHiddenByError() const;
// Called after configuration seed was unlocked.
void OnAdConfigurationUnlocked(std::string unlocked_data);
// Keeps the controller for this view.
Controller* controller_ = nullptr;
......@@ -138,6 +144,12 @@ class EnrollmentScreenHandler
// The enrollment configuration.
policy::EnrollmentConfig config_;
// Active Directory configuration in the form of encrypted binary data.
std::string active_directory_domain_join_config_;
// Whether unlock password input step should be shown.
bool show_unlock_password_ = false;
// True if screen was not shown yet.
bool first_show_ = true;
......
......@@ -698,7 +698,7 @@ void GaiaScreenHandler::DoAdAuth(
break;
case authpolicy::ERROR_BAD_PASSWORD:
CallJS("invalidateAd", username,
static_cast<int>(ActiveDirectoryErrorState::BAD_PASSWORD));
static_cast<int>(ActiveDirectoryErrorState::BAD_AUTH_PASSWORD));
break;
default:
CallJS("invalidateAd", username,
......
......@@ -13,6 +13,9 @@
#include "chromeos/dbus/auth_policy_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/upstart_client.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/symmetric_key.h"
namespace chromeos {
......@@ -65,6 +68,75 @@ bool ParseDomainAndOU(const std::string& distinguished_name,
return true;
}
std::string DoDecrypt(const std::string& encrypted_data,
const std::string& password) {
constexpr char error_msg[] = "Failed to decrypt data";
const size_t kSaltSize = 32;
const size_t kSignatureSize = 32;
if (encrypted_data.size() <= kSaltSize + kSignatureSize) {
LOG(ERROR) << error_msg;
return std::string();
}
const std::string salt = encrypted_data.substr(0, kSaltSize);
const std::string signature =
encrypted_data.substr(kSaltSize, kSignatureSize);
const std::string ciphertext =
encrypted_data.substr(kSaltSize + kSignatureSize);
// Derive AES key, AES IV and HMAC key from password.
const size_t kAesKeySize = 32;
const size_t kAesIvSize = 16;
const size_t kHmacKeySize = 32;
const size_t kKeySize = kAesKeySize + kAesIvSize + kHmacKeySize;
std::unique_ptr<crypto::SymmetricKey> key =
crypto::SymmetricKey::DeriveKeyFromPassword(
crypto::SymmetricKey::HMAC_SHA1, password, salt, 10000, kKeySize * 8);
if (!key) {
LOG(ERROR) << error_msg;
return std::string();
}
DCHECK(kAesKeySize + kAesIvSize + kHmacKeySize == key->key().size());
const char* key_data_chars = key->key().data();
std::string aes_key(key_data_chars, kAesKeySize);
std::string aes_iv(key_data_chars + kAesKeySize, kAesIvSize);
std::string hmac_key(key_data_chars + kAesKeySize + kAesIvSize, kHmacKeySize);
// Check signature.
crypto::HMAC hmac(crypto::HMAC::SHA256);
if (kSignatureSize != hmac.DigestLength()) {
LOG(ERROR) << error_msg;
return std::string();
}
uint8_t recomputed_signature[kSignatureSize];
if (!hmac.Init(hmac_key) ||
!hmac.Sign(ciphertext, recomputed_signature, kSignatureSize)) {
LOG(ERROR) << error_msg;
return std::string();
}
std::string recomputed_signature_str(
reinterpret_cast<char*>(recomputed_signature), kSignatureSize);
if (signature != recomputed_signature_str) {
LOG(ERROR) << error_msg;
return std::string();
}
// Decrypt.
std::unique_ptr<crypto::SymmetricKey> aes_key_obj(
crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, aes_key));
crypto::Encryptor encryptor;
if (!encryptor.Init(aes_key_obj.get(), crypto::Encryptor::CBC, aes_iv)) {
LOG(ERROR) << error_msg;
return std::string();
}
std::string decrypted_data;
if (!encryptor.Decrypt(ciphertext, &decrypted_data)) {
LOG(ERROR) << error_msg;
return std::string();
}
return decrypted_data;
}
} // namespace
AuthPolicyLoginHelper::AuthPolicyLoginHelper() : weak_factory_(this) {}
......@@ -87,6 +159,15 @@ void AuthPolicyLoginHelper::Restart() {
->RestartAuthPolicyService();
}
// static
void AuthPolicyLoginHelper::DecryptConfiguration(const std::string& blob,
const std::string& password,
OnDecryptedCallback callback) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&DoDecrypt, blob, password), std::move(callback));
}
// static
bool AuthPolicyLoginHelper::IsAdLocked() {
std::string mode;
......
......@@ -23,6 +23,8 @@ class CHROMEOS_EXPORT AuthPolicyLoginHelper {
public:
using AuthCallback = AuthPolicyClient::AuthCallback;
using JoinCallback = AuthPolicyClient::JoinCallback;
using OnDecryptedCallback =
base::OnceCallback<void(std::string decrypted_data)>;
AuthPolicyLoginHelper();
~AuthPolicyLoginHelper();
......@@ -37,6 +39,13 @@ class CHROMEOS_EXPORT AuthPolicyLoginHelper {
// Restarts AuthPolicy service.
static void Restart();
// Decrypts |blob| with |password| on a separate thread. Calls |callback| on
// the orginal thread. If decryption failed |callback| called with an empty
// string.
static void DecryptConfiguration(const std::string& blob,
const std::string& password,
OnDecryptedCallback callback);
// Checks if device is locked for Active Directory management.
static bool IsAdLocked();
......
......@@ -12,6 +12,7 @@
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/guid.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "components/policy/core/common/cloud/cloud_policy_util.h"
......@@ -734,6 +735,13 @@ void CloudPolicyClient::OnRegisterCompleted(
status_ = status;
if (status == DM_STATUS_SUCCESS) {
dm_token_ = response.register_response().device_management_token();
if (response.register_response().has_configuration_seed()) {
configuration_seed_ = base::DictionaryValue::From(base::JSONReader::Read(
response.register_response().configuration_seed(),
base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS));
if (!configuration_seed_)
LOG(ERROR) << "Failed to parse configuration seed";
}
DVLOG(1) << "Client registration complete - DMToken = " << dm_token_;
// Device mode is only relevant for device policy really, it's the
......
......@@ -19,6 +19,7 @@
#include "base/observer_list.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/remote_commands/remote_command_job.h"
#include "components/policy/policy_export.h"
......@@ -310,6 +311,9 @@ class POLICY_EXPORT CloudPolicyClient {
const std::string& dm_token() const { return dm_token_; }
const std::string& client_id() const { return client_id_; }
const base::DictionaryValue* configuration_seed() const {
return configuration_seed_.get();
};
// The device mode as received in the registration request.
DeviceMode device_mode() const { return device_mode_; }
......@@ -466,6 +470,7 @@ class POLICY_EXPORT CloudPolicyClient {
std::vector<std::string> state_keys_to_upload_;
std::string dm_token_;
std::unique_ptr<base::DictionaryValue> configuration_seed_;
DeviceMode device_mode_ = DEVICE_MODE_NOT_SET;
std::string client_id_;
base::Time last_policy_timestamp_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment