| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/policy/enrollment/auto_enrollment_controller.h" |
| |
| #include <memory> |
| #include <string_view> |
| |
| #include "ash/constants/ash_switches.h" |
| #include "base/check_is_test.h" |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ash/login/oobe_configuration.h" |
| #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h" |
| #include "chrome/browser/ash/policy/enrollment/auto_enrollment_client.h" |
| #include "chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h" |
| #include "chrome/browser/ash/policy/enrollment/auto_enrollment_state.h" |
| #include "chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.h" |
| #include "chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.h" |
| #include "chrome/browser/ash/policy/enrollment/psm/construct_rlwe_id.h" |
| #include "chrome/browser/ash/policy/enrollment/psm/rlwe_dmserver_client_impl.h" |
| #include "chrome/browser/ash/policy/server_backed_state/server_backed_state_keys_broker.h" |
| #include "chrome/browser/ash/settings/device_settings_service.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/browser_process_platform_part.h" |
| #include "chrome/browser/net/system_network_context_manager.h" |
| #include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h" |
| #include "chromeos/ash/components/dbus/dbus_thread_manager.h" |
| #include "chromeos/ash/components/dbus/device_management/install_attributes_client.h" |
| #include "chromeos/ash/components/dbus/system_clock/system_clock_client.h" |
| #include "chromeos/ash/components/dbus/system_clock/system_clock_sync_observation.h" |
| #include "chromeos/ash/components/install_attributes/install_attributes.h" |
| #include "chromeos/ash/components/network/network_handler.h" |
| #include "chromeos/ash/components/network/network_state_handler.h" |
| #include "chromeos/ash/components/system/statistics_provider.h" |
| #include "components/policy/core/common/cloud/device_management_service.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| |
| // This is used for logs that may not be strictly necessary but are of great use |
| // because they will log whether determinations are needed or not, along with |
| // some context. The information used to be logged using VLOG(1), and therefore |
| // was not available in customer logs. Because the only other logs have some |
| // ambiguity (e.g. there will not be a log if the device decides it does not |
| // need to make a determination), troubleshooting is difficult. If this changes, |
| // this can be made VLOG(1) again. |
| // |
| // We use LOG(WARNING) to guarantee that the messages will be into feedback |
| // reports. |
| #define LOG_DETERMINATION() LOG(WARNING) |
| |
| namespace policy { |
| namespace { |
| |
| const int kMaxRequestStateKeysTries = 10; |
| |
| // Maximum time to wait for the auto-enrollment check to reach a decision. |
| // Note that this encompasses all steps `AutoEnrollmentController` performs in |
| // order to determine if the device should be auto-enrolled. |
| // If `kSafeguardTimeout` after `Start()` has been called, |
| // `AutoEnrollmentController::state()` is still AutoEnrollmentState::kPending, |
| // the AutoEnrollmentController will switch to |
| // `AutoEnrollmentResult::kNoEnrollment` or |
| // `AutoEnrollmentSafeguardTimeoutError` (see |
| // `AutoEnrollmentController::Timeout`). Note that this timeout should not be |
| // too short, because one of the steps `AutoEnrollmentController` performs - |
| // downloading identifier hash buckets - can be non-negligible, especially on 2G |
| // connections. |
| constexpr base::TimeDelta kSafeguardTimeout = base::Seconds(90); |
| |
| // Maximum time to wait for time sync before forcing a decision on whether |
| // Initial Enrollment should be performed. This corresponds to at least seven |
| // TCP retransmissions attempts to the remote server used to update the system |
| // clock. |
| constexpr base::TimeDelta kSystemClockSyncWaitTimeout = base::Seconds(45); |
| |
| // A callback that will be invoked when the system clock has been synchronized, |
| // or if system clock synchronization has failed. |
| using SystemClockSyncCallback = base::OnceCallback<void( |
| AutoEnrollmentController::SystemClockSyncState system_clock_sync_state)>; |
| |
| // Returns the int value of the `switch_name` argument, clamped to the [0, 62] |
| // interval. Returns 0 if the argument doesn't exist or isn't an int value. |
| int GetSanitizedArg(const std::string& switch_name) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (!command_line->HasSwitch(switch_name)) { |
| return 0; |
| } |
| std::string value = command_line->GetSwitchValueASCII(switch_name); |
| int int_value; |
| if (!base::StringToInt(value, &int_value)) { |
| LOG(ERROR) << "Switch \"" << switch_name << "\" is not a valid int. " |
| << "Defaulting to 0."; |
| return 0; |
| } |
| if (int_value < 0) { |
| LOG(ERROR) << "Switch \"" << switch_name << "\" can't be negative. " |
| << "Using 0"; |
| return 0; |
| } |
| if (int_value > AutoEnrollmentClient::kMaximumPower) { |
| LOG(ERROR) << "Switch \"" << switch_name << "\" can't be greater than " |
| << AutoEnrollmentClient::kMaximumPower << ". Using " |
| << AutoEnrollmentClient::kMaximumPower << "."; |
| return AutoEnrollmentClient::kMaximumPower; |
| } |
| return int_value; |
| } |
| |
| bool IsSystemClockSynchronized( |
| AutoEnrollmentController::SystemClockSyncState state) { |
| switch (state) { |
| case AutoEnrollmentController::SystemClockSyncState::kSynchronized: |
| case AutoEnrollmentController::SystemClockSyncState::kSyncFailed: |
| return true; |
| case AutoEnrollmentController::SystemClockSyncState::kCanWaitForSync: |
| case AutoEnrollmentController::SystemClockSyncState::kWaitingForSync: |
| return false; |
| } |
| } |
| |
| enum class AutoEnrollmentControllerTimeoutReport { |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| kTimeoutCancelled = 0, |
| kTimeoutFRE = 1, |
| kTimeout = 2, |
| kTimeoutUnified = 3, |
| kMaxValue = kTimeoutUnified |
| }; |
| |
| void ReportTimeoutUMA(AutoEnrollmentControllerTimeoutReport report) { |
| base::UmaHistogramEnumeration("Enterprise.AutoEnrollmentControllerTimeout", |
| report); |
| } |
| |
| bool IsFinalAutoEnrollmentState(AutoEnrollmentState state) { |
| return state.has_value(); |
| } |
| |
| } // namespace |
| |
| EnrollmentFwmpHelper::EnrollmentFwmpHelper( |
| ash::InstallAttributesClient* install_attributes_client) |
| : install_attributes_client_(install_attributes_client) {} |
| |
| EnrollmentFwmpHelper::~EnrollmentFwmpHelper() = default; |
| |
| void EnrollmentFwmpHelper::DetermineDevDisableBoot( |
| ResultCallback result_callback) { |
| // D-Bus services may not be available yet, so we call |
| // WaitForServiceToBeAvailable. See https://crbug.com/841627. |
| install_attributes_client_->WaitForServiceToBeAvailable(base::BindOnce( |
| &EnrollmentFwmpHelper::RequestFirmwareManagementParameters, |
| weak_ptr_factory_.GetWeakPtr(), std::move(result_callback))); |
| } |
| |
| void EnrollmentFwmpHelper::RequestFirmwareManagementParameters( |
| ResultCallback result_callback, |
| bool service_is_ready) { |
| if (!service_is_ready) { |
| LOG(ERROR) << "Failed waiting for cryptohome D-Bus service availability."; |
| return std::move(result_callback).Run(false); |
| } |
| |
| device_management::GetFirmwareManagementParametersRequest request; |
| install_attributes_client_->GetFirmwareManagementParameters( |
| request, |
| base::BindOnce( |
| &EnrollmentFwmpHelper::OnGetFirmwareManagementParametersReceived, |
| weak_ptr_factory_.GetWeakPtr(), std::move(result_callback))); |
| } |
| |
| void EnrollmentFwmpHelper::OnGetFirmwareManagementParametersReceived( |
| ResultCallback result_callback, |
| std::optional<device_management::GetFirmwareManagementParametersReply> |
| reply) { |
| if (!reply.has_value() || reply->error() != |
| device_management::DeviceManagementErrorCode:: |
| DEVICE_MANAGEMENT_ERROR_NOT_SET) { |
| LOG(ERROR) << "Failed to retrieve firmware management parameters."; |
| return std::move(result_callback).Run(false); |
| } |
| |
| const bool dev_disable_boot = |
| (reply->fwmp().flags() & cryptohome::DEVELOPER_DISABLE_BOOT); |
| |
| std::move(result_callback).Run(dev_disable_boot); |
| } |
| |
| AutoEnrollmentController::AutoEnrollmentController( |
| scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory) |
| : AutoEnrollmentController( |
| ash::DeviceSettingsService::Get(), |
| g_browser_process->platform_part() |
| ->browser_policy_connector_ash() |
| ->device_management_service(), |
| g_browser_process->platform_part() |
| ->browser_policy_connector_ash() |
| ->GetStateKeysBroker(), |
| ash::NetworkHandler::Get()->network_state_handler(), |
| std::make_unique<AutoEnrollmentClientImpl::FactoryImpl>(), |
| base::BindRepeating(&policy::psm::RlweDmserverClientImpl::Create), |
| base::BindRepeating(EnrollmentStateFetcher::Create), |
| shared_url_loader_factory) {} |
| |
| AutoEnrollmentController::AutoEnrollmentController( |
| ash::DeviceSettingsService* device_settings_service, |
| DeviceManagementService* device_management_service, |
| ServerBackedStateKeysBroker* state_keys_broker, |
| ash::NetworkStateHandler* network_state_handler, |
| std::unique_ptr<AutoEnrollmentClient::Factory> |
| auto_enrollment_client_factory, |
| RlweClientFactory psm_rlwe_client_factory, |
| EnrollmentStateFetcher::Factory enrollment_state_fetcher_factory, |
| scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory) |
| : device_settings_service_(device_settings_service), |
| device_management_service_(device_management_service), |
| state_keys_broker_(state_keys_broker), |
| enrollment_fwmp_helper_(std::make_unique<EnrollmentFwmpHelper>( |
| ash::InstallAttributesClient::Get())), |
| auto_enrollment_client_factory_( |
| std::move(auto_enrollment_client_factory)), |
| psm_rlwe_client_factory_(std::move(psm_rlwe_client_factory)), |
| enrollment_state_fetcher_factory_( |
| std::move(enrollment_state_fetcher_factory)), |
| shared_url_loader_factory_(shared_url_loader_factory), |
| network_state_handler_(network_state_handler) {} |
| |
| AutoEnrollmentController::~AutoEnrollmentController() = default; |
| |
| void AutoEnrollmentController::Start() { |
| LOG(WARNING) << "Starting auto-enrollment controller."; |
| |
| if (state_.has_value() && IsFinalAutoEnrollmentState(state_.value())) { |
| return; |
| } |
| |
| if (!network_state_observation_.IsObserving()) { |
| // The controller could have already subscribed on the start and now we're |
| // restarting after an error. |
| network_state_observation_.Observe(network_state_handler_); |
| } |
| |
| if (!AutoEnrollmentTypeChecker::Initialized()) { |
| if (!auto_enrollment_check_type_init_started_) { |
| auto_enrollment_check_type_init_started_ = true; |
| AutoEnrollmentTypeChecker::Initialize( |
| shared_url_loader_factory_, |
| base::BindOnce(&AutoEnrollmentController::Start, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| return; |
| } |
| |
| if (IsInProgress()) { |
| return; |
| } |
| |
| // Arm the belts-and-suspenders timer to avoid hangs. |
| safeguard_timer_.Start(FROM_HERE, kSafeguardTimeout, |
| base::BindOnce(&AutoEnrollmentController::Timeout, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| if (AutoEnrollmentTypeChecker::IsUnifiedStateDeterminationEnabled()) { |
| // Emulate required FRE to prevent users from skipping enrollment. |
| auto_enrollment_check_type_ = AutoEnrollmentTypeChecker::CheckType:: |
| kForcedReEnrollmentExplicitlyRequired; |
| |
| device_management_service_->ScheduleInitialization(0); |
| enrollment_state_fetcher_ = enrollment_state_fetcher_factory_.Run( |
| base::BindRepeating(&AutoEnrollmentController::UpdateState, |
| weak_ptr_factory_.GetWeakPtr()), |
| g_browser_process->local_state(), psm_rlwe_client_factory_, |
| device_management_service_, shared_url_loader_factory_, |
| ash::SystemClockClient::Get(), state_keys_broker_, |
| device_settings_service_); |
| |
| enrollment_state_fetcher_->Start(); |
| return; |
| } |
| |
| request_state_keys_tries_ = 0; |
| |
| // The system clock sync state is not known yet, and this |
| // `AutoEnrollmentController` could wait for it if requested. |
| system_clock_sync_state_ = SystemClockSyncState::kCanWaitForSync; |
| |
| enrollment_fwmp_helper_->DetermineDevDisableBoot( |
| base::BindOnce(&AutoEnrollmentController::OnDevDisableBootDetermined, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AutoEnrollmentController::OnDevDisableBootDetermined( |
| bool dev_disable_boot) { |
| dev_disable_boot_ = dev_disable_boot; |
| |
| StartWithSystemClockSyncState(); |
| } |
| |
| void AutoEnrollmentController::StartWithSystemClockSyncState() { |
| auto_enrollment_check_type_ = |
| AutoEnrollmentTypeChecker::DetermineAutoEnrollmentCheckType( |
| IsSystemClockSynchronized(system_clock_sync_state_), |
| ash::system::StatisticsProvider::GetInstance(), dev_disable_boot_); |
| if (auto_enrollment_check_type_ == |
| AutoEnrollmentTypeChecker::CheckType::kNone) { |
| UpdateState(AutoEnrollmentResult::kNoEnrollment); |
| return; |
| } |
| // If waiting for system clock synchronization has been triggered, wait until |
| // it finishes (this function will be called again when a result is |
| // available). |
| if (system_clock_sync_state_ == SystemClockSyncState::kWaitingForSync) { |
| return; |
| } |
| |
| if (auto_enrollment_check_type_ == AutoEnrollmentTypeChecker::CheckType:: |
| kUnknownDueToMissingSystemClockSync) { |
| DCHECK_EQ(system_clock_sync_state_, SystemClockSyncState::kCanWaitForSync); |
| system_clock_sync_state_ = SystemClockSyncState::kWaitingForSync; |
| |
| LOG(WARNING) << "Waiting for clock sync"; |
| // Use `client_start_weak_factory_` so the callback is not invoked if |
| // `Timeout` has been called in the meantime (after `kSafeguardTimeout`). |
| system_clock_sync_observation_ = |
| ash::SystemClockSyncObservation::WaitForSystemClockSync( |
| ash::SystemClockClient::Get(), kSystemClockSyncWaitTimeout, |
| base::BindOnce(&AutoEnrollmentController::OnSystemClockSyncResult, |
| client_start_weak_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| LOG(WARNING) << "Get ownership status to check if it's enrollment recovery"; |
| device_settings_service_->GetOwnershipStatusAsync( |
| base::BindOnce(&AutoEnrollmentController::OnOwnershipStatusCheckDone, |
| client_start_weak_factory_.GetWeakPtr())); |
| } |
| |
| void AutoEnrollmentController::Retry() { |
| if (client_) { |
| client_->Retry(); |
| } else { |
| Start(); |
| } |
| } |
| |
| base::CallbackListSubscription |
| AutoEnrollmentController::RegisterProgressCallback( |
| const ProgressCallbackList::CallbackType& callback) { |
| return progress_callbacks_.Add(callback); |
| } |
| |
| void AutoEnrollmentController::PortalStateChanged( |
| const ash::NetworkState* /*default_network*/, |
| const ash::NetworkState::PortalState portal_state) { |
| // It is safe to retry regardless of the current state: if the check is idle |
| // or failed, we will restart the check process. If the check is in progress, |
| // the retry call will be ignored. |
| if (portal_state == ash::NetworkState::PortalState::kOnline) { |
| Retry(); |
| } |
| } |
| |
| void AutoEnrollmentController::OnShuttingDown() { |
| network_state_observation_.Reset(); |
| } |
| |
| void AutoEnrollmentController::SetRlweClientFactoryForTesting( |
| RlweClientFactory test_factory) { |
| CHECK_IS_TEST(); |
| psm_rlwe_client_factory_ = std::move(test_factory); |
| } |
| |
| void AutoEnrollmentController::SetAutoEnrollmentClientFactoryForTesting( |
| std::unique_ptr<AutoEnrollmentClient::Factory> |
| auto_enrollment_client_factory) { |
| CHECK_IS_TEST(); |
| auto_enrollment_client_factory_ = std::move(auto_enrollment_client_factory); |
| } |
| |
| void AutoEnrollmentController::OnOwnershipStatusCheckDone( |
| ash::DeviceSettingsService::OwnershipStatus status) { |
| switch (status) { |
| case ash::DeviceSettingsService::OwnershipStatus::kOwnershipNone: |
| switch (auto_enrollment_check_type_) { |
| case AutoEnrollmentTypeChecker::CheckType:: |
| kForcedReEnrollmentExplicitlyRequired: |
| case AutoEnrollmentTypeChecker::CheckType:: |
| kForcedReEnrollmentImplicitlyRequired: |
| ++request_state_keys_tries_; |
| // For FRE, request state keys first. |
| LOG(WARNING) << "Requesting state keys"; |
| state_keys_broker_->RequestStateKeys( |
| base::BindOnce(&AutoEnrollmentController::StartClientForFRE, |
| client_start_weak_factory_.GetWeakPtr())); |
| break; |
| case AutoEnrollmentTypeChecker::CheckType::kInitialStateDetermination: |
| LOG(WARNING) << "Start client for initial state determination"; |
| StartClientForInitialEnrollment(); |
| break; |
| case AutoEnrollmentTypeChecker::CheckType:: |
| kUnknownDueToMissingSystemClockSync: |
| case AutoEnrollmentTypeChecker::CheckType::kNone: |
| // The ownership check is only triggered if |
| // `auto_enrollment_check_type_` indicates that an auto-enrollment |
| // check should be done. |
| NOTREACHED(); |
| break; |
| } |
| return; |
| case ash::DeviceSettingsService::OwnershipStatus::kOwnershipTaken: |
| LOG(WARNING) << "Device already owned, skipping auto-enrollment check."; |
| UpdateState(AutoEnrollmentResult::kNoEnrollment); |
| return; |
| case ash::DeviceSettingsService::OwnershipStatus::kOwnershipUnknown: |
| LOG(ERROR) << "Ownership unknown, skipping auto-enrollment check."; |
| UpdateState(AutoEnrollmentResult::kNoEnrollment); |
| return; |
| } |
| } |
| |
| void AutoEnrollmentController::StartClientForFRE( |
| const std::vector<std::string>& state_keys) { |
| if (state_keys.empty()) { |
| LOG(ERROR) << "No state keys available."; |
| if (auto_enrollment_check_type_ == |
| AutoEnrollmentTypeChecker::CheckType:: |
| kForcedReEnrollmentExplicitlyRequired) { |
| if (request_state_keys_tries_ >= kMaxRequestStateKeysTries) { |
| if (safeguard_timer_.IsRunning()) { |
| safeguard_timer_.Stop(); |
| } |
| Timeout(); |
| return; |
| } |
| ++request_state_keys_tries_; |
| // Retry to fetch the state keys. For devices where FRE is required to be |
| // checked, we can't proceed with empty state keys. |
| state_keys_broker_->RequestStateKeys( |
| base::BindOnce(&AutoEnrollmentController::StartClientForFRE, |
| client_start_weak_factory_.GetWeakPtr())); |
| } else { |
| UpdateState(AutoEnrollmentResult::kNoEnrollment); |
| } |
| return; |
| } |
| |
| int power_initial = |
| GetSanitizedArg(ash::switches::kEnterpriseEnrollmentInitialModulus); |
| int power_limit = |
| GetSanitizedArg(ash::switches::kEnterpriseEnrollmentModulusLimit); |
| if (power_initial > power_limit) { |
| LOG(ERROR) << "Initial auto-enrollment modulus is larger than the limit, " |
| "clamping to the limit."; |
| power_initial = power_limit; |
| } |
| |
| device_management_service_->ScheduleInitialization(0); |
| |
| client_ = auto_enrollment_client_factory_->CreateForFRE( |
| base::BindRepeating(&AutoEnrollmentController::UpdateState, |
| weak_ptr_factory_.GetWeakPtr()), |
| device_management_service_, g_browser_process->local_state(), |
| shared_url_loader_factory_, state_keys.front(), power_initial, |
| power_limit); |
| |
| LOG(WARNING) << "Starting auto-enrollment client for FRE."; |
| client_->Start(); |
| } |
| |
| void AutoEnrollmentController::OnSystemClockSyncResult( |
| bool system_clock_synchronized) { |
| system_clock_sync_state_ = system_clock_synchronized |
| ? SystemClockSyncState::kSynchronized |
| : SystemClockSyncState::kSyncFailed; |
| LOG(WARNING) << "System clock " |
| << (system_clock_synchronized ? "synchronized" |
| : "failed to synchronize"); |
| // Only call StartWithSystemClockSyncState() to determine the auto-enrollment |
| // type if the system clock could synchronize successfully. Otherwise, return |
| // an error to show to not to proceed with the auto-enrollment checks until |
| // AutoEnrollmentController::Start() is called again by a network state |
| // change or network selection. |
| if (system_clock_sync_state_ == SystemClockSyncState::kSynchronized) { |
| StartWithSystemClockSyncState(); |
| } else { |
| UpdateState(base::unexpected(AutoEnrollmentSystemClockSyncError{})); |
| } |
| } |
| |
| void AutoEnrollmentController::StartClientForInitialEnrollment() { |
| device_management_service_->ScheduleInitialization(0); |
| |
| ash::system::StatisticsProvider* provider = |
| ash::system::StatisticsProvider::GetInstance(); |
| const std::optional<std::string_view> serial_number = |
| provider->GetMachineID(); |
| const std::optional<std::string_view> rlz_brand_code = |
| provider->GetMachineStatistic(ash::system::kRlzBrandCodeKey); |
| // The Initial State Determination should not be started if the serial number |
| // or brand code are missing. This is ensured in |
| // `GetInitialStateDeterminationRequirement`. |
| CHECK(serial_number); |
| CHECK(!serial_number->empty()); |
| CHECK(rlz_brand_code); |
| CHECK(!rlz_brand_code->empty()); |
| |
| const auto plaintext_id = psm::ConstructRlweId(); |
| client_ = auto_enrollment_client_factory_->CreateForInitialEnrollment( |
| base::BindRepeating(&AutoEnrollmentController::UpdateState, |
| weak_ptr_factory_.GetWeakPtr()), |
| device_management_service_, g_browser_process->local_state(), |
| shared_url_loader_factory_, std::string(serial_number.value()), |
| std::string(rlz_brand_code.value()), |
| std::make_unique<psm::RlweDmserverClientImpl>( |
| device_management_service_, shared_url_loader_factory_, plaintext_id, |
| psm_rlwe_client_factory_), |
| ash::OobeConfiguration::Get()); |
| |
| LOG(WARNING) << "Starting auto-enrollment client for Initial Enrollment."; |
| client_->Start(); |
| } |
| |
| void AutoEnrollmentController::UpdateState(AutoEnrollmentState new_state) { |
| LOG(WARNING) << "New auto-enrollment state: " |
| << AutoEnrollmentStateToString(new_state); |
| state_ = new_state; |
| |
| if (IsFinalAutoEnrollmentState(state_.value())) { |
| network_state_observation_.Reset(); |
| } |
| |
| // Stop the safeguard timer once a result comes in. |
| safeguard_timer_.Stop(); |
| // Reset enrollment state fetcher to allow restarting. |
| enrollment_state_fetcher_.reset(); |
| ReportTimeoutUMA(AutoEnrollmentControllerTimeoutReport::kTimeoutCancelled); |
| |
| // Device disabling mode is relying on device state stored in install |
| // attributes. In case that file is corrupted, this should prevent device |
| // re-enabling. |
| if (state_ == AutoEnrollmentResult::kDisabled) { |
| DeviceMode device_mode = ash::InstallAttributes::Get()->GetMode(); |
| if (device_mode == DeviceMode::DEVICE_MODE_PENDING || |
| device_mode == DeviceMode::DEVICE_MODE_NOT_SET) { |
| device_settings_service_->SetDeviceMode( |
| DeviceMode::DEVICE_MODE_ENTERPRISE); |
| } |
| } |
| |
| if (state_ == AutoEnrollmentResult::kNoEnrollment || |
| state_ == AutoEnrollmentResult::kSuggestedEnrollment) { |
| StartCleanupForcedReEnrollment(); |
| } else { |
| progress_callbacks_.Notify(state_.value()); |
| } |
| } |
| |
| void AutoEnrollmentController::StartCleanupForcedReEnrollment() { |
| // D-Bus services may not be available yet, so we call |
| // WaitForServiceToBeAvailable. See https://crbug.com/841627. |
| ash::InstallAttributesClient::Get()->WaitForServiceToBeAvailable( |
| base::BindOnce( |
| &AutoEnrollmentController::StartRemoveFirmwareManagementParameters, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AutoEnrollmentController::StartRemoveFirmwareManagementParameters( |
| bool service_is_ready) { |
| DCHECK(state_ == AutoEnrollmentResult::kNoEnrollment || |
| state_ == AutoEnrollmentResult::kSuggestedEnrollment); |
| if (!service_is_ready) { |
| LOG(ERROR) << "Failed waiting for cryptohome D-Bus service availability."; |
| progress_callbacks_.Notify(state_.value()); |
| return; |
| } |
| |
| device_management::RemoveFirmwareManagementParametersRequest request; |
| ash::InstallAttributesClient::Get()->RemoveFirmwareManagementParameters( |
| request, |
| base::BindOnce( |
| &AutoEnrollmentController::OnFirmwareManagementParametersRemoved, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AutoEnrollmentController::OnFirmwareManagementParametersRemoved( |
| std::optional<device_management::RemoveFirmwareManagementParametersReply> |
| reply) { |
| if (!reply.has_value() || reply->error() != |
| device_management::DeviceManagementErrorCode:: |
| DEVICE_MANAGEMENT_ERROR_NOT_SET) { |
| LOG(ERROR) << "Failed to remove firmware management parameters."; |
| } |
| |
| // D-Bus services may not be available yet, so we call |
| // WaitForServiceToBeAvailable. See https://crbug.com/841627. |
| ash::SessionManagerClient::Get()->WaitForServiceToBeAvailable( |
| base::BindOnce(&AutoEnrollmentController::StartClearForcedReEnrollmentVpd, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AutoEnrollmentController::StartClearForcedReEnrollmentVpd( |
| bool service_is_ready) { |
| DCHECK(state_ == AutoEnrollmentResult::kNoEnrollment || |
| state_ == AutoEnrollmentResult::kSuggestedEnrollment); |
| if (!service_is_ready) { |
| LOG(ERROR) |
| << "Failed waiting for session_manager D-Bus service availability."; |
| progress_callbacks_.Notify(state_.value()); |
| return; |
| } |
| |
| ash::SessionManagerClient::Get()->ClearForcedReEnrollmentVpd( |
| base::BindOnce(&AutoEnrollmentController::OnForcedReEnrollmentVpdCleared, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AutoEnrollmentController::OnForcedReEnrollmentVpdCleared(bool reply) { |
| if (!reply) { |
| LOG(ERROR) << "Failed to clear forced re-enrollment flags in RW VPD."; |
| } |
| |
| progress_callbacks_.Notify(state_.value()); |
| } |
| |
| void AutoEnrollmentController::Timeout() { |
| if (AutoEnrollmentTypeChecker::IsUnifiedStateDeterminationEnabled()) { |
| // This can actually happen in some cases, for example when state key |
| // generation is waiting for time sync or the server just doesn't reply and |
| // keeps the connection open. |
| LOG(ERROR) << "EnrollmentStateFetcher didn't complete within time limit."; |
| UpdateState(base::unexpected(AutoEnrollmentSafeguardTimeoutError{})); |
| ReportTimeoutUMA(AutoEnrollmentControllerTimeoutReport::kTimeoutUnified); |
| return; |
| } |
| |
| // When tightening the FRE flows, as a cautionary measure (to prevent |
| // interference with consumer devices) timeout was chosen to only enforce FRE |
| // for EXPLICITLY_REQUIRED. |
| // TODO(igorcov): Investigate the remaining causes of hitting timeout and |
| // potentially either remove the timeout altogether or enforce FRE in the |
| // REQUIRED case as well. |
| if (client_start_weak_factory_.HasWeakPtrs() && |
| auto_enrollment_check_type_ != |
| AutoEnrollmentTypeChecker::CheckType:: |
| kForcedReEnrollmentExplicitlyRequired) { |
| // If the callbacks to check ownership status or state keys are still |
| // pending, there's a bug in the code running on the device. No use in |
| // retrying anything, need to fix that bug. |
| LOG(ERROR) << "Failed to start auto-enrollment check, fix the code!"; |
| UpdateState(AutoEnrollmentResult::kNoEnrollment); |
| ReportTimeoutUMA(AutoEnrollmentControllerTimeoutReport::kTimeout); |
| } else { |
| // This can actually happen in some cases, for example when state key |
| // generation is waiting for time sync or the server just doesn't reply and |
| // keeps the connection open. |
| LOG(ERROR) << "AutoEnrollmentClient didn't complete within time limit."; |
| UpdateState(base::unexpected(AutoEnrollmentSafeguardTimeoutError{})); |
| ReportTimeoutUMA(AutoEnrollmentControllerTimeoutReport::kTimeoutFRE); |
| } |
| |
| client_.reset(); |
| |
| // Make sure to nuke pending `client_` start sequences. |
| client_start_weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| bool AutoEnrollmentController::IsInProgress() const { |
| if (AutoEnrollmentTypeChecker::IsUnifiedStateDeterminationEnabled()) { |
| if (enrollment_state_fetcher_) { |
| // If a fetcher has already been created, bail out. |
| LOG(ERROR) << "Enrollment state fetcher is already running."; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // If a client is being created or already existing, bail out. |
| if (client_start_weak_factory_.HasWeakPtrs() || client_) { |
| LOG(ERROR) << "Enrollment state client is already running."; |
| return true; |
| } |
| |
| // The timer runs from `Start()` where controller starts determining state, |
| // till `UpdateState()` where the controller receives a state or an error. |
| // Hence it can be used to decide whether the controller is running or not. |
| // If any of steps between `Start()` and `UpdateState()` are excluded from |
| // the timing, or the timer is extended to some other steps, the check will |
| // become wrong. |
| if (safeguard_timer_.IsRunning()) { |
| LOG(ERROR) << "State determination is already running."; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void AutoEnrollmentController::SetEnrollmentStateFetcherFactoryForTesting( |
| EnrollmentStateFetcher::Factory enrollment_state_fetcher_factory) { |
| CHECK_IS_TEST(); |
| if (enrollment_state_fetcher_factory) { |
| enrollment_state_fetcher_factory_ = enrollment_state_fetcher_factory; |
| } else { |
| enrollment_state_fetcher_factory_ = |
| base::BindRepeating(EnrollmentStateFetcher::Create); |
| } |
| } |
| |
| } // namespace policy |