| // Copyright 2020 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/cert_provisioning/cert_provisioning_scheduler.h" |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <memory> |
| #include <optional> |
| #include <unordered_set> |
| |
| #include "base/check.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/flat_set.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/observer_list.h" |
| #include "base/observer_list_types.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ash/cert_provisioning/cert_provisioning_client.h" |
| #include "chrome/browser/ash/cert_provisioning/cert_provisioning_common.h" |
| #include "chrome/browser/ash/cert_provisioning/cert_provisioning_metrics.h" |
| #include "chrome/browser/ash/cert_provisioning/cert_provisioning_worker.h" |
| #include "chrome/browser/ash/platform_keys/platform_keys_service.h" |
| #include "chrome/browser/ash/platform_keys/platform_keys_service_factory.h" |
| #include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/ash/components/network/network_handler.h" |
| #include "chromeos/ash/components/network/network_state_handler.h" |
| #include "chromeos/ash/components/platform_keys/platform_keys.h" |
| #include "components/invalidation/invalidation_listener.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| namespace ash::cert_provisioning { |
| |
| namespace { |
| |
| const base::TimeDelta kInconsistentDataErrorRetryDelay = base::Seconds(30); |
| |
| policy::CloudPolicyClient* GetCloudPolicyClientForUser(Profile* profile) { |
| policy::UserCloudPolicyManagerAsh* user_cloud_policy_manager = |
| profile->GetUserCloudPolicyManagerAsh(); |
| if (!user_cloud_policy_manager) { |
| return nullptr; |
| } |
| |
| policy::CloudPolicyCore* core = user_cloud_policy_manager->core(); |
| if (!core) { |
| return nullptr; |
| } |
| |
| return core->client(); |
| } |
| |
| NetworkStateHandler* GetNetworkStateHandler() { |
| // Can happen in tests. |
| if (!NetworkHandler::IsInitialized()) { |
| return nullptr; |
| } |
| return NetworkHandler::Get()->network_state_handler(); |
| } |
| |
| } // namespace |
| |
| FailedWorkerInfo::FailedWorkerInfo() = default; |
| FailedWorkerInfo::~FailedWorkerInfo() = default; |
| FailedWorkerInfo::FailedWorkerInfo(const FailedWorkerInfo&) = default; |
| FailedWorkerInfo& FailedWorkerInfo::operator=(const FailedWorkerInfo&) = |
| default; |
| |
| // static |
| std::unique_ptr<CertProvisioningScheduler> |
| CertProvisioningSchedulerImpl::CreateUserCertProvisioningScheduler( |
| Profile* profile) { |
| PrefService* pref_service = profile->GetPrefs(); |
| policy::CloudPolicyClient* cloud_policy_client = |
| GetCloudPolicyClientForUser(profile); |
| platform_keys::PlatformKeysService* platform_keys_service = |
| GetPlatformKeysService(CertScope::kUser, profile); |
| NetworkStateHandler* network_state_handler = GetNetworkStateHandler(); |
| |
| if (!profile || !pref_service || !cloud_policy_client || |
| !network_state_handler) { |
| LOG(ERROR) << "Failed to create user certificate provisioning scheduler"; |
| return nullptr; |
| } |
| |
| return std::make_unique<CertProvisioningSchedulerImpl>( |
| CertScope::kUser, profile, pref_service, |
| std::make_unique<CertProvisioningClientImpl>(*cloud_policy_client), |
| platform_keys_service, network_state_handler, |
| std::make_unique<CertProvisioningUserInvalidatorFactory>(profile)); |
| } |
| |
| // static |
| std::unique_ptr<CertProvisioningScheduler> |
| CertProvisioningSchedulerImpl::CreateDeviceCertProvisioningScheduler( |
| PrefService* local_state, |
| policy::CloudPolicyClient* cloud_policy_client, |
| invalidation::InvalidationListener* invalidation_listener) { |
| CHECK(local_state); |
| |
| platform_keys::PlatformKeysService* platform_keys_service = |
| GetPlatformKeysService(CertScope::kDevice, /*profile=*/nullptr); |
| NetworkStateHandler* network_state_handler = GetNetworkStateHandler(); |
| |
| if (!cloud_policy_client || !network_state_handler || |
| !platform_keys_service) { |
| LOG(ERROR) << "Failed to create device certificate provisioning scheduler"; |
| return nullptr; |
| } |
| |
| return std::make_unique<CertProvisioningSchedulerImpl>( |
| CertScope::kDevice, /*profile=*/nullptr, local_state, |
| std::make_unique<CertProvisioningClientImpl>(*cloud_policy_client), |
| platform_keys_service, network_state_handler, |
| std::make_unique<CertProvisioningDeviceInvalidatorFactory>( |
| invalidation_listener)); |
| } |
| |
| CertProvisioningSchedulerImpl::CertProvisioningSchedulerImpl( |
| CertScope cert_scope, |
| Profile* profile, |
| PrefService* pref_service, |
| std::unique_ptr<CertProvisioningClient> cert_provisioning_client, |
| platform_keys::PlatformKeysService* platform_keys_service, |
| NetworkStateHandler* network_state_handler, |
| std::unique_ptr<CertProvisioningInvalidatorFactory> invalidator_factory) |
| : cert_scope_(cert_scope), |
| profile_(profile), |
| pref_service_(pref_service), |
| cert_provisioning_client_(std::move(cert_provisioning_client)), |
| platform_keys_service_(platform_keys_service), |
| network_state_handler_(network_state_handler), |
| certs_with_ids_getter_(cert_scope, platform_keys_service), |
| cert_deleter_(cert_scope, platform_keys_service), |
| invalidator_factory_(std::move(invalidator_factory)) { |
| CHECK(profile_ || cert_scope_ == CertScope::kDevice); |
| CHECK(pref_service_); |
| CHECK(cert_provisioning_client_); |
| CHECK(platform_keys_service_); |
| CHECK(network_state_handler); |
| CHECK(invalidator_factory_); |
| |
| pref_name_ = GetPrefNameForCertProfiles(cert_scope); |
| CHECK(pref_name_); |
| |
| scoped_platform_keys_service_observation_.Observe( |
| platform_keys_service_.get()); |
| |
| network_state_handler_observer_.Observe(network_state_handler_.get()); |
| |
| ScheduleInitialUpdate(); |
| ScheduleDailyUpdate(); |
| } |
| |
| CertProvisioningSchedulerImpl::~CertProvisioningSchedulerImpl() = default; |
| |
| void CertProvisioningSchedulerImpl::ScheduleInitialUpdate() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CertProvisioningSchedulerImpl::InitialUpdateCerts, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CertProvisioningSchedulerImpl::ScheduleDailyUpdate() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&CertProvisioningSchedulerImpl::DailyUpdateWorkers, |
| weak_factory_.GetWeakPtr()), |
| base::Days(1)); |
| } |
| |
| void CertProvisioningSchedulerImpl::ScheduleRetry( |
| const CertProfileId& profile_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // TODO: b/299054905 - Instead of using a hardcoded delay time, trigger a |
| // policy refresh and restart workers when policies have been applied. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&CertProvisioningSchedulerImpl::UpdateOneWorkerImpl, |
| weak_factory_.GetWeakPtr(), profile_id), |
| kInconsistentDataErrorRetryDelay); |
| } |
| |
| void CertProvisioningSchedulerImpl::ScheduleRenewal( |
| const CertProfileId& profile_id, |
| base::TimeDelta delay) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (base::Contains(scheduled_renewals_, profile_id)) { |
| return; |
| } |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&CertProvisioningSchedulerImpl::InitiateRenewal, |
| weak_factory_.GetWeakPtr(), profile_id), |
| delay); |
| } |
| |
| void CertProvisioningSchedulerImpl::InitialUpdateCerts() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| DeleteCertsWithoutPolicy(); |
| } |
| |
| void CertProvisioningSchedulerImpl::DeleteCertsWithoutPolicy() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| // No-op if the PlatformKeysService has already been shut down. |
| if (!platform_keys_service_) { |
| return; |
| } |
| |
| cert_deleter_.DeleteCerts( |
| base::MakeFlatSet<CertProfileId>(GetCertProfiles(), {}, |
| &CertProfile::profile_id), |
| base::BindOnce( |
| &CertProvisioningSchedulerImpl::OnDeleteCertsWithoutPolicyDone, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CertProvisioningSchedulerImpl::OnDeleteCertsWithoutPolicyDone( |
| chromeos::platform_keys::Status status) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (status != chromeos::platform_keys::Status::kSuccess) { |
| LOG(ERROR) << "Failed to delete certificates without policies: " |
| << chromeos::platform_keys::StatusToString(status); |
| } |
| |
| DeserializeWorkers(); |
| CleanVaKeysIfIdle(); |
| } |
| |
| void CertProvisioningSchedulerImpl::CleanVaKeysIfIdle() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (!workers_.empty()) { |
| OnCleanVaKeysIfIdleDone(true); |
| return; |
| } |
| |
| DeleteVaKeysByPrefix( |
| cert_scope_, profile_, kKeyNamePrefix, |
| base::BindOnce(&CertProvisioningSchedulerImpl::OnCleanVaKeysIfIdleDone, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CertProvisioningSchedulerImpl::OnCleanVaKeysIfIdleDone( |
| bool delete_result) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (!delete_result) { |
| LOG(ERROR) << "Failed to delete keys while idle"; |
| } |
| |
| RegisterForPrefsChanges(); |
| UpdateAllWorkers(); |
| } |
| |
| void CertProvisioningSchedulerImpl::RegisterForPrefsChanges() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| pref_change_registrar_.Init(pref_service_); |
| pref_change_registrar_.Add( |
| pref_name_, |
| base::BindRepeating(&CertProvisioningSchedulerImpl::OnPrefsChange, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CertProvisioningSchedulerImpl::DailyUpdateWorkers() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| ClearFailedWorkers(); |
| UpdateAllWorkers(); |
| ScheduleDailyUpdate(); |
| } |
| |
| void CertProvisioningSchedulerImpl::DeserializeWorkers() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| const base::Value::Dict& saved_workers = |
| pref_service_->GetDict(GetPrefNameForSerialization(cert_scope_)); |
| |
| for (const auto kv : saved_workers) { |
| const base::Value::Dict& saved_worker = kv.second.GetDict(); |
| |
| std::unique_ptr<CertProvisioningWorker> worker = |
| CertProvisioningWorkerFactory::Get()->Deserialize( |
| cert_scope_, profile_, pref_service_, saved_worker, |
| cert_provisioning_client_.get(), invalidator_factory_->Create(), |
| base::BindRepeating( |
| &CertProvisioningSchedulerImpl::OnVisibleStateChanged, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&CertProvisioningSchedulerImpl::OnProfileFinished, |
| weak_factory_.GetWeakPtr())); |
| if (!worker) { |
| // Deserialization error message was already logged. |
| continue; |
| } |
| |
| AddWorkerToMap(std::move(worker)); |
| } |
| } |
| |
| void CertProvisioningSchedulerImpl::OnPrefsChange() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| ClearFailedWorkers(); |
| UpdateAllWorkers(); |
| } |
| |
| void CertProvisioningSchedulerImpl::InitiateRenewal( |
| const CertProfileId& cert_profile_id) { |
| scheduled_renewals_.erase(cert_profile_id); |
| UpdateOneWorkerImpl(cert_profile_id); |
| } |
| |
| bool CertProvisioningSchedulerImpl::UpdateOneWorker( |
| const CertProfileId& cert_profile_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| auto worker_iter = workers_.find(cert_profile_id); |
| if (worker_iter == workers_.end()) { |
| return false; |
| } |
| |
| RecordEvent(worker_iter->second->GetCertProfile().protocol_version, |
| cert_scope_, CertProvisioningEvent::kWorkerRetryManual); |
| UpdateOneWorkerImpl(cert_profile_id); |
| return true; |
| } |
| |
| void CertProvisioningSchedulerImpl::UpdateOneWorkerImpl( |
| const CertProfileId& cert_profile_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| RemoveFailedWorker(cert_profile_id); |
| |
| std::optional<CertProfile> cert_profile = GetOneCertProfile(cert_profile_id); |
| if (!cert_profile) { |
| return; |
| } |
| |
| UpdateWorkerList({std::move(cert_profile).value()}); |
| } |
| |
| void CertProvisioningSchedulerImpl::UpdateAllWorkers() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| std::vector<CertProfile> profiles = GetCertProfiles(); |
| CancelWorkersWithoutPolicy(profiles); |
| |
| if (profiles.empty()) { |
| return; |
| } |
| |
| UpdateWorkerList(std::move(profiles)); |
| } |
| |
| void CertProvisioningSchedulerImpl::UpdateWorkerList( |
| std::vector<CertProfile> profiles) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // No-op if the PlatformKeysService has already been shut down. |
| if (!platform_keys_service_) { |
| return; |
| } |
| |
| if (!MaybeWaitForInternetConnection()) { |
| return; |
| } |
| |
| if (certs_with_ids_getter_.IsRunning()) { |
| queued_profiles_to_update_.insert(std::make_move_iterator(profiles.begin()), |
| std::make_move_iterator(profiles.end())); |
| return; |
| } |
| |
| certs_with_ids_getter_.GetCertsWithIds(base::BindOnce( |
| &CertProvisioningSchedulerImpl::UpdateWorkerListWithExistingCerts, |
| weak_factory_.GetWeakPtr(), std::move(profiles))); |
| } |
| |
| void CertProvisioningSchedulerImpl::UpdateWorkerListWithExistingCerts( |
| std::vector<CertProfile> profiles, |
| base::flat_map<CertProfileId, scoped_refptr<net::X509Certificate>> |
| existing_certs_with_ids, |
| chromeos::platform_keys::Status status) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (status != chromeos::platform_keys::Status::kSuccess) { |
| LOG(ERROR) << "Failed to get existing cert ids: " |
| << chromeos::platform_keys::StatusToString(status); |
| return; |
| } |
| |
| for (const auto& profile : profiles) { |
| if (base::Contains(failed_cert_profiles_, profile.profile_id)) { |
| continue; |
| } |
| |
| auto cert_iter = existing_certs_with_ids.find(profile.profile_id); |
| if (cert_iter == existing_certs_with_ids.end()) { |
| // The certificate does not exists and should be provisioned. |
| ProcessProfile(profile); |
| continue; |
| } |
| |
| const auto& cert = cert_iter->second; |
| base::Time now = base::Time::Now(); |
| if ((now + profile.renewal_period) >= cert->valid_expiry()) { |
| // The certificate should be renewed immediately. |
| ProcessProfile(profile); |
| continue; |
| } |
| |
| if ((now + base::Days(1) + profile.renewal_period) >= |
| cert->valid_expiry()) { |
| // The certificate should be renewed within 1 day. |
| base::Time target_time = cert->valid_expiry() - profile.renewal_period; |
| ScheduleRenewal(profile.profile_id, /*delay=*/target_time - now); |
| continue; |
| } |
| |
| CertProvisioningWorker* worker = FindWorker(profile.profile_id); |
| if (worker) { |
| // If a valid, non-expiring certificate is found but its associated worker |
| // exists, this indicates the worker was likely restored from a previous |
| // state (deserialized) and is now redundant. |
| worker->Stop(CertProvisioningWorkerState::kSucceeded); |
| continue; |
| } |
| } |
| |
| if (!queued_profiles_to_update_.empty()) { |
| // base::flat_set::extract() guaranties that the set is `empty()` |
| // afterwards. |
| UpdateWorkerList(std::move(queued_profiles_to_update_).extract()); |
| } |
| } |
| |
| void CertProvisioningSchedulerImpl::ProcessProfile( |
| const CertProfile& cert_profile) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| CertProvisioningWorker* worker = FindWorker(cert_profile.profile_id); |
| if (!worker) { |
| CreateCertProvisioningWorker(cert_profile); |
| return; |
| } |
| |
| if ((worker->GetCertProfile().policy_version != |
| cert_profile.policy_version)) { |
| // The worker has outdated policy version. Make it stop, clean up current |
| // state and report back through its callback. That will trigger retry for |
| // its certificate profile. |
| worker->Stop(CertProvisioningWorkerState::kInconsistentDataError); |
| return; |
| } |
| |
| if (worker->IsWaiting()) { |
| worker->DoStep(); |
| return; |
| } |
| |
| // There already is an active worker for this profile. No action required. |
| return; |
| } |
| |
| void CertProvisioningSchedulerImpl::CreateCertProvisioningWorker( |
| CertProfile cert_profile) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| std::unique_ptr<CertProvisioningWorker> worker = |
| CertProvisioningWorkerFactory::Get()->Create( |
| GenerateCertProvisioningId(), cert_scope_, profile_, pref_service_, |
| cert_profile, cert_provisioning_client_.get(), |
| invalidator_factory_->Create(), |
| base::BindRepeating( |
| &CertProvisioningSchedulerImpl::OnVisibleStateChanged, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&CertProvisioningSchedulerImpl::OnProfileFinished, |
| weak_factory_.GetWeakPtr())); |
| CertProvisioningWorker* worker_unowned = AddWorkerToMap(std::move(worker)); |
| worker_unowned->DoStep(); |
| } |
| |
| void CertProvisioningSchedulerImpl::OnProfileFinished( |
| CertProfile profile, |
| std::string process_id, |
| CertProvisioningWorkerState state) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| auto worker_iter = workers_.find(profile.profile_id); |
| if (worker_iter == workers_.end()) { |
| NOTREACHED() << "Finished worker is not found" |
| << base::StringPrintf(" [cppId: %s]", process_id.c_str()); |
| } |
| bool recreate = false; |
| switch (state) { |
| case CertProvisioningWorkerState::kSucceeded: |
| VLOG(0) << "Successfully provisioned certificate" |
| << base::StringPrintf(" [cppId: %s, profileId: %s]", |
| process_id.c_str(), |
| profile.profile_id.c_str()); |
| break; |
| case CertProvisioningWorkerState::kInconsistentDataError: |
| LOG(WARNING) << "Inconsistent data error" |
| << base::StringPrintf(" [cppId: %s, profileId: %s]", |
| process_id.c_str(), |
| profile.profile_id.c_str()); |
| ScheduleRetry(profile.profile_id); |
| break; |
| case CertProvisioningWorkerState::kCanceled: |
| if (worker_iter->second->IsWorkerMarkedForReset()) { |
| recreate = true; |
| } |
| break; |
| default: |
| LOG(ERROR) << "Failed to process certificate" |
| << base::StringPrintf(" [cppId: %s, profileId: %s]", |
| process_id.c_str(), |
| profile.profile_id.c_str()); |
| UpdateFailedCertProfiles(*(worker_iter->second)); |
| break; |
| } |
| |
| if (recreate) { |
| // Avoid updating the ui after removal of the worker as it it will close the |
| // dialogue while reseting. The ui will be updated when the new worker is |
| // created. |
| RemoveWorkerFromMap(worker_iter, |
| /*send_visible_state_changed_update=*/false); |
| CreateCertProvisioningWorker(std::move(profile)); |
| } else { |
| RemoveWorkerFromMap(worker_iter, |
| /*send_visible_state_changed_update=*/true); |
| } |
| } |
| |
| bool CertProvisioningSchedulerImpl::ResetOneWorker( |
| const CertProfileId& cert_profile_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| RemoveFailedWorker(cert_profile_id); |
| |
| std::optional<CertProfile> cert_profile = GetOneCertProfile(cert_profile_id); |
| if (!cert_profile) { |
| return false; |
| } |
| CertProvisioningWorker* worker = FindWorker(cert_profile_id); |
| if (!worker) { |
| UpdateOneWorkerImpl(cert_profile_id); |
| return true; |
| } |
| if (worker->IsWorkerMarkedForReset()) { |
| return true; |
| } |
| worker->MarkWorkerForReset(); |
| worker->Stop(CertProvisioningWorkerState::kCanceled); |
| return true; |
| } |
| |
| CertProvisioningWorker* CertProvisioningSchedulerImpl::FindWorker( |
| CertProfileId profile_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| auto iter = workers_.find(profile_id); |
| if (iter == workers_.end()) { |
| return nullptr; |
| } |
| |
| return iter->second.get(); |
| } |
| |
| CertProvisioningWorker* CertProvisioningSchedulerImpl::AddWorkerToMap( |
| std::unique_ptr<CertProvisioningWorker> worker) { |
| CertProvisioningWorker* worker_unowned = worker.get(); |
| workers_[worker_unowned->GetCertProfile().profile_id] = std::move(worker); |
| OnVisibleStateChanged(); |
| return worker_unowned; |
| } |
| |
| void CertProvisioningSchedulerImpl::RemoveWorkerFromMap( |
| WorkerMap::iterator worker_iter, |
| bool send_visible_state_changed_update) { |
| workers_.erase(worker_iter); |
| // In a case like removing an existing worker for the intent of recreation, |
| // the ui should not be sent an update here as this will cause the worker |
| // dialogue to be closed. Instead, the ui update will be triggered by the new |
| // worker. |
| if (send_visible_state_changed_update) { |
| OnVisibleStateChanged(); |
| } |
| } |
| |
| void CertProvisioningSchedulerImpl::RemoveFailedWorker( |
| const CertProfileId& cert_profile_id) { |
| auto failed_profile = failed_cert_profiles_.find(cert_profile_id); |
| if (failed_profile != failed_cert_profiles_.end()) { |
| failed_cert_profiles_.erase(failed_profile); |
| OnVisibleStateChanged(); |
| } |
| } |
| |
| void CertProvisioningSchedulerImpl::ClearFailedWorkers() { |
| if (!failed_cert_profiles_.empty()) { |
| failed_cert_profiles_.clear(); |
| OnVisibleStateChanged(); |
| } |
| } |
| |
| std::optional<CertProfile> CertProvisioningSchedulerImpl::GetOneCertProfile( |
| const CertProfileId& cert_profile_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| const base::Value& profile_list = pref_service_->GetValue(pref_name_); |
| |
| for (const base::Value& cur_profile : profile_list.GetList()) { |
| const base::Value::Dict& cur_profile_dict = cur_profile.GetDict(); |
| const CertProfileId* id = cur_profile_dict.FindString(kCertProfileIdKey); |
| if (!id || (*id != cert_profile_id)) { |
| continue; |
| } |
| |
| return CertProfile::MakeFromValue(cur_profile_dict); |
| } |
| |
| return std::nullopt; |
| } |
| |
| std::vector<CertProfile> CertProvisioningSchedulerImpl::GetCertProfiles() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| const base::Value& profile_list = pref_service_->GetValue(pref_name_); |
| |
| std::vector<CertProfile> result_profiles; |
| for (const base::Value& cur_profile : profile_list.GetList()) { |
| std::optional<CertProfile> p = |
| CertProfile::MakeFromValue(cur_profile.GetDict()); |
| if (!p) { |
| LOG(WARNING) << "Failed to parse certificate profile"; |
| continue; |
| } |
| |
| result_profiles.emplace_back(std::move(p.value())); |
| } |
| |
| return result_profiles; |
| } |
| |
| const WorkerMap& CertProvisioningSchedulerImpl::GetWorkers() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| return workers_; |
| } |
| |
| const base::flat_map<CertProfileId, FailedWorkerInfo>& |
| CertProvisioningSchedulerImpl::GetFailedCertProfileIds() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| return failed_cert_profiles_; |
| } |
| |
| base::CallbackListSubscription CertProvisioningSchedulerImpl::AddObserver( |
| base::RepeatingClosure callback) { |
| return observers_.Add(std::move(callback)); |
| } |
| |
| bool CertProvisioningSchedulerImpl::MaybeWaitForInternetConnection() { |
| const NetworkState* network = network_state_handler_->DefaultNetwork(); |
| bool is_online = network && network->IsOnline(); |
| |
| if (is_online) { |
| is_waiting_for_online_ = false; |
| return true; |
| } |
| |
| WaitForInternetConnection(); |
| return false; |
| } |
| |
| void CertProvisioningSchedulerImpl::WaitForInternetConnection() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (is_waiting_for_online_) { |
| return; |
| } |
| |
| if (!workers_.empty()) { |
| VLOG(0) << "Waiting for internet connection"; |
| } |
| |
| is_waiting_for_online_ = true; |
| for (auto& kv : workers_) { |
| auto& worker_ptr = kv.second; |
| worker_ptr->Pause(); |
| } |
| } |
| |
| void CertProvisioningSchedulerImpl::OnNetworkChange( |
| const NetworkState* network) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // If waiting for connection and some network becomes online, try to continue. |
| if (is_waiting_for_online_ && network && network->IsOnline()) { |
| is_waiting_for_online_ = false; |
| UpdateAllWorkers(); |
| return; |
| } |
| |
| // If not waiting, check that after this network change some connection still |
| // exists. |
| if (!is_waiting_for_online_ && !workers_.empty()) { |
| MaybeWaitForInternetConnection(); |
| return; |
| } |
| } |
| |
| void CertProvisioningSchedulerImpl::DefaultNetworkChanged( |
| const NetworkState* network) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| OnNetworkChange(network); |
| } |
| |
| void CertProvisioningSchedulerImpl::NetworkConnectionStateChanged( |
| const NetworkState* network) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| OnNetworkChange(network); |
| } |
| |
| void CertProvisioningSchedulerImpl::UpdateFailedCertProfiles( |
| const CertProvisioningWorker& worker) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| FailedWorkerInfo info; |
| info.process_id = worker.GetProcessId(); |
| info.state_before_failure = worker.GetPreviousState(); |
| info.cert_profile_name = worker.GetCertProfile().name; |
| info.public_key = worker.GetPublicKey(); |
| info.last_update_time = worker.GetLastUpdateTime(); |
| info.failure_message = worker.GetFailureMessageWithPii(); |
| |
| failed_cert_profiles_[worker.GetCertProfile().profile_id] = std::move(info); |
| } |
| |
| void CertProvisioningSchedulerImpl::OnPlatformKeysServiceShutDown() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // The |platform_keys_service_| will only return errors going forward, so |
| // stop using it. Shutdown all workers, as if this CertProvisioningScheduler |
| // was destroyed, and stop pending tasks that may depend on |
| // |platform_keys_service_|. |
| workers_.clear(); |
| certs_with_ids_getter_.Cancel(); |
| cert_deleter_.Cancel(); |
| pref_change_registrar_.RemoveAll(); |
| weak_factory_.InvalidateWeakPtrs(); |
| |
| scoped_platform_keys_service_observation_.Reset(); |
| platform_keys_service_ = nullptr; |
| } |
| |
| void CertProvisioningSchedulerImpl::CancelWorkersWithoutPolicy( |
| const std::vector<CertProfile>& profiles) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (workers_.empty()) { |
| return; |
| } |
| |
| std::unordered_set<CertProfileId> cert_profile_ids; |
| for (const CertProfile& profile : profiles) { |
| cert_profile_ids.insert(profile.profile_id); |
| } |
| |
| for (auto& kv : workers_) { |
| auto& worker_ptr = kv.second; |
| if (cert_profile_ids.find(worker_ptr->GetCertProfile().profile_id) == |
| cert_profile_ids.end()) { |
| // This will trigger clean up (if any) in the worker and make it call its |
| // callback. |
| worker_ptr->Stop(CertProvisioningWorkerState::kCanceled); |
| } |
| } |
| } |
| |
| void CertProvisioningSchedulerImpl::OnVisibleStateChanged() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // |notify_observers_pending_| prevents the scheduler from sending multiple |
| // notifications from a single synchronous code execution sequence. Extra |
| // notifications are discarded. |
| if (observers_.empty() || notify_observers_pending_) { |
| return; |
| } |
| |
| // |hold_back_updates_timer_| prevents the scheduler from sending multiple |
| // notifications within a specified time period from asynchronous tasks. Extra |
| // notifications are combined into one and delayed. |
| if (hold_back_updates_timer_.IsRunning()) { |
| update_after_hold_back_ = true; |
| return; |
| } |
| constexpr base::TimeDelta kTimeToHoldBackUpdates = base::Milliseconds(300); |
| hold_back_updates_timer_.Start( |
| FROM_HERE, kTimeToHoldBackUpdates, |
| base::BindOnce( |
| &CertProvisioningSchedulerImpl::OnHoldBackUpdatesTimerExpired, |
| weak_factory_.GetWeakPtr())); |
| |
| notify_observers_pending_ = true; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CertProvisioningSchedulerImpl::NotifyObserversVisibleStateChanged, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CertProvisioningSchedulerImpl::OnHoldBackUpdatesTimerExpired() { |
| if (update_after_hold_back_) { |
| update_after_hold_back_ = false; |
| NotifyObserversVisibleStateChanged(); |
| } |
| } |
| |
| void CertProvisioningSchedulerImpl::NotifyObserversVisibleStateChanged() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| notify_observers_pending_ = false; |
| observers_.Notify(); |
| } |
| |
| } // namespace ash::cert_provisioning |