chromium/chrome/browser/ash/policy/enrollment/auto_enrollment_controller.h

// 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.

#ifndef CHROME_BROWSER_ASH_POLICY_ENROLLMENT_AUTO_ENROLLMENT_CONTROLLER_H_
#define CHROME_BROWSER_ASH_POLICY_ENROLLMENT_AUTO_ENROLLMENT_CONTROLLER_H_

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/callback_list.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/policy/enrollment/auto_enrollment_client.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/settings/device_settings_service.h"
#include "chromeos/ash/components/dbus/cryptohome/UserDataAuth.pb.h"
#include "chromeos/ash/components/dbus/device_management/device_management_interface.pb.h"
#include "chromeos/ash/components/network/network_state_handler_observer.h"

namespace ash {
class InstallAttributesClient;
class NetworkStateHandler;
class SystemClockSyncObservation;
}  // namespace ash

namespace policy {

class DeviceManagementService;
class ServerBackedStateKeysBroker;

// Helper class to obtain FWMP flags.
// See b/268267865.
class EnrollmentFwmpHelper {
 public:
  using ResultCallback = base::OnceCallback<void(bool)>;

  // `install_attributes_client` has to be not nullptr. It will be used to
  // obtain the FWMP flags.
  explicit EnrollmentFwmpHelper(
      ash::InstallAttributesClient* install_attributes_client);
  EnrollmentFwmpHelper(const EnrollmentFwmpHelper&) = delete;
  EnrollmentFwmpHelper& operator=(const EnrollmentFwmpHelper&) = delete;
  ~EnrollmentFwmpHelper();

  // Read FWMP.dev_disable_boot (a.k.a. block_devmode) and return the
  // value asynchronously via result_callback.
  // Return `false` in case of errors (e.g. `install_attributes_client_` or
  // FWMP not available).
  void DetermineDevDisableBoot(ResultCallback result_callback);

 private:
  void RequestFirmwareManagementParameters(ResultCallback result_callback,
                                           bool service_is_ready);

  void OnGetFirmwareManagementParametersReceived(
      ResultCallback result_callback,
      std::optional<device_management::GetFirmwareManagementParametersReply>
          reply);

  raw_ptr<ash::InstallAttributesClient> install_attributes_client_;
  base::WeakPtrFactory<EnrollmentFwmpHelper> weak_ptr_factory_{this};
};

// Drives the forced re-enrollment check (for historical reasons called
// auto-enrollment check), running an `AutoEnrollmentClient` if appropriate to
// make a decision.
// The controller tracks network status to retry when the device is going
// online in case of a prior failure.
class AutoEnrollmentController : public ash::NetworkStateHandlerObserver {
 public:
  using ProgressCallbackList =
      base::RepeatingCallbackList<void(AutoEnrollmentState)>;
  using RlweClientFactory =
      policy::psm::RlweDmserverClientImpl::RlweClientFactory;

  // State of the system clock.
  enum class SystemClockSyncState {
    // This `AutoEnrollmentController` has not tried to wait for the system
    // clock sync state yet.
    kCanWaitForSync,
    // Currently waiting for the system clock to become synchronized.
    kWaitingForSync,
    // Waiting for the system clock to become synchronized timed out.
    kSyncFailed,
    // The system clock is synchronized
    kSynchronized
  };

  explicit AutoEnrollmentController(
      scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory);

  AutoEnrollmentController(const AutoEnrollmentController&) = delete;
  AutoEnrollmentController& operator=(const AutoEnrollmentController&) = delete;

  ~AutoEnrollmentController() override;

  // Starts the auto-enrollment check.  Safe to call multiple times: aborts in
  // case a check is currently running or a decision has already been made.
  void Start();

  // Retry checking.
  void Retry();

  // Registers a callback to invoke on state changes.
  base::CallbackListSubscription RegisterProgressCallback(
      const ProgressCallbackList::CallbackType& callback);

  // ash::NetworkStateHandlerObserver:
  void PortalStateChanged(
      const ash::NetworkState* default_network,
      const ash::NetworkState::PortalState portal_state) override;
  void OnShuttingDown() override;

  const std::optional<AutoEnrollmentState>& state() const { return state_; }

  // Returns the auto-enrollment check type performed by this client.
  // The returned value will be `CheckType::kNone` before calling `Start()`.
  AutoEnrollmentTypeChecker::CheckType auto_enrollment_check_type() const {
    return auto_enrollment_check_type_;
  }

  // Sets the factory function that will be used to create the
  // `psm::RlweClient` for tests.
  void SetRlweClientFactoryForTesting(RlweClientFactory test_factory);

  // Sets the factory that will be used to create the `AutoEnrollmentClient`.
  // Ownership is not transferred when calling this - the caller must ensure
  // that the `Factory` pointed to by `auto_enrollment_client_factory` remains
  // valid while this `AutoEnrollmentController` is using it.
  // To use the default factory again, call with nullptr.
  void SetAutoEnrollmentClientFactoryForTesting(
      std::unique_ptr<AutoEnrollmentClient::Factory>
          auto_enrollment_client_factory);

  // Sets factory that will be used to create `EnrollmentStateFetcher`.  To use
  // the default factory again, call with `base::NullCallback()`.
  void SetEnrollmentStateFetcherFactoryForTesting(
      EnrollmentStateFetcher::Factory enrollment_state_fetcher_factory);

  // Returns safeguard timer. Used for testing
  base::OneShotTimer& SafeguardTimerForTesting() { return safeguard_timer_; }

 protected:
  // Complete constructor which can be used to inject testing modules.
  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);

 private:
  void OnDevDisableBootDetermined(bool dev_disable_boot);

  // Determines the FRE and Initial Enrollment requirement and starts initial
  // enrollment if necessary. If Initial Enrollment would be skipped and the
  // system clock has not been synchronized yet, triggers waiting for system
  // clock sync and will be called again when the system clock state is known.
  void StartWithSystemClockSyncState();

  // Callback for the ownership status check.
  void OnOwnershipStatusCheckDone(
      ash::DeviceSettingsService::OwnershipStatus status);

  // Starts the auto-enrollment client for forced re-enrollment.
  void StartClientForFRE(const std::vector<std::string>& state_keys);

  // Called when the system clock has been synchronized or a timeout has been
  // reached while waiting for the system clock sync.
  void OnSystemClockSyncResult(bool system_clock_synchronized);

  // Starts the auto-enrollment client for initial enrollment.
  void StartClientForInitialEnrollment();

  // Sets `state_` and notifies `progress_callbacks_`.
  void UpdateState(AutoEnrollmentState state);

  // Clears everything that needs to be cleared at OOBE if
  // the device gets the response that forced re-enrollment is not required.
  // This currently removes firmware management parameters and sets
  // block_devmode=0 and check_enrollment=0 in RW_VPD by making asynchronous
  // calls to the respective D-Bus services.
  // The notifications have to be sent only after the FWMP and VPD is cleared,
  // because the user might try to switch to devmode. In this case, if
  // block_devmode is in FWMP and the clear operation didn't finish, the switch
  // would be denied. Also the safeguard timer has to be active until the FWMP
  // is cleared to avoid the risk of blocked flow.
  void StartCleanupForcedReEnrollment();

  // Makes a D-Bus call to cryptohome to remove the firmware management
  // parameters (FWMP) from TPM. Stops the `safeguard_timer_` and notifies the
  // `progress_callbacks_` in case cryptohome does not become available and the
  // timer is still running.
  // `service_is_ready` indicates if the cryptohome D-Bus service is ready.
  void StartRemoveFirmwareManagementParameters(bool service_is_ready);

  // Callback for RemoveFirmwareManagementParameters(). If an error is received
  // here, it is logged only, without changing the flow after that, because
  // the FWMP is used only for newer devices.
  // This also starts the VPD clearing process.
  void OnFirmwareManagementParametersRemoved(
      std::optional<device_management::RemoveFirmwareManagementParametersReply>
          reply);

  // Makes a D-Bus call to session_manager to set block_devmode=0 and
  // check_enrollment=0 in RW_VPD. Stops the `safeguard_timer_` and notifies the
  // `progress_callbacks_` in case session manager does not become available
  // and the timer is still running.
  // `service_is_ready` indicates if the session manager D-Bus service is ready.
  void StartClearForcedReEnrollmentVpd(bool service_is_ready);

  // Callback for ClearForcedReEnrollmentVpd(). If an error is received
  // here, it is logged only, without changing the flow after that.
  // This also notifies the `progress_callbacks_` since the forced re-enrollment
  // cleanup is finished at this point.
  void OnForcedReEnrollmentVpdCleared(bool reply);

  // Handles timeout of the safeguard timer and stops waiting for a result.
  void Timeout();

  bool IsInProgress() const;

  // Used for checking ownership.
  raw_ptr<ash::DeviceSettingsService> device_settings_service_;

  // Used for communication with management service.
  raw_ptr<DeviceManagementService> device_management_service_;

  // Used for retrieving device state keys.
  raw_ptr<ServerBackedStateKeysBroker> state_keys_broker_;

  // Used for checking dev boot status.
  std::unique_ptr<EnrollmentFwmpHelper> enrollment_fwmp_helper_;

  std::optional<AutoEnrollmentState> state_;
  ProgressCallbackList progress_callbacks_;

  std::unique_ptr<AutoEnrollmentClient> client_;

  // This will be used to create the `client_`. It can be set using
  // `SetAutoEnrollmentClientFactoryForTesting`.
  std::unique_ptr<AutoEnrollmentClient::Factory>
      auto_enrollment_client_factory_;

  // Constructs the PSM RLWE client. It will either create a fake or real
  // implementation of the client.
  // It is only used for PSM during creating the client for initial enrollment.
  RlweClientFactory psm_rlwe_client_factory_;

  // This timer acts as a belt-and-suspenders safety for the case where one of
  // the asynchronous steps required to make the auto-enrollment decision
  // doesn't come back. Even though in theory they should all terminate, better
  // safe than sorry: There are DBus interactions, an entire network stack etc.
  // - just too many moving pieces to be confident there are no bugs. If
  // something goes wrong, the timer will ensure that a decision gets made
  // eventually, which is crucial to not block OOBE forever. See
  // http://crbug.com/433634 for background.
  // The timer is expected to run during the state determination. The controller
  // is considered idle and can be restarted when the timer is not running.
  base::OneShotTimer safeguard_timer_;

  // Enrollment state fetcher. Invokes `UpdateState` on success or failure.
  std::unique_ptr<EnrollmentStateFetcher> enrollment_state_fetcher_;

  // Factory to create the `enrollment_state_fetcher_`. By default, it is set to
  // `EnrollmentStateFetcher::Create`, but can be overridden with
  // `SetEnrollmentStateFetcherFactoryForTesting`.
  EnrollmentStateFetcher::Factory enrollment_state_fetcher_factory_;

  bool dev_disable_boot_ = false;

  // Which type of auto-enrollment check is being performed by this
  // `AutoEnrollmentClient`.
  AutoEnrollmentTypeChecker::CheckType auto_enrollment_check_type_ =
      AutoEnrollmentTypeChecker::CheckType::kNone;
  bool auto_enrollment_check_type_init_started_ = false;

  // Shared factory for outgoing network requests.
  scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;

  // Utility for waiting until the system clock has been synchronized.
  std::unique_ptr<ash::SystemClockSyncObservation>
      system_clock_sync_observation_;

  raw_ptr<ash::NetworkStateHandler> network_state_handler_;
  // Observes network state and calls `PortalStateChanged` when it changes from
  // the start until the auto-enrollment state is resolved. Triggers a retry
  // when the device goes online.
  ash::NetworkStateHandlerScopedObservation network_state_observation_{this};

  // Current system clock sync state. This is only modified in
  // `OnSystemClockSyncResult` after `system_clock_sync_wait_requested_` has
  // been set to true.
  SystemClockSyncState system_clock_sync_state_ =
      SystemClockSyncState::kCanWaitForSync;

  // Keeps track of number of tries to request state keys.
  int request_state_keys_tries_ = 0;

  // TODO(igorcov): Merge the two weak_ptr factories in one.
  base::WeakPtrFactory<AutoEnrollmentController> client_start_weak_factory_{
      this};
  base::WeakPtrFactory<AutoEnrollmentController> weak_ptr_factory_{this};
};

}  // namespace policy

#endif  // CHROME_BROWSER_ASH_POLICY_ENROLLMENT_AUTO_ENROLLMENT_CONTROLLER_H_