chromium/chrome/browser/ash/crosapi/cert_provisioning_ash.cc

// Copyright 2022 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/crosapi/cert_provisioning_ash.h"

#include "chrome/browser/ash/cert_provisioning/cert_provisioning_common.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_scheduler.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_scheduler_user_service.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_worker.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"

using ash::cert_provisioning::CertProvisioningScheduler;
using ash::cert_provisioning::CertProvisioningWorkerState;

namespace crosapi {

namespace {

constexpr mojom::CertProvisioningProcessState AshToMojoState(
    CertProvisioningWorkerState state) {
  switch (state) {
    case CertProvisioningWorkerState::kInitState:
      return mojom::CertProvisioningProcessState::kInitState;
    case CertProvisioningWorkerState::kKeypairGenerated:
      return mojom::CertProvisioningProcessState::kKeypairGenerated;
    case CertProvisioningWorkerState::kStartCsrResponseReceived:
      return mojom::CertProvisioningProcessState::kStartCsrResponseReceived;
    case CertProvisioningWorkerState::kVaChallengeFinished:
      return mojom::CertProvisioningProcessState::kVaChallengeFinished;
    case CertProvisioningWorkerState::kKeyRegistered:
      return mojom::CertProvisioningProcessState::kKeyRegistered;
    case CertProvisioningWorkerState::kKeypairMarked:
      return mojom::CertProvisioningProcessState::kKeypairMarked;
    case CertProvisioningWorkerState::kSignCsrFinished:
      return mojom::CertProvisioningProcessState::kSignCsrFinished;
    case CertProvisioningWorkerState::kFinishCsrResponseReceived:
      return mojom::CertProvisioningProcessState::kFinishCsrResponseReceived;
    case CertProvisioningWorkerState::kSucceeded:
      return mojom::CertProvisioningProcessState::kSucceeded;
    case CertProvisioningWorkerState::kInconsistentDataError:
      return mojom::CertProvisioningProcessState::kInconsistentDataError;
    case CertProvisioningWorkerState::kFailed:
      return mojom::CertProvisioningProcessState::kFailed;
    case CertProvisioningWorkerState::kCanceled:
      return mojom::CertProvisioningProcessState::kCanceled;
    case CertProvisioningWorkerState::kReadyForNextOperation:
      return mojom::CertProvisioningProcessState::kReadyForNextOperation;
    case CertProvisioningWorkerState::kAuthorizeInstructionReceived:
      return mojom::CertProvisioningProcessState::kAuthorizeInstructionReceived;
    case CertProvisioningWorkerState::kProofOfPossessionInstructionReceived:
      return mojom::CertProvisioningProcessState::
          kProofOfPossessionInstructionReceived;
    case CertProvisioningWorkerState::kImportCertificateInstructionReceived:
      return mojom::CertProvisioningProcessState::
          kImportCertificateInstructionReceived;
  }
}

}  // namespace

CertProvisioningAsh::CertProvisioningAsh() {
  // Unretained(this) is safe because `observers_` is owned by `this` and will
  // never outlive it.
  observers_.set_disconnect_handler(base::BindRepeating(
      &CertProvisioningAsh::OnObserverDisconnected, base::Unretained(this)));
}

CertProvisioningAsh::~CertProvisioningAsh() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}

void CertProvisioningAsh::BindReceiver(
    mojo::PendingReceiver<mojom::CertProvisioning> pending_receiver) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  receivers_.Add(this, std::move(pending_receiver));
}

void CertProvisioningAsh::AddObserver(
    mojo::PendingRemote<mojom::CertProvisioningObserver> observer) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (observers_.empty()) {
    ObserveSchedulers();
  }
  observers_.Add(
      mojo::Remote<mojom::CertProvisioningObserver>(std::move(observer)));
}

void CertProvisioningAsh::OnObserverDisconnected(mojo::RemoteSetElementId) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (observers_.empty()) {
    StopObservingSchedulers();
  }
}

void CertProvisioningAsh::ObserveSchedulers() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // Unretained(this) is safe because the subscriptions never outlive `this`.
  if (CertProvisioningScheduler* user_scheduler = GetUserScheduler()) {
    user_subscription_ = user_scheduler->AddObserver(base::BindRepeating(
        &CertProvisioningAsh::OnSchedulersChanged, base::Unretained(this)));
  }
  if (CertProvisioningScheduler* device_scheduler = GetDeviceScheduler()) {
    device_subscription_ = device_scheduler->AddObserver(base::BindRepeating(
        &CertProvisioningAsh::OnSchedulersChanged, base::Unretained(this)));
  }
}

void CertProvisioningAsh::StopObservingSchedulers() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // Destroy the subscriptions so the schedulers stop sending the notifications.
  user_subscription_ = {};
  device_subscription_ = {};
}

void CertProvisioningAsh::OnSchedulersChanged() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  for (const auto& observer : observers_) {
    observer->OnStateChanged();
  }
}

void CertProvisioningAsh::GetStatus(GetStatusCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  std::vector<mojom::CertProvisioningProcessStatusPtr> result;

  AppendWorkerStatuses(GetUserScheduler(), /*is_device_wide=*/false, result);
  AppendWorkerStatuses(GetDeviceScheduler(), /*is_device_wide=*/true, result);

  std::move(callback).Run(std::move(result));
}

void CertProvisioningAsh::AppendWorkerStatuses(
    CertProvisioningScheduler* scheduler,
    bool is_device_wide,
    std::vector<mojom::CertProvisioningProcessStatusPtr>& result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!scheduler) {
    return;
  }

  const auto& worker_map = scheduler->GetWorkers();
  const auto& failed_workers_map = scheduler->GetFailedCertProfileIds();

  result.reserve(result.size() + worker_map.size() + failed_workers_map.size());

  for (const auto& [id, worker] : worker_map) {
    result.push_back(mojom::CertProvisioningProcessStatus::New());
    mojom::CertProvisioningProcessStatusPtr& status = result.back();

    status->cert_profile_id = id;
    status->cert_profile_name = worker->GetCertProfile().name;
    status->public_key = worker->GetPublicKey();
    status->last_update_time = worker->GetLastUpdateTime();
    status->state = AshToMojoState(worker->GetState());
    status->did_fail = false;
    status->is_device_wide = is_device_wide;

    const auto& backend_error = worker->GetLastBackendServerError();
    if (backend_error.has_value()) {
      status->last_backend_server_error =
          crosapi::mojom::CertProvisioningBackendServerError::New(
              backend_error->time, backend_error->status);
    }
  }

  for (const auto& [id, worker] : failed_workers_map) {
    result.push_back(mojom::CertProvisioningProcessStatus::New());
    mojom::CertProvisioningProcessStatusPtr& status = result.back();

    status->cert_profile_id = id;
    status->cert_profile_name = worker.cert_profile_name;
    status->public_key = worker.public_key;
    status->last_update_time = worker.last_update_time;
    status->state = AshToMojoState(worker.state_before_failure);
    status->did_fail = true;
    status->is_device_wide = is_device_wide;
    status->failure_message = worker.failure_message;
  }
}

void CertProvisioningAsh::UpdateOneProcess(const std::string& cert_profile_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  CertProvisioningScheduler* user_scheduler = GetUserScheduler();
  if (user_scheduler && user_scheduler->UpdateOneWorker(cert_profile_id)) {
    return;
  }

  CertProvisioningScheduler* device_scheduler = GetDeviceScheduler();
  if (device_scheduler) {
    device_scheduler->UpdateOneWorker(cert_profile_id);
  }
}

void CertProvisioningAsh::ResetOneProcess(const std::string& cert_profile_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  CertProvisioningScheduler* user_scheduler = GetUserScheduler();
  if (user_scheduler && user_scheduler->ResetOneWorker(cert_profile_id)) {
    return;
  }

  CertProvisioningScheduler* device_scheduler = GetDeviceScheduler();
  if (device_scheduler && device_scheduler->ResetOneWorker(cert_profile_id)) {
    return;
  }

  if (user_scheduler || device_scheduler) {
    LOG(ERROR) << "resetting cert_profile_id was not found. id:"
               << cert_profile_id << " user_scheduler:" << bool(user_scheduler)
               << " device_scheduler:" << bool(device_scheduler);
    return;
  }
}

void CertProvisioningAsh::InjectForTesting(
    ash::cert_provisioning::CertProvisioningScheduler* user_scheduler,
    ash::cert_provisioning::CertProvisioningScheduler* device_scheduler) {
  user_scheduler_for_testing_ = user_scheduler;
  device_scheduler_for_testing_ = device_scheduler;
}

CertProvisioningScheduler* CertProvisioningAsh::GetUserScheduler() {
  if (user_scheduler_for_testing_.has_value()) {
    return user_scheduler_for_testing_.value();
  }

  Profile* user_profile = ProfileManager::GetPrimaryUserProfile();
  if (!user_profile) {
    return nullptr;
  }

  ash::cert_provisioning::CertProvisioningSchedulerUserService* user_service =
      ash::cert_provisioning::CertProvisioningSchedulerUserServiceFactory::
          GetForProfile(user_profile);
  if (!user_service) {
    return nullptr;
  }

  return user_service->scheduler();
}

CertProvisioningScheduler* CertProvisioningAsh::GetDeviceScheduler() {
  if (device_scheduler_for_testing_.has_value()) {
    return device_scheduler_for_testing_.value();
  }

  Profile* user_profile = ProfileManager::GetPrimaryUserProfile();
  if (!user_profile) {
    return nullptr;
  }

  const user_manager::User* user =
      ash::ProfileHelper::Get()->GetUserByProfile(user_profile);
  if (!user || !user->IsAffiliated()) {
    return nullptr;
  }

  policy::BrowserPolicyConnectorAsh* connector =
      g_browser_process->platform_part()->browser_policy_connector_ash();
  return connector->GetDeviceCertProvisioningScheduler();
}

}  // namespace crosapi