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