chromium/chromeos/ash/components/dbus/device_management/fake_install_attributes_client.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chromeos/ash/components/dbus/device_management/fake_install_attributes_client.h"

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "chromeos/dbus/constants/dbus_paths.h"
#include "components/policy/proto/install_attributes.pb.h"

namespace ash {

namespace {

// Buffer size for reading install attributes file. 16k should be plenty. The
// file contains six attributes only (see InstallAttributes::LockDevice).
constexpr size_t kInstallAttributesFileMaxSize = 16384;

// Used to track the fake instance, mirrors the instance in the base class.
FakeInstallAttributesClient* g_instance = nullptr;

}  // namespace

FakeInstallAttributesClient::FakeInstallAttributesClient() {
  DCHECK(!g_instance);
  g_instance = this;

  base::FilePath cache_path;
  locked_ = base::PathService::Get(
                chromeos::dbus_paths::FILE_INSTALL_ATTRIBUTES, &cache_path) &&
            base::PathExists(cache_path);
  if (locked_) {
    LoadInstallAttributes();
  }
}

FakeInstallAttributesClient::~FakeInstallAttributesClient() {
  DCHECK_EQ(this, g_instance);
  g_instance = nullptr;
}

// static
FakeInstallAttributesClient* FakeInstallAttributesClient::Get() {
  return g_instance;
}

void FakeInstallAttributesClient::InstallAttributesGet(
    const ::device_management::InstallAttributesGetRequest& request,
    InstallAttributesGetCallback callback) {
  NOTIMPLEMENTED();
}
void FakeInstallAttributesClient::InstallAttributesFinalize(
    const ::device_management::InstallAttributesFinalizeRequest& request,
    InstallAttributesFinalizeCallback callback) {
  NOTIMPLEMENTED();
}
void FakeInstallAttributesClient::InstallAttributesGetStatus(
    const ::device_management::InstallAttributesGetStatusRequest& request,
    InstallAttributesGetStatusCallback callback) {
  std::optional<::device_management::InstallAttributesGetStatusReply> reply =
      BlockingInstallAttributesGetStatus(request);
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), reply));
}
void FakeInstallAttributesClient::RemoveFirmwareManagementParameters(
    const ::device_management::RemoveFirmwareManagementParametersRequest& request,
    RemoveFirmwareManagementParametersCallback callback) {
  remove_firmware_management_parameters_from_tpm_call_count_++;
  fwmp_flags_ = std::nullopt;
  ReturnProtobufMethodCallback(
      ::device_management::RemoveFirmwareManagementParametersReply(),
      std::move(callback));
}
void FakeInstallAttributesClient::SetFirmwareManagementParameters(
    const ::device_management::SetFirmwareManagementParametersRequest& request,
    SetFirmwareManagementParametersCallback callback) {
  if (request.has_fwmp()) {
    fwmp_flags_ = request.fwmp().flags();
  }
  ReturnProtobufMethodCallback(
      ::device_management::SetFirmwareManagementParametersReply(),
      std::move(callback));
}
void FakeInstallAttributesClient::GetFirmwareManagementParameters(
    const ::device_management::GetFirmwareManagementParametersRequest& request,
    GetFirmwareManagementParametersCallback callback) {
  auto reply = ::device_management::GetFirmwareManagementParametersReply();
  if (fwmp_flags_) {
    reply.mutable_fwmp()->set_flags(*fwmp_flags_);
  } else {
    reply.set_error(
        ::device_management::
            DEVICE_MANAGEMENT_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID);
  }
  ReturnProtobufMethodCallback(reply, std::move(callback));
}
std::optional<::device_management::InstallAttributesGetReply>
FakeInstallAttributesClient::BlockingInstallAttributesGet(
    const ::device_management::InstallAttributesGetRequest& request) {
  ::device_management::InstallAttributesGetReply reply;
  if (install_attrs_.find(request.name()) != install_attrs_.end()) {
    reply.set_value(install_attrs_[request.name()]);
  } else {
    reply.set_error(::device_management::DeviceManagementErrorCode::
                        DEVICE_MANAGEMENT_ERROR_INSTALL_ATTRIBUTES_GET_FAILED);
  }
  return reply;
}
std::optional<::device_management::InstallAttributesSetReply>
FakeInstallAttributesClient::BlockingInstallAttributesSet(
    const ::device_management::InstallAttributesSetRequest& request) {
  ::device_management::InstallAttributesSetReply reply;
  install_attrs_[request.name()] = request.value();
  return reply;
}
std::optional<::device_management::InstallAttributesFinalizeReply>
FakeInstallAttributesClient::BlockingInstallAttributesFinalize(
    const ::device_management::InstallAttributesFinalizeRequest& request) {
  locked_ = true;
  ::device_management::InstallAttributesFinalizeReply reply;

  // Persist the install attributes so that they can be reloaded if the
  // browser is restarted. This is used for ease of development when device
  // enrollment is required.
  base::FilePath cache_path;
  if (!base::PathService::Get(chromeos::dbus_paths::FILE_INSTALL_ATTRIBUTES,
                              &cache_path)) {
    reply.set_error(::device_management::DeviceManagementErrorCode::
                        DEVICE_MANAGEMENT_ERROR_INSTALL_ATTRIBUTES_FINALIZE_FAILED);
    return reply;
  }

  cryptohome::SerializedInstallAttributes install_attrs_proto;
  for (const auto& it : install_attrs_) {
    const std::string& name = it.first;
    const std::string& value = it.second;
    cryptohome::SerializedInstallAttributes::Attribute* attr_entry =
        install_attrs_proto.add_attributes();
    attr_entry->set_name(name);
    attr_entry->set_value(value);
  }

  std::string result;
  install_attrs_proto.SerializeToString(&result);

  // The real implementation does a blocking wait on the dbus call; the fake
  // implementation must have this file written before returning.
  base::ScopedAllowBlockingForTesting allow_io;
  base::WriteFile(cache_path, result);

  return reply;
}
std::optional<::device_management::InstallAttributesGetStatusReply>
FakeInstallAttributesClient::BlockingInstallAttributesGetStatus(
    const ::device_management::InstallAttributesGetStatusRequest& request) {
  ::device_management::InstallAttributesGetStatusReply reply;
  if (locked_) {
    reply.set_state(::device_management::InstallAttributesState::VALID);
  } else {
    reply.set_state(::device_management::InstallAttributesState::FIRST_INSTALL);
  }
  return reply;
}

void FakeInstallAttributesClient::WaitForServiceToBeAvailable(
    chromeos::WaitForServiceToBeAvailableCallback callback) {
  if (service_is_available_ || service_reported_not_available_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), service_is_available_));
  } else {
    pending_wait_for_service_to_be_available_callbacks_.push_back(
        std::move(callback));
  }
}

void FakeInstallAttributesClient::SetServiceIsAvailable(bool is_available) {
  service_is_available_ = is_available;
  if (!is_available) {
    return;
  }

  std::vector<chromeos::WaitForServiceToBeAvailableCallback> callbacks;
  callbacks.swap(pending_wait_for_service_to_be_available_callbacks_);
  for (auto& callback : callbacks) {
    std::move(callback).Run(true);
  }
}

void FakeInstallAttributesClient::ReportServiceIsNotAvailable() {
  DCHECK(!service_is_available_);
  service_reported_not_available_ = true;

  std::vector<chromeos::WaitForServiceToBeAvailableCallback> callbacks;
  callbacks.swap(pending_wait_for_service_to_be_available_callbacks_);
  for (auto& callback : callbacks) {
    std::move(callback).Run(false);
  }
}

template <typename ReplyType>
void FakeInstallAttributesClient::ReturnProtobufMethodCallback(
    const ReplyType& reply,
    chromeos::DBusMethodCallback<ReplyType> callback) {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), reply));
}

bool FakeInstallAttributesClient::LoadInstallAttributes() {
  base::FilePath cache_file;
  const bool file_exists =
      base::PathService::Get(chromeos::dbus_paths::FILE_INSTALL_ATTRIBUTES,
                             &cache_file) &&
      base::PathExists(cache_file);
  DCHECK(file_exists);
  // Mostly copied from
  // chromeos/ash/components/install_attributes/install_attributes.cc.
  std::string file_blob;
  if (!base::ReadFileToStringWithMaxSize(cache_file, &file_blob,
                                         kInstallAttributesFileMaxSize)) {
    PLOG(ERROR) << "Failed to read " << cache_file.value();
    return false;
  }

  cryptohome::SerializedInstallAttributes install_attrs_proto;
  if (!install_attrs_proto.ParseFromString(file_blob)) {
    LOG(ERROR) << "Failed to parse install attributes cache.";
    return false;
  }

  for (const auto& entry : install_attrs_proto.attributes()) {
    install_attrs_[entry.name()].assign(
        entry.value().data(), entry.value().data() + entry.value().size());
  }

  return true;
}

}  // namespace ash