chromium/chrome/browser/ash/settings/owner_pending_setting_controller.cc

// Copyright 2021 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/settings/owner_pending_setting_controller.h"

#include <string>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "components/ownership/owner_settings_service.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"

namespace ash {

OwnerPendingSettingController::OwnerPendingSettingController(
    const std::string& pref_name,
    const std::string& pending_pref_name,
    PrefService* local_state)
    : local_state_(local_state),
      pref_name_(pref_name),
      pending_pref_name_(pending_pref_name) {
  value_notified_to_observers_ = GetValue();
}

void OwnerPendingSettingController::Set(Profile* profile,
                                        const base::Value& value) {
  DCHECK(profile);
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (GetOwnershipStatus() ==
      DeviceSettingsService::OwnershipStatus::kOwnershipTaken) {
    // The device has an owner. If the current profile is that owner, we will
    // write the value on their behalf, otherwise no action is taken.
    VLOG(1) << "Already has owner";
    SetWithServiceAsync(GetOwnerSettingsService(profile), value);
  } else {
    // The device has no owner, or we do not know yet whether the device has an
    // owner. We write a pending value that will be persisted when ownership is
    // taken (if that has not already happened).
    // We store the new value in the local state, so that even if Chrome is
    // restarted before ownership is taken, we will still persist it eventually.
    // See OnOwnershipTaken.
    VLOG(1) << "Pending owner; setting pending pref name: "
            << pending_pref_name_;
    local_state_->Set(pending_pref_name_, value);
    NotifyObservers();
  }
}

std::optional<base::Value> OwnerPendingSettingController::GetValue() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  std::optional<base::Value> value = GetPendingValue();
  if (ShouldReadFromPendingValue() && value.has_value()) {
    // Return the pending value if it exists.
    return value;
  }
  // Otherwise, always return the value from the signed store.
  return GetSignedStoredValue();
}

base::CallbackListSubscription OwnerPendingSettingController::AddObserver(
    const base::RepeatingClosure& callback) {
  DCHECK(!callback.is_null());
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return callback_list_.Add(callback);
}

void OwnerPendingSettingController::OnOwnershipTaken(
    ownership::OwnerSettingsService* service) {
  DCHECK_EQ(GetOwnershipStatus(),
            DeviceSettingsService::OwnershipStatus::kOwnershipTaken);
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  VLOG(1) << "OnOwnershipTaken";

  std::optional<base::Value> pending_value = GetPendingValue();
  if (pending_value.has_value()) {
    // At the time ownership is taken, there is a value waiting to be written.
    // Use the OwnerSettingsService of the new owner to write the setting.
    SetWithServiceAsync(service, pending_value.value());
  }
}

OwnerPendingSettingController::~OwnerPendingSettingController() {
  owner_settings_service_observation_.Reset();
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void OwnerPendingSettingController::OnSignedPolicyStored(bool success) {
  if (!success)
    return;
  std::optional<base::Value> pending_value = GetPendingValue();
  std::optional<base::Value> signed_value = GetSignedStoredValue();
  if (pending_value.has_value() && signed_value.has_value() &&
      pending_value == signed_value) {
    is_value_being_set_with_service_ = false;
    owner_settings_service_observation_.Reset();
    ClearPendingValue();
    NotifyObservers();
    if (on_device_settings_stored_callback_)
      std::move(on_device_settings_stored_callback_).Run();
  }
}

void OwnerPendingSettingController::SetOnDeviceSettingsStoredCallBack(
    base::OnceClosure callback) {
  CHECK(!on_device_settings_stored_callback_);
  on_device_settings_stored_callback_ = std::move(callback);
}

void OwnerPendingSettingController::SetWithServiceAsync(
    ownership::OwnerSettingsService* service,  // Can be null for non-owners.
    const base::Value& value) {
  bool not_yet_ready = service && !service->IsReady();
  if (not_yet_ready) {
    VLOG(1) << "Service not yet ready. Adding listener.";
    // Service is not yet ready. Listen for changes in its readiness so we can
    // write the value once it is ready. Uses weak pointers, so if everything
    // is shutdown and deleted in the meantime, this callback isn't run.
    service->IsOwnerAsync(base::BindOnce(
        &OwnerPendingSettingController::SetWithServiceCallback,
        this->as_weak_ptr(), service->as_weak_ptr(), value.Clone()));
  } else {
    // Service is either null, or ready - use it right now.
    SetWithService(service, value);
  }
}

void OwnerPendingSettingController::SetWithServiceCallback(
    const base::WeakPtr<ownership::OwnerSettingsService>& service,
    const base::Value value,
    bool is_owner) {
  if (service)  // Make sure service wasn't deleted in the meantime.
    SetWithService(service.get(), value);
}

void OwnerPendingSettingController::SetWithService(
    ownership::OwnerSettingsService* service,  // Can be null for non-owners.
    const base::Value& value) {
  if (service && service->IsOwner()) {
    if (!owner_settings_service_observation_.IsObserving())
      owner_settings_service_observation_.Observe(service);

    // `is_value_being_set_with_service_` must be set to `true` and
    // `pending_pref_name_` must be set with `value` before setting `pref_name_`
    // with `OwnerSettingService` to guarantee that future calls to `GetValue()`
    // returns the pending value instead of the signed value until
    // `OnSignedPolicyStored(true)` is called.
    // Since calls to `GetValue()` can happen inside `service->Set()`, the order
    // of the following lines is important.
    is_value_being_set_with_service_ = true;
    local_state_->Set(pending_pref_name_, value);

    service->Set(pref_name_, value);
  } else {
    // Do nothing since we are not the owner.
    LOG(WARNING) << "Changing settings from non-owner, setting=" << pref_name_;
  }
}

void OwnerPendingSettingController::NotifyObservers() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  std::optional<base::Value> current_value = GetValue();
  if (current_value != value_notified_to_observers_) {
    VLOG(1) << "Notifying observers";
    value_notified_to_observers_ = std::move(current_value);
    callback_list_.Notify();
  } else {
    VLOG(1) << "Not notifying (already notified)";
  }
}

DeviceSettingsService::OwnershipStatus
OwnerPendingSettingController::GetOwnershipStatus() const {
  return DeviceSettingsService::Get()->GetOwnershipStatus();
}

ownership::OwnerSettingsService*
OwnerPendingSettingController::GetOwnerSettingsService(Profile* profile) {
  return OwnerSettingsServiceAshFactory::GetForBrowserContext(profile);
}

std::optional<base::Value> OwnerPendingSettingController::GetPendingValue()
    const {
  if (local_state_->HasPrefPath(pending_pref_name_)) {
    return local_state_->GetValue(pending_pref_name_).Clone();
  }
  return std::nullopt;
}

void OwnerPendingSettingController::ClearPendingValue() {
  VLOG(1) << "ClearPendingValue";
  local_state_->ClearPref(pending_pref_name_);
}

std::optional<base::Value> OwnerPendingSettingController::GetSignedStoredValue()
    const {
  const base::Value* value = CrosSettings::Get()->GetPref(pref_name_);
  if (value) {
    return value->Clone();
  }
  return std::nullopt;
}

bool OwnerPendingSettingController::ShouldReadFromPendingValue() const {
  // Read from pending value before ownership is taken or ownership is
  // unknown. There's a brief moment when ownership is unknown for every
  // Chrome starts. In that case, we will read from pending value if it exists
  // (which means ownership is not taken), and read from service when pending
  // value pending is cleared (which means ownership is taken).
  if (GetOwnershipStatus() ==
          DeviceSettingsService::OwnershipStatus::kOwnershipNone ||
      GetOwnershipStatus() ==
          DeviceSettingsService::OwnershipStatus::kOwnershipUnknown) {
    return true;
  }
  // Read from pending value if ownership is taken but pending value has not
  // been set successfully with service.
  return is_value_being_set_with_service_;
}

}  // namespace ash