| // 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. |
| |
| #include "chrome/browser/chromeos/login/enrollment/enrollment_screen.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/browser_process_platform_part.h" |
| #include "chrome/browser/chromeos/login/configuration_keys.h" |
| #include "chrome/browser/chromeos/login/enrollment/enrollment_uma.h" |
| #include "chrome/browser/chromeos/login/screen_manager.h" |
| #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h" |
| #include "chrome/browser/chromeos/login/startup_utils.h" |
| #include "chrome/browser/chromeos/login/wizard_controller.h" |
| #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" |
| #include "chrome/browser/chromeos/policy/enrollment_status_chromeos.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/lifetime/application_lifetime.h" |
| #include "chrome/browser/lifetime/browser_shutdown.h" |
| #include "chromeos/dbus/cryptohome_client.h" |
| #include "chromeos/dbus/dbus_method_call_status.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "components/policy/core/common/cloud/cloud_policy_constants.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| |
| using policy::EnrollmentConfig; |
| |
| // Do not change the UMA histogram parameters without renaming the histograms! |
| #define UMA_ENROLLMENT_TIME(histogram_name, elapsed_timer) \ |
| do { \ |
| UMA_HISTOGRAM_CUSTOM_TIMES( \ |
| (histogram_name), (elapsed_timer)->Elapsed(), \ |
| base::TimeDelta::FromMilliseconds(100) /* min */, \ |
| base::TimeDelta::FromMinutes(15) /* max */, 100 /* bucket_count */); \ |
| } while (0) |
| |
| namespace { |
| |
| const char* const kMetricEnrollmentTimeCancel = |
| "Enterprise.EnrollmentTime.Cancel"; |
| const char* const kMetricEnrollmentTimeFailure = |
| "Enterprise.EnrollmentTime.Failure"; |
| const char* const kMetricEnrollmentTimeSuccess = |
| "Enterprise.EnrollmentTime.Success"; |
| |
| const char* const kLicenseTypePerpetual = "perpetual"; |
| const char* const kLicenseTypeAnnual = "annual"; |
| const char* const kLicenseTypeKiosk = "kiosk"; |
| |
| // Retry policy constants. |
| constexpr int kInitialDelayMS = 4 * 1000; // 4 seconds |
| constexpr double kMultiplyFactor = 1.5; |
| constexpr double kJitterFactor = 0.1; // +/- 10% jitter |
| constexpr int64_t kMaxDelayMS = 8 * 60 * 1000; // 8 minutes |
| |
| ::policy::LicenseType GetLicenseTypeById(const std::string& id) { |
| if (id == kLicenseTypePerpetual) |
| return ::policy::LicenseType::PERPETUAL; |
| if (id == kLicenseTypeAnnual) |
| return ::policy::LicenseType::ANNUAL; |
| if (id == kLicenseTypeKiosk) |
| return ::policy::LicenseType::KIOSK; |
| return ::policy::LicenseType::UNKNOWN; |
| } |
| |
| std::string GetLicenseIdByType(::policy::LicenseType type) { |
| switch (type) { |
| case ::policy::LicenseType::PERPETUAL: |
| return kLicenseTypePerpetual; |
| case ::policy::LicenseType::ANNUAL: |
| return kLicenseTypeAnnual; |
| case ::policy::LicenseType::KIOSK: |
| return kLicenseTypeKiosk; |
| default: |
| NOTREACHED(); |
| return std::string(); |
| } |
| } |
| |
| } // namespace |
| |
| namespace chromeos { |
| |
| // static |
| EnrollmentScreen* EnrollmentScreen::Get(ScreenManager* manager) { |
| return static_cast<EnrollmentScreen*>( |
| manager->GetScreen(OobeScreen::SCREEN_OOBE_ENROLLMENT)); |
| } |
| |
| EnrollmentScreen::EnrollmentScreen(BaseScreenDelegate* base_screen_delegate, |
| EnrollmentScreenView* view) |
| : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_ENROLLMENT), |
| view_(view), |
| weak_ptr_factory_(this) { |
| retry_policy_.num_errors_to_ignore = 0; |
| retry_policy_.initial_delay_ms = kInitialDelayMS; |
| retry_policy_.multiply_factor = kMultiplyFactor; |
| retry_policy_.jitter_factor = kJitterFactor; |
| retry_policy_.maximum_backoff_ms = kMaxDelayMS; |
| retry_policy_.entry_lifetime_ms = -1; |
| retry_policy_.always_use_initial_delay = true; |
| retry_backoff_.reset(new net::BackoffEntry(&retry_policy_)); |
| } |
| |
| EnrollmentScreen::~EnrollmentScreen() { |
| DCHECK(!enrollment_helper_ || g_browser_process->IsShuttingDown() || |
| browser_shutdown::IsTryingToQuit() || |
| DBusThreadManager::Get()->IsUsingFakes()); |
| } |
| |
| void EnrollmentScreen::SetEnrollmentConfig( |
| const policy::EnrollmentConfig& enrollment_config) { |
| enrollment_config_ = enrollment_config; |
| switch (enrollment_config_.auth_mechanism) { |
| case EnrollmentConfig::AUTH_MECHANISM_INTERACTIVE: |
| current_auth_ = AUTH_OAUTH; |
| last_auth_ = AUTH_OAUTH; |
| break; |
| case EnrollmentConfig::AUTH_MECHANISM_ATTESTATION: |
| current_auth_ = AUTH_ATTESTATION; |
| last_auth_ = AUTH_ATTESTATION; |
| break; |
| case EnrollmentConfig::AUTH_MECHANISM_BEST_AVAILABLE: |
| current_auth_ = AUTH_ATTESTATION; |
| last_auth_ = enrollment_config_.should_enroll_interactively() |
| ? AUTH_OAUTH |
| : AUTH_ATTESTATION; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| SetConfig(); |
| } |
| |
| void EnrollmentScreen::SetConfig() { |
| config_ = enrollment_config_; |
| if (current_auth_ == AUTH_OAUTH && config_.is_mode_attestation_server()) { |
| config_.mode = |
| config_.mode == |
| policy::EnrollmentConfig::MODE_ATTESTATION_INITIAL_SERVER_FORCED |
| ? policy::EnrollmentConfig::MODE_ATTESTATION_INITIAL_MANUAL_FALLBACK |
| : policy::EnrollmentConfig::MODE_ATTESTATION_MANUAL_FALLBACK; |
| } else if (current_auth_ == AUTH_ATTESTATION && |
| !enrollment_config_.is_mode_attestation()) { |
| config_.mode = config_.is_attestation_forced() |
| ? policy::EnrollmentConfig::MODE_ATTESTATION_LOCAL_FORCED |
| : policy::EnrollmentConfig::MODE_ATTESTATION; |
| } |
| view_->SetEnrollmentConfig(this, config_); |
| enrollment_helper_ = nullptr; |
| } |
| |
| bool EnrollmentScreen::AdvanceToNextAuth() { |
| if (current_auth_ != last_auth_ && current_auth_ == AUTH_ATTESTATION) { |
| current_auth_ = last_auth_; |
| SetConfig(); |
| return true; |
| } |
| return false; |
| } |
| |
| void EnrollmentScreen::CreateEnrollmentHelper() { |
| if (!enrollment_helper_) { |
| enrollment_helper_ = EnterpriseEnrollmentHelper::Create( |
| this, this, config_, enrolling_user_domain_); |
| } |
| } |
| |
| void EnrollmentScreen::ClearAuth(const base::Closure& callback) { |
| if (!enrollment_helper_) { |
| callback.Run(); |
| return; |
| } |
| enrollment_helper_->ClearAuth(base::Bind(&EnrollmentScreen::OnAuthCleared, |
| weak_ptr_factory_.GetWeakPtr(), |
| callback)); |
| } |
| |
| void EnrollmentScreen::OnAuthCleared(const base::Closure& callback) { |
| enrollment_helper_ = nullptr; |
| callback.Run(); |
| } |
| |
| void EnrollmentScreen::Show() { |
| UMA(policy::kMetricEnrollmentTriggered); |
| if (enrollment_config_.mode == |
| policy::EnrollmentConfig::MODE_ENROLLED_ROLLBACK) { |
| RestoreAfterRollback(); |
| return; |
| } |
| switch (current_auth_) { |
| case AUTH_OAUTH: |
| ShowInteractiveScreen(); |
| break; |
| case AUTH_ATTESTATION: |
| AuthenticateUsingAttestation(); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void EnrollmentScreen::ShowInteractiveScreen() { |
| ClearAuth(base::Bind(&EnrollmentScreen::ShowSigninScreen, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void EnrollmentScreen::Hide() { |
| view_->Hide(); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| |
| void EnrollmentScreen::RestoreAfterRollback() { |
| VLOG(1) << "Restoring after version rollback."; |
| elapsed_timer_.reset(new base::ElapsedTimer()); |
| view_->Show(); |
| view_->ShowEnrollmentSpinnerScreen(); |
| CreateEnrollmentHelper(); |
| enrollment_helper_->RestoreAfterRollback(); |
| } |
| |
| void EnrollmentScreen::AuthenticateUsingAttestation() { |
| VLOG(1) << "Authenticating using attestation."; |
| elapsed_timer_.reset(new base::ElapsedTimer()); |
| view_->Show(); |
| CreateEnrollmentHelper(); |
| if (enrollment_config_.mode == |
| policy::EnrollmentConfig::MODE_ATTESTATION_ENROLLMENT_TOKEN) { |
| view_->ShowEnrollmentSpinnerScreen(); |
| enrollment_helper_->EnrollUsingEnrollmentToken( |
| enrollment_config_.enrollment_token); |
| } else { |
| enrollment_helper_->EnrollUsingAttestation(); |
| } |
| } |
| |
| void EnrollmentScreen::OnLoginDone(const std::string& user, |
| const std::string& auth_code) { |
| LOG_IF(ERROR, auth_code.empty()) << "Auth code is empty."; |
| elapsed_timer_.reset(new base::ElapsedTimer()); |
| enrolling_user_domain_ = gaia::ExtractDomainName(user); |
| UMA(enrollment_failed_once_ ? policy::kMetricEnrollmentRestarted |
| : policy::kMetricEnrollmentStarted); |
| |
| view_->ShowEnrollmentSpinnerScreen(); |
| CreateEnrollmentHelper(); |
| enrollment_helper_->EnrollUsingAuthCode(auth_code, |
| false /* fetch_additional_token */); |
| } |
| |
| void EnrollmentScreen::OnLicenseTypeSelected(const std::string& license_type) { |
| view_->ShowEnrollmentSpinnerScreen(); |
| const ::policy::LicenseType license = GetLicenseTypeById(license_type); |
| CHECK(license != ::policy::LicenseType::UNKNOWN) |
| << "license_type = " << license_type; |
| enrollment_helper_->UseLicenseType(license); |
| } |
| |
| void EnrollmentScreen::OnRetry() { |
| retry_task_.Cancel(); |
| ProcessRetry(); |
| } |
| |
| void EnrollmentScreen::AutomaticRetry() { |
| retry_backoff_->InformOfRequest(false); |
| retry_task_.Reset(base::Bind(&EnrollmentScreen::ProcessRetry, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, retry_task_.callback(), retry_backoff_->GetTimeUntilRelease()); |
| } |
| |
| void EnrollmentScreen::ProcessRetry() { |
| ++num_retries_; |
| LOG(WARNING) << "Enrollment retries: " << num_retries_ |
| << ", current_auth_: " << current_auth_; |
| Show(); |
| } |
| |
| void EnrollmentScreen::OnCancel() { |
| if (enrollment_succeeded_) { |
| // Cancellation is the same to confirmation after the successful enrollment. |
| OnConfirmationClosed(); |
| return; |
| } |
| |
| // Record cancellation for that one enrollment mode. |
| UMA(policy::kMetricEnrollmentCancelled); |
| |
| if (AdvanceToNextAuth()) { |
| Show(); |
| return; |
| } |
| |
| // Record the total time for all auth attempts until final cancellation. |
| if (elapsed_timer_) |
| UMA_ENROLLMENT_TIME(kMetricEnrollmentTimeCancel, elapsed_timer_); |
| |
| on_joined_callback_.Reset(); |
| if (authpolicy_login_helper_) |
| authpolicy_login_helper_->CancelRequestsAndRestart(); |
| |
| const ScreenExitCode exit_code = |
| config_.is_forced() ? ScreenExitCode::ENTERPRISE_ENROLLMENT_BACK |
| : ScreenExitCode::ENTERPRISE_ENROLLMENT_COMPLETED; |
| ClearAuth( |
| base::Bind(&EnrollmentScreen::Finish, base::Unretained(this), exit_code)); |
| } |
| |
| void EnrollmentScreen::OnConfirmationClosed() { |
| ClearAuth(base::Bind(&EnrollmentScreen::Finish, base::Unretained(this), |
| ScreenExitCode::ENTERPRISE_ENROLLMENT_COMPLETED)); |
| // Restart browser to switch from DeviceCloudPolicyManagerChromeOS to |
| // DeviceActiveDirectoryPolicyManager. |
| if (g_browser_process->platform_part() |
| ->browser_policy_connector_chromeos() |
| ->IsActiveDirectoryManaged()) { |
| // TODO(tnagel): Refactor BrowserPolicyConnectorChromeOS so that device |
| // policy providers are only registered after enrollment has finished and |
| // thus the correct one can be picked without restarting the browser. |
| chrome::AttemptRestart(); |
| } |
| } |
| |
| void EnrollmentScreen::OnAuthError(const GoogleServiceAuthError& error) { |
| RecordEnrollmentErrorMetrics(); |
| view_->ShowAuthError(error); |
| } |
| |
| void EnrollmentScreen::OnMultipleLicensesAvailable( |
| const EnrollmentLicenseMap& licenses) { |
| if (GetConfiguration()) { |
| auto* license_type_value = GetConfiguration()->FindKeyOfType( |
| configuration::kEnrollmentLicenseType, base::Value::Type::STRING); |
| if (license_type_value) { |
| const std::string& license_type = license_type_value->GetString(); |
| for (const auto& it : licenses) { |
| if (license_type == GetLicenseIdByType(it.first) && it.second > 0) { |
| VLOG(1) << "Using License type from configuration " << license_type; |
| OnLicenseTypeSelected(license_type); |
| return; |
| } |
| } |
| VLOG(1) << "No licenses for License type from configuration " |
| << license_type; |
| } |
| } |
| base::DictionaryValue license_dict; |
| for (const auto& it : licenses) |
| license_dict.SetInteger(GetLicenseIdByType(it.first), it.second); |
| view_->ShowLicenseTypeSelectionScreen(license_dict); |
| } |
| |
| void EnrollmentScreen::OnEnrollmentError(policy::EnrollmentStatus status) { |
| RecordEnrollmentErrorMetrics(); |
| // If the DM server does not have a device pre-provisioned for attestation- |
| // based enrollment and we have a fallback authentication, show it. |
| if (status.status() == policy::EnrollmentStatus::REGISTRATION_FAILED && |
| status.client_status() == policy::DM_STATUS_SERVICE_DEVICE_NOT_FOUND && |
| current_auth_ == AUTH_ATTESTATION) { |
| UMA(policy::kMetricEnrollmentDeviceNotPreProvisioned); |
| if (AdvanceToNextAuth()) { |
| Show(); |
| return; |
| } |
| } |
| |
| view_->ShowEnrollmentStatus(status); |
| if (WizardController::UsingHandsOffEnrollment()) |
| AutomaticRetry(); |
| } |
| |
| void EnrollmentScreen::OnOtherError( |
| EnterpriseEnrollmentHelper::OtherError error) { |
| RecordEnrollmentErrorMetrics(); |
| view_->ShowOtherError(error); |
| if (WizardController::UsingHandsOffEnrollment()) |
| AutomaticRetry(); |
| } |
| |
| void EnrollmentScreen::OnDeviceEnrolled() { |
| enrollment_succeeded_ = true; |
| enrollment_helper_->GetDeviceAttributeUpdatePermission(); |
| } |
| |
| void EnrollmentScreen::OnActiveDirectoryCredsProvided( |
| const std::string& machine_name, |
| const std::string& distinguished_name, |
| int encryption_types, |
| const std::string& username, |
| const std::string& password) { |
| DCHECK(authpolicy_login_helper_); |
| authpolicy_login_helper_->JoinAdDomain( |
| machine_name, distinguished_name, encryption_types, username, password, |
| base::BindOnce(&EnrollmentScreen::OnActiveDirectoryJoined, |
| weak_ptr_factory_.GetWeakPtr(), machine_name, username)); |
| } |
| |
| void EnrollmentScreen::OnDeviceAttributeProvided(const std::string& asset_id, |
| const std::string& location) { |
| enrollment_helper_->UpdateDeviceAttributes(asset_id, location); |
| } |
| |
| void EnrollmentScreen::OnDeviceAttributeUpdatePermission(bool granted) { |
| // If user is permitted to update device attributes |
| // Show attribute prompt screen |
| if (granted && !WizardController::skip_enrollment_prompts()) { |
| StartupUtils::MarkDeviceRegistered( |
| base::BindOnce(&EnrollmentScreen::ShowAttributePromptScreen, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| StartupUtils::MarkDeviceRegistered( |
| base::BindOnce(&EnrollmentScreen::ShowEnrollmentStatusOnSuccess, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void EnrollmentScreen::OnRestoreAfterRollbackCompleted() { |
| StartupUtils::MarkDeviceRegistered( |
| base::BindOnce(&EnrollmentScreen::ShowEnrollmentStatusOnSuccess, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void EnrollmentScreen::OnDeviceAttributeUploadCompleted(bool success) { |
| if (success) { |
| // If the device attributes have been successfully uploaded, fetch policy. |
| policy::BrowserPolicyConnectorChromeOS* connector = |
| g_browser_process->platform_part()->browser_policy_connector_chromeos(); |
| connector->GetDeviceCloudPolicyManager()->core()->RefreshSoon(); |
| view_->ShowEnrollmentStatus( |
| policy::EnrollmentStatus::ForStatus(policy::EnrollmentStatus::SUCCESS)); |
| } else { |
| view_->ShowEnrollmentStatus(policy::EnrollmentStatus::ForStatus( |
| policy::EnrollmentStatus::ATTRIBUTE_UPDATE_FAILED)); |
| } |
| } |
| |
| void EnrollmentScreen::ShowAttributePromptScreen() { |
| policy::BrowserPolicyConnectorChromeOS* connector = |
| g_browser_process->platform_part()->browser_policy_connector_chromeos(); |
| policy::DeviceCloudPolicyManagerChromeOS* policy_manager = |
| connector->GetDeviceCloudPolicyManager(); |
| |
| std::string asset_id; |
| std::string location; |
| |
| if (GetConfiguration()) { |
| auto* asset_id_value = GetConfiguration()->FindKeyOfType( |
| configuration::kEnrollmentAssetId, base::Value::Type::STRING); |
| if (asset_id_value) { |
| VLOG(1) << "Using Asset ID from configuration " |
| << asset_id_value->GetString(); |
| asset_id = asset_id_value->GetString(); |
| } |
| auto* location_value = GetConfiguration()->FindKeyOfType( |
| configuration::kEnrollmentLocation, base::Value::Type::STRING); |
| if (location_value) { |
| VLOG(1) << "Using Location from configuration " |
| << location_value->GetString(); |
| location = location_value->GetString(); |
| } |
| } |
| |
| policy::CloudPolicyStore* store = policy_manager->core()->store(); |
| |
| const enterprise_management::PolicyData* policy = store->policy(); |
| |
| if (policy) { |
| asset_id = policy->annotated_asset_id(); |
| location = policy->annotated_location(); |
| } |
| |
| if (GetConfiguration()) { |
| auto* auto_attributes = GetConfiguration()->FindKeyOfType( |
| configuration::kEnrollmentAutoAttributes, base::Value::Type::BOOLEAN); |
| if (auto_attributes && auto_attributes->GetBool()) { |
| VLOG(1) << "Automatically accept attributes"; |
| OnDeviceAttributeProvided(asset_id, location); |
| return; |
| } |
| } |
| |
| view_->ShowAttributePromptScreen(asset_id, location); |
| } |
| |
| void EnrollmentScreen::ShowEnrollmentStatusOnSuccess() { |
| retry_backoff_->InformOfRequest(true); |
| if (elapsed_timer_) |
| UMA_ENROLLMENT_TIME(kMetricEnrollmentTimeSuccess, elapsed_timer_); |
| if (WizardController::UsingHandsOffEnrollment() || |
| WizardController::skip_enrollment_prompts() || |
| enrollment_config_.mode == |
| policy::EnrollmentConfig::MODE_ENROLLED_ROLLBACK) { |
| OnConfirmationClosed(); |
| } else { |
| view_->ShowEnrollmentStatus( |
| policy::EnrollmentStatus::ForStatus(policy::EnrollmentStatus::SUCCESS)); |
| } |
| } |
| |
| void EnrollmentScreen::UMA(policy::MetricEnrollment sample) { |
| EnrollmentUMA(sample, config_.mode); |
| } |
| |
| void EnrollmentScreen::ShowSigninScreen() { |
| view_->Show(); |
| view_->ShowSigninScreen(); |
| } |
| |
| void EnrollmentScreen::RecordEnrollmentErrorMetrics() { |
| enrollment_failed_once_ = true; |
| // TODO(crbug.com/896793): Have other metrics for each auth mechanism. |
| if (elapsed_timer_ && current_auth_ == last_auth_) |
| UMA_ENROLLMENT_TIME(kMetricEnrollmentTimeFailure, elapsed_timer_); |
| } |
| |
| 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( |
| domain_join_config, std::string() /* machine_name */, |
| std::string() /* username */, authpolicy::ERROR_NONE); |
| } |
| |
| void EnrollmentScreen::OnActiveDirectoryJoined( |
| const std::string& machine_name, |
| const std::string& username, |
| authpolicy::ErrorType error, |
| const std::string& machine_domain) { |
| if (error == authpolicy::ERROR_NONE) { |
| view_->ShowEnrollmentSpinnerScreen(); |
| std::move(on_joined_callback_).Run(machine_domain); |
| return; |
| } |
| view_->ShowActiveDirectoryScreen(std::string() /* domain_join_config */, |
| machine_name, username, error); |
| } |
| |
| } // namespace chromeos |