| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/policy/core/common/policy_service_impl.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/observer_list.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/policy/core/common/policy_bundle.h" |
| #include "components/policy/core/common/policy_logger.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/core/common/policy_merger.h" |
| #include "components/policy/core/common/policy_types.h" |
| #include "components/policy/core/common/proxy_settings_constants.h" |
| #include "components/policy/core/common/values_util.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "extensions/buildflags/buildflags.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "components/policy/core/common/android/policy_service_android.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "components/policy/core/common/default_chrome_apps_migrator.h" |
| #endif |
| |
| namespace policy { |
| |
| namespace { |
| |
| // Precedence policies cannot be set at the user cloud level regardless of |
| // affiliation status. This is done to prevent cloud users from potentially |
| // giving themselves increased priority, causing a security issue. |
| void IgnoreUserCloudPrecedencePolicies(PolicyMap* policies) { |
| for (auto* policy_name : metapolicy::kPrecedence) { |
| const PolicyMap::Entry* policy_entry = policies->Get(policy_name); |
| if (policy_entry && policy_entry->scope == POLICY_SCOPE_USER && |
| policy_entry->source == POLICY_SOURCE_CLOUD) { |
| PolicyMap::Entry* policy_entry_mutable = |
| policies->GetMutable(policy_name); |
| policy_entry_mutable->SetIgnored(); |
| policy_entry_mutable->AddMessage(PolicyMap::MessageType::kError, |
| IDS_POLICY_IGNORED_CHROME_PROFILE); |
| } |
| } |
| } |
| |
| // Metrics should not be enforced so if this policy is set as mandatory |
| // downgrade it to a recommended level policy. |
| void DowngradeMetricsReportingToRecommendedPolicy(PolicyMap* policies) { |
| // Capture both the Chrome-only and device-level policies on Chrome OS. |
| const std::vector<const char*> metrics_keys = { |
| #if BUILDFLAG(IS_CHROMEOS) |
| policy::key::kDeviceMetricsReportingEnabled, |
| #else |
| policy::key::kMetricsReportingEnabled, |
| #endif |
| }; |
| for (const char* policy_key : metrics_keys) { |
| PolicyMap::Entry* policy = policies->GetMutable(policy_key); |
| if (policy && policy->level != POLICY_LEVEL_RECOMMENDED && |
| policy->value(base::Value::Type::BOOLEAN) && |
| policy->value(base::Value::Type::BOOLEAN)->GetBool()) { |
| policy->level = POLICY_LEVEL_RECOMMENDED; |
| policy->AddMessage(PolicyMap::MessageType::kInfo, |
| IDS_POLICY_IGNORED_MANDATORY_REPORTING_POLICY); |
| } |
| } |
| } |
| |
| // Returns the string values of |policy|. Returns an empty set if the values are |
| // not strings. |
| base::flat_set<std::string> GetStringListPolicyItems( |
| const PolicyBundle& bundle, |
| const PolicyNamespace& space, |
| const std::string& policy) { |
| return ValueToStringSet( |
| bundle.Get(space).GetValue(policy, base::Value::Type::LIST)); |
| } |
| |
| bool IsUserCloudMergingAllowed(const PolicyMap& policies) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| return false; |
| #else |
| const base::Value* cloud_user_policy_merge_value = |
| policies.GetValue(key::kCloudUserPolicyMerge, base::Value::Type::BOOLEAN); |
| return cloud_user_policy_merge_value && |
| cloud_user_policy_merge_value->GetBool(); |
| #endif |
| } |
| |
| void AddPolicyMessages(PolicyMap& policies) { |
| #if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_IOS) |
| // Add warning to inform users that these policies are ignored when the user |
| // is unaffiliated. |
| if (policies.IsUserAffiliated()) |
| return; |
| |
| auto* cloud_user_precedence_entry = |
| policies.GetMutable(key::kCloudUserPolicyOverridesCloudMachinePolicy); |
| if (cloud_user_precedence_entry && |
| cloud_user_precedence_entry->value(base::Value::Type::BOOLEAN) && |
| cloud_user_precedence_entry->value(base::Value::Type::BOOLEAN) |
| ->GetBool()) { |
| cloud_user_precedence_entry->AddMessage(PolicyMap::MessageType::kError, |
| IDS_POLICY_IGNORED_UNAFFILIATED); |
| } |
| |
| if (IsUserCloudMergingAllowed(policies)) { |
| policies.GetMutable(key::kCloudUserPolicyMerge) |
| ->AddMessage(PolicyMap::MessageType::kError, |
| IDS_POLICY_IGNORED_UNAFFILIATED); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_IOS) |
| } |
| |
| } // namespace |
| |
| PolicyServiceImpl::PolicyServiceImpl(Providers providers, Migrators migrators) |
| : PolicyServiceImpl(std::move(providers), |
| std::move(migrators), |
| /*initialization_throttled=*/false) {} |
| |
| PolicyServiceImpl::PolicyServiceImpl(Providers providers, |
| Migrators migrators, |
| bool initialization_throttled) |
| : providers_(std::move(providers)), |
| migrators_(std::move(migrators)), |
| initialization_throttled_(initialization_throttled) { |
| for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) |
| policy_domain_status_[domain] = PolicyDomainStatus::kUninitialized; |
| |
| for (auto* provider : providers_) |
| provider->AddObserver(this); |
| // There are no observers yet, but calls to GetPolicies() should already get |
| // the processed policy values. |
| MergeAndTriggerUpdates(); |
| } |
| |
| // static |
| std::unique_ptr<PolicyServiceImpl> |
| PolicyServiceImpl::CreateWithThrottledInitialization(Providers providers, |
| Migrators migrators) { |
| return base::WrapUnique( |
| new PolicyServiceImpl(std::move(providers), std::move(migrators), |
| /*initialization_throttled=*/true)); |
| } |
| |
| PolicyServiceImpl::~PolicyServiceImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| for (auto* provider : providers_) |
| provider->RemoveObserver(this); |
| } |
| |
| void PolicyServiceImpl::AddObserver(PolicyDomain domain, |
| PolicyService::Observer* observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| observers_[domain].AddObserver(observer); |
| } |
| |
| void PolicyServiceImpl::RemoveObserver(PolicyDomain domain, |
| PolicyService::Observer* observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| auto it = observers_.find(domain); |
| if (it == observers_.end()) |
| return; |
| it->second.RemoveObserver(observer); |
| if (it->second.empty()) { |
| observers_.erase(it); |
| } |
| } |
| |
| void PolicyServiceImpl::AddProviderUpdateObserver( |
| ProviderUpdateObserver* observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| provider_update_observers_.AddObserver(observer); |
| } |
| |
| void PolicyServiceImpl::RemoveProviderUpdateObserver( |
| ProviderUpdateObserver* observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| provider_update_observers_.RemoveObserver(observer); |
| } |
| |
| bool PolicyServiceImpl::HasProvider( |
| ConfigurationPolicyProvider* provider) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return base::Contains(providers_, provider); |
| } |
| |
| const PolicyMap& PolicyServiceImpl::GetPolicies( |
| const PolicyNamespace& ns) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return policy_bundle_.Get(ns); |
| } |
| |
| bool PolicyServiceImpl::IsInitializationComplete(PolicyDomain domain) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE); |
| return !initialization_throttled_ && |
| policy_domain_status_[domain] != PolicyDomainStatus::kUninitialized; |
| } |
| |
| bool PolicyServiceImpl::IsFirstPolicyLoadComplete(PolicyDomain domain) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(domain >= 0 && domain < POLICY_DOMAIN_SIZE); |
| return !initialization_throttled_ && |
| policy_domain_status_[domain] == PolicyDomainStatus::kPolicyReady; |
| } |
| |
| void PolicyServiceImpl::RefreshPolicies(base::OnceClosure callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| VLOG_POLICY(2, POLICY_PROCESSING) << "Policy refresh starting"; |
| |
| if (!callback.is_null()) |
| refresh_callbacks_.push_back(std::move(callback)); |
| |
| if (providers_.empty()) { |
| // Refresh is immediately complete if there are no providers. See the note |
| // on OnUpdatePolicy() about why this is a posted task. |
| update_task_ptr_factory_.InvalidateWeakPtrs(); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&PolicyServiceImpl::MergeAndTriggerUpdates, |
| update_task_ptr_factory_.GetWeakPtr())); |
| |
| VLOG_POLICY(2, POLICY_PROCESSING) << "Policy refresh has no providers"; |
| } else { |
| // Some providers might invoke OnUpdatePolicy synchronously while handling |
| // RefreshPolicies. Mark all as pending before refreshing. |
| for (auto* provider : providers_) |
| refresh_pending_.insert(provider); |
| for (auto* provider : providers_) |
| provider->RefreshPolicies(); |
| } |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| android::PolicyServiceAndroid* PolicyServiceImpl::GetPolicyServiceAndroid() { |
| if (!policy_service_android_) |
| policy_service_android_ = |
| std::make_unique<android::PolicyServiceAndroid>(this); |
| return policy_service_android_.get(); |
| } |
| #endif |
| |
| void PolicyServiceImpl::UnthrottleInitialization() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!initialization_throttled_) |
| return; |
| |
| initialization_throttled_ = false; |
| std::vector<PolicyDomain> updated_domains; |
| for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) |
| updated_domains.push_back(static_cast<PolicyDomain>(domain)); |
| MaybeNotifyPolicyDomainStatusChange(updated_domains); |
| } |
| |
| void PolicyServiceImpl::OnUpdatePolicy(ConfigurationPolicyProvider* provider) { |
| DCHECK_EQ(1, base::ranges::count(providers_, provider)); |
| refresh_pending_.erase(provider); |
| provider_update_pending_.insert(provider); |
| |
| // Note: a policy change may trigger further policy changes in some providers. |
| // For example, disabling SigninAllowed would cause the CloudPolicyManager to |
| // drop all its policies, which makes this method enter again for that |
| // provider. |
| // |
| // Therefore this update is posted asynchronously, to prevent reentrancy in |
| // MergeAndTriggerUpdates. Also, cancel a pending update if there is any, |
| // since both will produce the same PolicyBundle. |
| update_task_ptr_factory_.InvalidateWeakPtrs(); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&PolicyServiceImpl::MergeAndTriggerUpdates, |
| update_task_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void PolicyServiceImpl::NotifyNamespaceUpdated(const PolicyNamespace& ns, |
| const PolicyMap& previous, |
| const PolicyMap& current) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| auto iterator = observers_.find(ns.domain); |
| if (iterator != observers_.end()) { |
| for (auto& observer : iterator->second) |
| observer.OnPolicyUpdated(ns, previous, current); |
| } |
| } |
| |
| void PolicyServiceImpl::NotifyProviderUpdatesPropagated() { |
| if (provider_update_pending_.empty()) |
| return; |
| |
| for (auto& provider_update_observer : provider_update_observers_) { |
| for (ConfigurationPolicyProvider* provider : provider_update_pending_) { |
| provider_update_observer.OnProviderUpdatePropagated(provider); |
| } |
| } |
| provider_update_pending_.clear(); |
| } |
| |
| void PolicyServiceImpl::MergeAndTriggerUpdates() { |
| // Merge from each provider in their order of priority. |
| const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string()); |
| PolicyBundle bundle; |
| #if BUILDFLAG(IS_CHROMEOS) |
| DefaultChromeAppsMigrator chrome_apps_migrator; |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| #if !BUILDFLAG(IS_CHROMEOS) |
| // Merge precedence metapolicies into the bundle first. |
| // Because their value affects policy overriding. |
| for (auto* provider : providers_) { |
| if (!provider->is_active()) { |
| continue; |
| } |
| PolicyMap provider_map = provider->policies().Get(chrome_namespace).Clone(); |
| IgnoreUserCloudPrecedencePolicies(&provider_map); |
| bundle.Get(chrome_namespace) |
| .MergeFrom(provider_map, /*merge_precedence_metapolicies=*/true); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| for (auto* provider : providers_) { |
| if (!provider->is_active()) { |
| continue; |
| } |
| |
| PolicyBundle provided_bundle = provider->policies().Clone(); |
| DowngradeMetricsReportingToRecommendedPolicy( |
| &provided_bundle.Get(chrome_namespace)); |
| #if BUILDFLAG(IS_CHROMEOS) |
| IgnoreUserCloudPrecedencePolicies(&provided_bundle.Get(chrome_namespace)); |
| chrome_apps_migrator.Migrate(&provided_bundle.Get(chrome_namespace)); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| bundle.MergeFrom(provided_bundle); |
| } |
| |
| auto& chrome_policies = bundle.Get(chrome_namespace); |
| |
| // Merges all the mergeable policies |
| base::flat_set<std::string> policy_lists_to_merge = GetStringListPolicyItems( |
| bundle, chrome_namespace, key::kPolicyListMultipleSourceMergeList); |
| base::flat_set<std::string> policy_dictionaries_to_merge = |
| GetStringListPolicyItems(bundle, chrome_namespace, |
| key::kPolicyDictionaryMultipleSourceMergeList); |
| |
| // This has to be done after setting enterprise default values since it is |
| // enabled by default for enterprise users. |
| auto* atomic_policy_group_enabled_entry = |
| chrome_policies.Get(key::kPolicyAtomicGroupsEnabled); |
| |
| // This policy has to be ignored if it comes from a user signed-in profile. |
| bool atomic_policy_group_enabled = |
| atomic_policy_group_enabled_entry && |
| atomic_policy_group_enabled_entry->value(base::Value::Type::BOOLEAN) && |
| atomic_policy_group_enabled_entry->value(base::Value::Type::BOOLEAN) |
| ->GetBool() && |
| !(atomic_policy_group_enabled_entry->source == POLICY_SOURCE_CLOUD && |
| atomic_policy_group_enabled_entry->scope == POLICY_SCOPE_USER); |
| |
| PolicyListMerger policy_list_merger(std::move(policy_lists_to_merge)); |
| PolicyDictionaryMerger policy_dictionary_merger( |
| std::move(policy_dictionaries_to_merge)); |
| |
| // Pass affiliation and CloudUserPolicyMerge values to both mergers. |
| const bool is_user_affiliated = chrome_policies.IsUserAffiliated(); |
| const bool is_user_cloud_merging_enabled = |
| IsUserCloudMergingAllowed(chrome_policies); |
| policy_list_merger.SetAllowUserCloudPolicyMerging( |
| is_user_affiliated && is_user_cloud_merging_enabled); |
| policy_dictionary_merger.SetAllowUserCloudPolicyMerging( |
| is_user_affiliated && is_user_cloud_merging_enabled); |
| |
| std::vector<PolicyMerger*> mergers{&policy_list_merger, |
| &policy_dictionary_merger}; |
| |
| PolicyGroupMerger policy_group_merger; |
| if (atomic_policy_group_enabled) |
| mergers.push_back(&policy_group_merger); |
| |
| for (auto& entry : bundle) |
| entry.second.MergeValues(mergers); |
| |
| for (auto& migrator : migrators_) |
| migrator->Migrate(&bundle); |
| |
| // Add informational messages to specific policies. |
| AddPolicyMessages(chrome_policies); |
| |
| // Swap first, so that observers that call GetPolicies() see the current |
| // values. |
| std::swap(policy_bundle_, bundle); |
| |
| // Only notify observers of namespaces that have been modified. |
| const PolicyMap kEmpty; |
| PolicyBundle::const_iterator it_new = policy_bundle_.begin(); |
| PolicyBundle::const_iterator end_new = policy_bundle_.end(); |
| PolicyBundle::const_iterator it_old = bundle.begin(); |
| PolicyBundle::const_iterator end_old = bundle.end(); |
| while (it_new != end_new && it_old != end_old) { |
| if (it_new->first < it_old->first) { |
| // A new namespace is available. |
| NotifyNamespaceUpdated(it_new->first, kEmpty, it_new->second); |
| ++it_new; |
| } else if (it_old->first < it_new->first) { |
| // A previously available namespace is now gone. |
| NotifyNamespaceUpdated(it_old->first, it_old->second, kEmpty); |
| ++it_old; |
| } else { |
| if (!it_new->second.Equals(it_old->second)) { |
| // An existing namespace's policies have changed. |
| NotifyNamespaceUpdated(it_new->first, it_old->second, it_new->second); |
| } |
| ++it_new; |
| ++it_old; |
| } |
| } |
| |
| // Send updates for the remaining new namespaces, if any. |
| for (; it_new != end_new; ++it_new) |
| NotifyNamespaceUpdated(it_new->first, kEmpty, it_new->second); |
| |
| // Sends updates for the remaining removed namespaces, if any. |
| for (; it_old != end_old; ++it_old) |
| NotifyNamespaceUpdated(it_old->first, it_old->second, kEmpty); |
| |
| const std::vector<PolicyDomain> updated_domains = UpdatePolicyDomainStatus(); |
| CheckRefreshComplete(); |
| NotifyProviderUpdatesPropagated(); |
| // This has to go last as one of the observers might actually destroy `this`. |
| // See https://crbug.com/747817 |
| MaybeNotifyPolicyDomainStatusChange(updated_domains); |
| } |
| |
| std::vector<PolicyDomain> PolicyServiceImpl::UpdatePolicyDomainStatus() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| std::vector<PolicyDomain> updated_domains; |
| |
| // Check if all the providers just became initialized for each domain; if so, |
| // notify that domain's observers. If they were initialized, check if they had |
| // their first policies loaded. |
| for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) { |
| PolicyDomain policy_domain = static_cast<PolicyDomain>(domain); |
| if (policy_domain_status_[domain] == PolicyDomainStatus::kPolicyReady) |
| continue; |
| |
| PolicyDomainStatus new_status = PolicyDomainStatus::kPolicyReady; |
| |
| for (auto* provider : providers_) { |
| if (!provider->IsInitializationComplete(policy_domain)) { |
| new_status = PolicyDomainStatus::kUninitialized; |
| break; |
| } else if (!provider->IsFirstPolicyLoadComplete(policy_domain)) { |
| new_status = PolicyDomainStatus::kInitialized; |
| } |
| } |
| |
| if (new_status == policy_domain_status_[domain]) |
| continue; |
| |
| policy_domain_status_[domain] = new_status; |
| updated_domains.push_back(static_cast<PolicyDomain>(domain)); |
| } |
| return updated_domains; |
| } |
| |
| void PolicyServiceImpl::MaybeNotifyPolicyDomainStatusChange( |
| const std::vector<PolicyDomain>& updated_domains) { |
| if (initialization_throttled_) |
| return; |
| |
| for (const auto policy_domain : updated_domains) { |
| if (policy_domain_status_[policy_domain] == |
| PolicyDomainStatus::kUninitialized) { |
| continue; |
| } |
| |
| auto iter = observers_.find(policy_domain); |
| if (iter == observers_.end()) |
| continue; |
| |
| // If and when crbug.com/1221454 gets fixed, we should drop the WeakPtr |
| // construction and checks here. |
| const auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
| for (auto& observer : iter->second) { |
| observer.OnPolicyServiceInitialized(policy_domain); |
| if (!weak_this) { |
| VLOG_POLICY(1, POLICY_PROCESSING) |
| << "PolicyService destroyed while notifying observers."; |
| return; |
| } |
| if (policy_domain_status_[policy_domain] == |
| PolicyDomainStatus::kPolicyReady) { |
| observer.OnFirstPoliciesLoaded(policy_domain); |
| // If this gets hit, it implies that some OnFirstPoliciesLoaded() |
| // observer was changed to trigger the deletion of |this|. See |
| // crbug.com/1221454 for a similar problem with |
| // OnPolicyServiceInitialized(). |
| CHECK(weak_this); |
| } |
| } |
| } |
| } |
| |
| void PolicyServiceImpl::CheckRefreshComplete() { |
| if (refresh_pending_.empty()) { |
| VLOG(2) << "Policy refresh complete"; |
| } |
| |
| // Invoke all the callbacks if a refresh has just fully completed. |
| if (refresh_pending_.empty() && !refresh_callbacks_.empty()) { |
| std::vector<base::OnceClosure> callbacks; |
| callbacks.swap(refresh_callbacks_); |
| for (auto& callback : callbacks) |
| std::move(callback).Run(); |
| } |
| } |
| |
| } // namespace policy |