// Copyright 2012 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_SETTINGS_DEVICE_SETTINGS_SERVICE_H_
#define CHROME_BROWSER_ASH_SETTINGS_DEVICE_SETTINGS_SERVICE_H_

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

#include "base/containers/circular_deque.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/observer_list.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "components/ownership/owner_settings_service.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/cloud_policy_validator.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/policy/proto/device_management_backend.pb.h"

class PrefService;

namespace ownership {
class OwnerKeyUtil;
class PublicKey;
}  // namespace ownership

namespace policy {
namespace off_hours {
class DeviceOffHoursController;
}  // namespace off_hours
}  // namespace policy

namespace ash {

class SessionManagerOperation;

// Deals with the low-level interface to Chrome OS device settings. Device
// settings are stored in a protobuf that's protected by a cryptographic
// signature generated by a key in the device owner's possession. Key and
// settings are brokered by the session_manager daemon.
//
// The purpose of DeviceSettingsService is to keep track of the current key and
// settings blob. For reading and writing device settings, use CrosSettings
// instead, which provides a high-level interface that allows for manipulation
// of individual settings.
//
// DeviceSettingsService generates notifications for key and policy update
// events so interested parties can reload state as appropriate.
class DeviceSettingsService : public SessionManagerClient::Observer {
 public:
  // Indicates ownership status of the device (listed in upgrade order).
  enum class OwnershipStatus {
    // These values are persisted to logs. Entries should not be renumbered and
    // numeric values should never be reused.
    kOwnershipUnknown = 0,
    // Not yet owned.
    kOwnershipNone = 1,
    // Either consumer ownership or cloud management.
    kOwnershipTaken = 2,
    kMaxValue = kOwnershipTaken
  };

  using OwnershipStatusCallback = base::OnceCallback<void(OwnershipStatus)>;
  using PolicyDataCallback =
      base::OnceCallback<void(enterprise_management::PolicyData*)>;

  // Status codes for Load() and Store().
  // These values are logged to UMA. Entries should not be renumbered and
  // numeric values should never be reused. Please keep in sync with
  // "DeviceSettingsStatus2" in
  // tools/metrics/histograms/metadata/enterprise/enums.xml.
  enum Status {
    STORE_SUCCESS,
    STORE_KEY_UNAVAILABLE,   // Owner key not yet configured.
    STORE_OPERATION_FAILED,  // IPC to session_manager daemon failed.
    STORE_NO_POLICY,         // No settings blob present.
    STORE_INVALID_POLICY,    // Invalid settings blob (proto parse failed).
    STORE_VALIDATION_ERROR,  // Policy validation failure.
    STORE_KEY_UNAVAILABLE_NOT_INITIALIZED,  // Early in boot process.
    STORE_KEY_UNAVAILABLE_NOT_LOCKED,       // Owner key not present, ownership
                                            // not taken. Not an error.
    STORE_KEY_UNAVAILABLE_MANAGED,  // Owner key not present for managed device.
    kMaxValue = STORE_KEY_UNAVAILABLE_MANAGED,
  };

  // Observer interface.
  class Observer {
   public:
    virtual ~Observer();

    // Indicates device ownership status changes.  This is triggered upon every
    // browser start since the transition from uninitialized (OWNERSHIP_UNKNOWN)
    // to initialized (either of OWNERSHIP_{NONE,TAKEN}) also counts as an
    // ownership change.
    virtual void OwnershipStatusChanged();

    // Gets called after updates to the device settings.
    virtual void DeviceSettingsUpdated();

    virtual void OnDeviceSettingsServiceShutdown();
  };

  // Manage singleton instance.
  static void Initialize();
  static bool IsInitialized();
  static void Shutdown();
  static DeviceSettingsService* Get();

  // Returns a human-readable string describing |status|.
  static const char* StatusToString(Status status);

  // Creates a device settings service instance. This is meant for unit tests,
  // production code uses the singleton returned by Get() above.
  DeviceSettingsService();

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

  ~DeviceSettingsService() override;

  // To be called on startup once threads are initialized and D-Bus is ready.
  // `local_state` must be valid until `StopProcessing()`. `local_state` may be
  // null only in tests.
  void StartProcessing(PrefService* local_state,
                       SessionManagerClient* session_manager_client,
                       scoped_refptr<ownership::OwnerKeyUtil> owner_key_util);

  // Prevents the service from making further calls to session_manager_client
  // and stops any pending operations.
  void StopProcessing();

  // Must only be used with a |device_mode| that has been read and verified by
  // the InstallAttributes class.
  void SetDeviceMode(policy::DeviceMode device_mode);

  const enterprise_management::PolicyData* policy_data() const {
    return policy_data_.get();
  }

  const enterprise_management::PolicyFetchResponse* policy_fetch_response()
      const {
    return policy_fetch_response_.get();
  }

  // Returns the currently active device settings. Returns nullptr if the device
  // settings have not been retrieved from session_manager yet.
  const enterprise_management::ChromeDeviceSettingsProto* device_settings()
      const {
    return device_settings_.get();
  }

  // Returns the currently used owner key.
  scoped_refptr<ownership::PublicKey> GetPublicKey();

  // Returns the status generated by the *last operation*.
  // WARNING: It is not correct to take this method as an indication of whether
  // DeviceSettingsService contains valid device settings. In order to answer
  // that question, simply check whether device_settings() is different from
  // nullptr.
  Status status() const { return store_status_; }

  // Returns the currently device off hours controller. The returned pointer is
  // guaranteed to be non-null.
  policy::off_hours::DeviceOffHoursController* device_off_hours_controller()
      const {
    return device_off_hours_controller_.get();
  }

  void SetDeviceOffHoursControllerForTesting(
      std::unique_ptr<policy::off_hours::DeviceOffHoursController> controller);

  // Triggers an attempt to pull the public half of the owner key from disk and
  // load the device settings.
  void Load();

  // Attempts to load asynchronously the settings if they haven't been loaded
  // already and no request is in the queue. The aim is to avoid additional
  // request when not needed. Should NOT be used when the settings need to be
  // renewed, like in invalidations flow or when an explicit request from user
  // to reload is received.
  void LoadIfNotPresent();

  // Synchronously pulls the public key and loads the device settings.
  void LoadImmediately();

  // Stores a policy blob to session_manager. The result of the operation is
  // reported through |callback|. If successful, the updated device settings are
  // present in policy_data() and device_settings() when the callback runs.
  void Store(std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
             base::OnceClosure callback);

  // Returns the ownership status. May return OWNERSHIP_UNKNOWN if the disk
  // hasn't been checked yet.
  OwnershipStatus GetOwnershipStatus();

  // Determines the ownership status and reports the result to `callback`.
  // This obtains the ownership status of the device by reading the public owner
  // key.
  // As a side effect, it also obtains device settings from the session manager,
  // see SessionManagerOperation::StartLoading().
  //
  // May pass OWNERSHIP_UNKNOWN to `callback` if the public owner key file is
  // invalid (empty), see also ash::SessionManagerOperation::LoadPublicKey().
  virtual void GetOwnershipStatusAsync(OwnershipStatusCallback callback);

  // Checks whether we have the private owner key.
  //
  // DEPRECATED (ygorshenin@, crbug.com/41143265): this method should
  // not be used since private key is a profile-specific resource and
  // should be checked and used in a profile-aware manner, through
  // OwnerSettingsService.
  bool HasPrivateOwnerKey();

  // Sets the identity of the user that's interacting with the service. This is
  // relevant only for writing settings through SignAndStore().
  //
  // TODO (ygorshenin@, crbug.com/41143265): get rid of the method when
  // write path for device settings will be removed from
  // DeviceSettingsProvider and all existing clients will be switched
  // to OwnerSettingsServiceAsh.
  void InitOwner(const std::string& username,
                 const base::WeakPtr<ownership::OwnerSettingsService>&
                     owner_settings_service);

  const std::string& GetUsername() const;

  ownership::OwnerSettingsService* GetOwnerSettingsService() const;

  // Mark that the device will establish consumer ownership. If the flag is set
  // and ownership is not taken, policy reload will be deferred until InitOwner
  // is called. So that the ownership status is flipped after the private part
  // of owner is fully loaded.
  void MarkWillEstablishConsumerOwnership();

  // Returns whether the current user should take ownership of the device
  // (effectively whether the user is the first consumer user on the device).
  bool GetWillEstablishConsumerOwnership() const {
    return will_establish_consumer_ownership_;
  }

  // Returns if the device is managed according to the device settings.
  bool IsDeviceManaged() const;

  // Returns if the device policy is loaded and contains the DM token.
  bool HasDmToken() const;

  // Adds an observer.
  void AddObserver(Observer* observer);
  // Removes an observer.
  void RemoveObserver(Observer* observer);

  // SessionManagerClient::Observer:
  void OwnerKeySet(bool success) override;
  void PropertyChangeComplete(bool success) override;
  void SessionStopping() override;

 private:
  friend class OwnerSettingsServiceAsh;

  // Enqueues a new operation. Takes ownership of |operation| and starts it
  // right away if there is no active operation currently.
  void Enqueue(std::unique_ptr<SessionManagerOperation> operation);

  // Enqueues a load operation.
  void EnqueueLoad(bool request_key_load);

  // Makes sure there's a reload operation so changes to the settings (and key,
  // in case |request_key_load| is set) are getting picked up.
  void EnsureReload(bool request_key_load);

  // Runs the next pending operation.
  void StartNextOperation();

  // Updates status, policy data and owner key from a finished operation.
  void HandleCompletedOperation(base::OnceClosure callback,
                                SessionManagerOperation* operation,
                                Status status);

  // Same as HandleCompletedOperation(), but also starts the next pending
  // operation if available.
  void HandleCompletedAsyncOperation(base::OnceClosure callback,
                                     SessionManagerOperation* operation,
                                     Status status);

  // Helper method for GetOwnershipStatusAsync to avoid data race upon
  // user sign-in.
  void ValidateOwnershipStatusAndNotify(OwnershipStatusCallback callback);

  // Run OwnershipStatusChanged() for observers.
  void NotifyOwnershipStatusChanged() const;

  // Run DeviceSettingsUpdated() for observers.
  void NotifyDeviceSettingsUpdated() const;

  // Processes pending callbacks from GetOwnershipStatusAsync().
  void RunPendingOwnershipStatusCallbacks();

  raw_ptr<PrefService> local_state_ = nullptr;

  raw_ptr<SessionManagerClient> session_manager_client_ = nullptr;
  scoped_refptr<ownership::OwnerKeyUtil> owner_key_util_;

  Status store_status_ = STORE_SUCCESS;

  std::vector<OwnershipStatusCallback> pending_ownership_status_callbacks_;

  std::string username_;
  scoped_refptr<ownership::PublicKey> public_key_;
  base::WeakPtr<ownership::OwnerSettingsService> owner_settings_service_;
  // Ownership status before the current session manager operation.
  OwnershipStatus previous_ownership_status_ =
      OwnershipStatus::kOwnershipUnknown;

  std::unique_ptr<enterprise_management::PolicyFetchResponse>
      policy_fetch_response_;
  std::unique_ptr<enterprise_management::PolicyData> policy_data_;
  std::unique_ptr<enterprise_management::ChromeDeviceSettingsProto>
      device_settings_;

  policy::DeviceMode device_mode_ = policy::DEVICE_MODE_PENDING;

  // The queue of pending operations. The first operation on the queue is
  // currently active; it gets removed and destroyed once it completes.
  base::circular_deque<std::unique_ptr<SessionManagerOperation>>
      pending_operations_;

  base::ObserverList<Observer>::Unchecked observers_;

  // Whether the device will be establishing consumer ownership.
  bool will_establish_consumer_ownership_ = false;

  // Whether we received the signal that the session is stopping.
  bool session_stopping_ = false;

  std::unique_ptr<policy::off_hours::DeviceOffHoursController>
      device_off_hours_controller_;

  base::WeakPtrFactory<DeviceSettingsService> weak_factory_{this};
};

std::ostream& operator<<(std::ostream&, DeviceSettingsService::OwnershipStatus);

}  // namespace ash

#endif  // CHROME_BROWSER_ASH_SETTINGS_DEVICE_SETTINGS_SERVICE_H_
