blob: 1558cebae33650d232cd270496b9af5b268f70a4 [file] [log] [blame]
// 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 "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.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_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/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/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 {
// 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);
enum class AutoEnrollmentControllerTimeoutReport {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
kTimeoutCancelled = 0,
// kTimeoutFRE_Obsolete = 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
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(),
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,
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),
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() {
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 (IsInProgress()) {
return;
}
// If Unified State Determination is turned off (e.g. by command line switch),
// then enrollment will not be forced.
if (!AutoEnrollmentTypeChecker::IsUnifiedStateDeterminationEnabled()) {
UpdateState(AutoEnrollmentResult::kNoEnrollment);
return;
}
// Arm the belts-and-suspenders timer to avoid hangs.
safeguard_timer_.Start(FROM_HERE, kSafeguardTimeout,
base::BindOnce(&AutoEnrollmentController::Timeout,
weak_ptr_factory_.GetWeakPtr()));
// TODO(crbug.com/353731379):
// BrowserPolicyConnector::ScheduleServiceInitialization.
if (device_management_service_) {
device_management_service_->ScheduleInitialization(0);
} else {
CHECK_IS_TEST();
}
LOG(WARNING) << "Starting state determination";
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_,
state_keys_broker_, device_settings_service_,
ash::OobeConfiguration::Get());
enrollment_state_fetcher_->Start();
return;
}
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) {
Start();
}
}
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::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::StartClearBlockDevmodeVpd,
weak_ptr_factory_.GetWeakPtr()));
}
void AutoEnrollmentController::StartClearBlockDevmodeVpd(
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;
}
// This clears block_devmode in the VPD.
ash::SessionManagerClient::Get()->ClearBlockDevmodeVpd(
base::BindOnce(&AutoEnrollmentController::OnBlockDevmodeClearedVpd,
weak_ptr_factory_.GetWeakPtr()));
}
void AutoEnrollmentController::OnBlockDevmodeClearedVpd(bool succeeded) {
if (!succeeded) {
LOG(ERROR) << "Failed to clear forced re-enrollment flags in RW VPD.";
}
progress_callbacks_.Notify(state_.value());
}
void AutoEnrollmentController::Timeout() {
// The timer is only started if Unified State Determination is enabled.
CHECK(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);
}
bool AutoEnrollmentController::IsInProgress() const {
if (AutoEnrollmentTypeChecker::IsUnifiedStateDeterminationEnabled()) {
if (enrollment_state_fetcher_) {
// If a fetcher has already been created, bail out.
VLOG(1) << "Enrollment state fetcher 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);
}
}
bool AutoEnrollmentController::IsGuestSigninAllowed() const {
return state_ == AutoEnrollmentResult::kNoEnrollment ||
state_ == AutoEnrollmentResult::kSuggestedEnrollment;
}
} // namespace policy