blob: 91ecf1cd30ecbc24c1e1914568d190e47640b446 [file] [log] [blame]
// Copyright 2013 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_map.h"
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/optional_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/policy/core/common/cloud/affiliation.h"
#include "components/policy/core/common/policy_details.h"
#include "components/policy/core/common/policy_logger.h"
#include "components/policy/core/common/policy_merger.h"
#include "components/policy/policy_constants.h"
#include "components/strings/grit/components_strings.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/base/l10n/l10n_util.h"
namespace policy {
namespace {
const std::u16string GetLocalizedString(
PolicyMap::Entry::L10nLookupFunction lookup,
const std::map<int, absl::optional<std::vector<std::u16string>>>&
localized_string_ids) {
std::u16string result = std::u16string();
std::u16string line_feed = u"\n";
for (const auto& string_pairs : localized_string_ids) {
if (string_pairs.second)
result += l10n_util::GetStringFUTF16(
string_pairs.first, string_pairs.second.value(), nullptr);
else
result += lookup.Run(string_pairs.first);
result += line_feed;
}
// Remove the trailing newline.
if (!result.empty() && result[result.length() - 1] == line_feed[0])
result.pop_back();
return result;
}
// Inserts additional user affiliation IDs to the existing set.
base::flat_set<std::string> CombineIds(
const base::flat_set<std::string>& ids_first,
const base::flat_set<std::string>& ids_second) {
base::flat_set<std::string> combined_ids;
combined_ids.insert(ids_first.begin(), ids_first.end());
combined_ids.insert(ids_second.begin(), ids_second.end());
return combined_ids;
}
#if !BUILDFLAG(IS_CHROMEOS)
// Returns the calculated priority of the policy entry based on the policy's
// scope and source, in addition to external factors such as precedence
// metapolicy values. Used for browser policies.
PolicyPriorityBrowser GetPriority(
PolicySource source,
PolicyScope scope,
bool cloud_policy_overrides_platform_policy,
bool cloud_user_policy_overrides_cloud_machine_policy,
bool is_user_affiliated) {
switch (source) {
case POLICY_SOURCE_ENTERPRISE_DEFAULT:
return POLICY_PRIORITY_BROWSER_ENTERPRISE_DEFAULT;
case POLICY_SOURCE_COMMAND_LINE:
return POLICY_PRIORITY_BROWSER_COMMAND_LINE;
case POLICY_SOURCE_CLOUD:
if (scope == POLICY_SCOPE_MACHINE) {
// Raise the priority of cloud machine policies only when the metapolicy
// CloudPolicyOverridesPlatformPolicy is set to true.
return cloud_policy_overrides_platform_policy
? POLICY_PRIORITY_BROWSER_CLOUD_MACHINE_RAISED
: POLICY_PRIORITY_BROWSER_CLOUD_MACHINE;
}
if (cloud_user_policy_overrides_cloud_machine_policy &&
is_user_affiliated) {
// Raise the priority of cloud user policies only when the metapolicy
// CloudUserPolicyOverridesCloudMachinePolicy is set to true and the
// user is affiliated. Its priority relative to cloud machine policies
// also depends on the value of CloudPolicyOverridesPlatformPolicy.
return cloud_policy_overrides_platform_policy
? POLICY_PRIORITY_BROWSER_CLOUD_USER_DOUBLE_RAISED
: POLICY_PRIORITY_BROWSER_CLOUD_USER_RAISED;
}
return POLICY_PRIORITY_BROWSER_CLOUD_USER;
case POLICY_SOURCE_PRIORITY_CLOUD_DEPRECATED:
return POLICY_PRIORITY_BROWSER_CLOUD_MACHINE_RAISED;
case POLICY_SOURCE_PLATFORM:
return scope == POLICY_SCOPE_MACHINE
? POLICY_PRIORITY_BROWSER_PLATFORM_MACHINE
: POLICY_PRIORITY_BROWSER_PLATFORM_USER;
case POLICY_SOURCE_MERGED:
return POLICY_PRIORITY_BROWSER_MERGED;
default:
NOTREACHED();
return POLICY_PRIORITY_BROWSER_ENTERPRISE_DEFAULT;
}
}
#endif // BUILDFLAG(IS_CHROMEOS)
// Helper function used in the invocation of `PolicyMap::CloneIf` from
// `PolicyMap::Clone`.
bool AllowAllPolicies(PolicyMap::const_reference unused) {
return true;
}
} // namespace
PolicyMap::Entry::Entry() = default;
PolicyMap::Entry::Entry(
PolicyLevel level,
PolicyScope scope,
PolicySource source,
absl::optional<base::Value> value,
std::unique_ptr<ExternalDataFetcher> external_data_fetcher)
: level(level),
scope(scope),
source(source),
external_data_fetcher(std::move(external_data_fetcher)),
value_(std::move(value)) {}
PolicyMap::Entry::~Entry() = default;
PolicyMap::Entry::Entry(Entry&&) noexcept = default;
PolicyMap::Entry& PolicyMap::Entry::operator=(Entry&&) noexcept = default;
PolicyMap::Entry PolicyMap::Entry::DeepCopy() const {
Entry copy(level, scope, source,
value_ ? absl::make_optional<base::Value>(value_->Clone())
: absl::nullopt,
external_data_fetcher
? std::make_unique<ExternalDataFetcher>(*external_data_fetcher)
: nullptr);
copy.ignored_ = ignored_;
copy.message_ids_ = message_ids_;
copy.is_default_value_ = is_default_value_;
copy.conflicts.reserve(conflicts.size());
for (const auto& conflict : conflicts) {
copy.AddConflictingPolicy(conflict.entry().DeepCopy());
}
return copy;
}
const base::Value* PolicyMap::Entry::value(base::Value::Type value_type) const {
const base::Value* value = value_unsafe();
return value && value->type() == value_type ? value : nullptr;
}
base::Value* PolicyMap::Entry::value(base::Value::Type value_type) {
base::Value* value = value_unsafe();
return value && value->type() == value_type ? value : nullptr;
}
const base::Value* PolicyMap::Entry::value_unsafe() const {
return base::OptionalToPtr(value_);
}
base::Value* PolicyMap::Entry::value_unsafe() {
return base::OptionalToPtr(value_);
}
void PolicyMap::Entry::set_value(absl::optional<base::Value> val) {
value_ = std::move(val);
}
bool PolicyMap::Entry::Equals(const PolicyMap::Entry& other) const {
bool conflicts_are_equal = conflicts.size() == other.conflicts.size();
for (size_t i = 0; conflicts_are_equal && i < conflicts.size(); ++i)
conflicts_are_equal &=
conflicts[i].entry().Equals(other.conflicts[i].entry());
const bool equals =
conflicts_are_equal && level == other.level && scope == other.scope &&
source == other.source && // Necessary for PolicyUIHandler observers.
// They have to update when sources change.
message_ids_ == other.message_ids_ &&
is_default_value_ == other.is_default_value_ &&
((!value_ && !other.value_unsafe()) ||
(value_ && other.value_unsafe() && *value_ == *other.value_unsafe())) &&
ExternalDataFetcher::Equals(external_data_fetcher.get(),
other.external_data_fetcher.get());
return equals;
}
void PolicyMap::Entry::AddMessage(MessageType type, int message_id) {
message_ids_[type].emplace(message_id, absl::nullopt);
}
void PolicyMap::Entry::AddMessage(MessageType type,
int message_id,
std::vector<std::u16string>&& message_args) {
message_ids_[type].emplace(message_id, std::move(message_args));
}
void PolicyMap::Entry::ClearMessage(MessageType type, int message_id) {
if (message_ids_.find(type) == message_ids_.end() ||
message_ids_[type].find(message_id) == message_ids_[type].end()) {
return;
}
message_ids_[type].erase(message_id);
if (message_ids_[type].size() == 0)
message_ids_.erase(type);
}
void PolicyMap::Entry::AddConflictingPolicy(Entry&& conflict) {
// Move all of the newly conflicting Entry's conflicts into this Entry.
std::move(conflict.conflicts.begin(), conflict.conflicts.end(),
std::back_inserter(conflicts));
bool is_value_equal = (!this->value_unsafe() && !conflict.value_unsafe()) ||
(this->value_unsafe() && conflict.value_unsafe() &&
*this->value_unsafe() == *conflict.value_unsafe());
ConflictType type =
is_value_equal ? ConflictType::Supersede : ConflictType::Override;
// Clean up conflict Entry to ensure there's no duplication since entire Entry
// is moved and treated as a freshly constructed Entry.
conflict.ClearConflicts();
conflict.is_default_value_ = false;
conflict.message_ids_.clear();
// Avoid conflict nesting
conflicts.emplace_back(type, std::move(conflict));
}
void PolicyMap::Entry::ClearConflicts() {
conflicts.clear();
ClearMessage(MessageType::kInfo, IDS_POLICY_CONFLICT_SAME_VALUE);
ClearMessage(MessageType::kWarning, IDS_POLICY_CONFLICT_DIFF_VALUE);
}
bool PolicyMap::Entry::HasConflicts() {
return !conflicts.empty();
}
bool PolicyMap::Entry::HasMessage(MessageType type) const {
return message_ids_.find(type) != message_ids_.end();
}
std::u16string PolicyMap::Entry::GetLocalizedMessages(
MessageType type,
L10nLookupFunction lookup) const {
if (!HasMessage(type)) {
return std::u16string();
}
return GetLocalizedString(lookup, message_ids_.at(type));
}
bool PolicyMap::Entry::ignored() const {
return ignored_;
}
void PolicyMap::Entry::SetIgnored() {
ignored_ = true;
}
void PolicyMap::Entry::SetBlocked() {
SetIgnored();
AddMessage(MessageType::kError, IDS_POLICY_BLOCKED);
}
void PolicyMap::Entry::SetInvalid() {
SetIgnored();
AddMessage(MessageType::kError, IDS_POLICY_INVALID);
}
void PolicyMap::Entry::SetIgnoredByPolicyAtomicGroup() {
SetIgnored();
AddMessage(MessageType::kError, IDS_POLICY_IGNORED_BY_GROUP_MERGING);
}
bool PolicyMap::Entry::IsIgnoredByAtomicGroup() const {
return message_ids_.find(MessageType::kError) != message_ids_.end() &&
message_ids_.at(MessageType::kError)
.find(IDS_POLICY_IGNORED_BY_GROUP_MERGING) !=
message_ids_.at(MessageType::kError).end();
}
void PolicyMap::Entry::SetIsDefaultValue() {
is_default_value_ = true;
}
bool PolicyMap::Entry::IsDefaultValue() const {
return is_default_value_;
}
PolicyMap::EntryConflict::EntryConflict() = default;
PolicyMap::EntryConflict::EntryConflict(ConflictType type, Entry&& entry)
: conflict_type_(type), entry_(std::move(entry)) {}
PolicyMap::EntryConflict::~EntryConflict() = default;
PolicyMap::EntryConflict::EntryConflict(EntryConflict&&) noexcept = default;
PolicyMap::EntryConflict& PolicyMap::EntryConflict::operator=(
EntryConflict&&) noexcept = default;
void PolicyMap::EntryConflict::SetConflictType(ConflictType type) {
conflict_type_ = type;
}
PolicyMap::ConflictType PolicyMap::EntryConflict::conflict_type() const {
return conflict_type_;
}
const PolicyMap::Entry& PolicyMap::EntryConflict::entry() const {
return entry_;
}
PolicyMap::PolicyMap()
: details_callback_{base::BindRepeating(&GetChromePolicyDetails)} {}
PolicyMap::PolicyMap(PolicyMap&&) noexcept = default;
PolicyMap& PolicyMap::operator=(PolicyMap&&) noexcept = default;
PolicyMap::~PolicyMap() = default;
const PolicyMap::Entry* PolicyMap::Get(const std::string& policy) const {
auto entry = map_.find(policy);
return entry != map_.end() && !entry->second.ignored() ? &entry->second
: nullptr;
}
PolicyMap::Entry* PolicyMap::GetMutable(const std::string& policy) {
auto entry = map_.find(policy);
return entry != map_.end() && !entry->second.ignored() ? &entry->second
: nullptr;
}
const base::Value* PolicyMap::GetValue(const std::string& policy,
base::Value::Type value_type) const {
auto* entry = Get(policy);
return entry ? entry->value(value_type) : nullptr;
}
base::Value* PolicyMap::GetMutableValue(const std::string& policy,
base::Value::Type value_type) {
auto* entry = GetMutable(policy);
return entry ? entry->value(value_type) : nullptr;
}
const base::Value* PolicyMap::GetValueUnsafe(const std::string& policy) const {
auto* entry = Get(policy);
return entry ? entry->value_unsafe() : nullptr;
}
base::Value* PolicyMap::GetMutableValueUnsafe(const std::string& policy) {
auto* entry = GetMutable(policy);
return entry ? entry->value_unsafe() : nullptr;
}
const PolicyMap::Entry* PolicyMap::GetUntrusted(
const std::string& policy) const {
auto entry = map_.find(policy);
return entry != map_.end() ? &entry->second : nullptr;
}
PolicyMap::Entry* PolicyMap::GetMutableUntrusted(const std::string& policy) {
auto entry = map_.find(policy);
return entry != map_.end() ? &entry->second : nullptr;
}
bool PolicyMap::IsPolicySet(const std::string& policy) const {
return GetValueUnsafe(policy) != nullptr;
}
void PolicyMap::Set(
const std::string& policy,
PolicyLevel level,
PolicyScope scope,
PolicySource source,
absl::optional<base::Value> value,
std::unique_ptr<ExternalDataFetcher> external_data_fetcher) {
Entry entry(level, scope, source, std::move(value),
std::move(external_data_fetcher));
Set(policy, std::move(entry));
}
void PolicyMap::Set(const std::string& policy, Entry entry) {
map_[policy] = std::move(entry);
}
void PolicyMap::AddMessage(const std::string& policy,
MessageType type,
int message_id) {
map_[policy].AddMessage(type, message_id);
}
void PolicyMap::AddMessage(const std::string& policy,
MessageType type,
int message_id,
std::vector<std::u16string>&& message_args) {
map_[policy].AddMessage(type, message_id, std::move(message_args));
}
bool PolicyMap::IsPolicyIgnoredByAtomicGroup(const std::string& policy) const {
const auto& entry = map_.find(policy);
return entry != map_.end() && entry->second.IsIgnoredByAtomicGroup();
}
void PolicyMap::SetSourceForAll(PolicySource source) {
for (auto& it : map_) {
it.second.source = source;
}
}
void PolicyMap::SetAllInvalid() {
for (auto& it : map_) {
it.second.SetInvalid();
}
}
void PolicyMap::Erase(const std::string& policy) {
map_.erase(policy);
}
PolicyMap::iterator PolicyMap::EraseIt(const_iterator it) {
return map_.erase(it);
}
void PolicyMap::Swap(PolicyMap* other) {
map_.swap(other->map_);
}
PolicyMap PolicyMap::Clone() const {
return CloneIf(base::BindRepeating(&AllowAllPolicies));
}
PolicyMap PolicyMap::CloneIf(
const base::RepeatingCallback<bool(const_reference)>& filter) const {
PolicyMap clone;
for (const_reference it : map_) {
if (filter.Run(it)) {
clone.Set(it.first, it.second.DeepCopy());
}
}
clone.cloud_policy_overrides_platform_policy_ =
cloud_policy_overrides_platform_policy_;
clone.cloud_user_policy_overrides_cloud_machine_policy_ =
cloud_user_policy_overrides_cloud_machine_policy_;
clone.SetUserAffiliationIds(user_affiliation_ids_);
clone.SetDeviceAffiliationIds(device_affiliation_ids_);
return clone;
}
void PolicyMap::MergePolicy(const std::string& policy_name,
const PolicyMap& other,
bool using_default_precedence) {
const Entry* other_policy = other.GetUntrusted(policy_name);
if (!other_policy)
return;
Entry* policy = GetMutableUntrusted(policy_name);
Entry other_policy_copy = other_policy->DeepCopy();
if (!policy) {
Set(policy_name, std::move(other_policy_copy));
return;
}
#if BUILDFLAG(IS_CHROMEOS)
const bool other_is_higher_priority =
EntryHasHigherPriority(other_policy_copy, *policy);
#else // BUILDFLAG(IS_CHROMEOS)
const bool other_is_higher_priority = EntryHasHigherPriority(
other_policy_copy, *policy, using_default_precedence);
#endif // BUILDFLAG(IS_CHROMEOS)
Entry& higher_policy = other_is_higher_priority ? other_policy_copy : *policy;
Entry& conflicting_policy =
other_is_higher_priority ? *policy : other_policy_copy;
const bool overwriting_default_policy =
higher_policy.source != conflicting_policy.source &&
conflicting_policy.source == POLICY_SOURCE_ENTERPRISE_DEFAULT;
if (!overwriting_default_policy) {
policy->value_unsafe() &&
*other_policy_copy.value_unsafe() == *policy->value_unsafe()
? higher_policy.AddMessage(MessageType::kInfo,
IDS_POLICY_CONFLICT_SAME_VALUE)
: higher_policy.AddMessage(MessageType::kWarning,
IDS_POLICY_CONFLICT_DIFF_VALUE);
higher_policy.AddConflictingPolicy(std::move(conflicting_policy));
}
if (other_is_higher_priority)
*policy = std::move(other_policy_copy);
}
void PolicyMap::MergeFrom(const PolicyMap& other,
bool merge_precedence_metapolicies) {
DCHECK_NE(this, &other);
// Set affiliation IDs before merging policy values because user affiliation
// affects the policy precedence check.
SetUserAffiliationIds(
CombineIds(GetUserAffiliationIds(), other.GetUserAffiliationIds()));
SetDeviceAffiliationIds(
CombineIds(GetDeviceAffiliationIds(), other.GetDeviceAffiliationIds()));
#if !BUILDFLAG(IS_CHROMEOS)
if (merge_precedence_metapolicies) {
// Precedence metapolicies are merged before all other policies, including
// merging metapolicies, because their value affects policy overriding.
for (auto* policy : metapolicy::kPrecedence) {
// Default precedence is used during merging of precedence metapolicies to
// prevent circular dependencies.
MergePolicy(policy, other, true);
}
UpdateStoredComputedMetapolicies();
return;
}
#endif
for (const auto& policy_and_entry : other) {
#if !BUILDFLAG(IS_CHROMEOS)
// Skip precedence metapolicies since they have already been merged into the
// current PolicyMap.
if (base::Contains(metapolicy::kPrecedence, policy_and_entry.first)) {
continue;
}
#endif
// External factors, such as the values of metapolicies, are considered.
MergePolicy(policy_and_entry.first, other, false);
}
}
void PolicyMap::MergeValues(const std::vector<PolicyMerger*>& mergers) {
for (const auto* it : mergers)
it->Merge(this);
}
void PolicyMap::set_chrome_policy_details_callback_for_test(
const GetChromePolicyDetailsCallback& details_callback) {
details_callback_ = details_callback;
}
bool PolicyMap::IsPolicyExternal(const std::string& policy) {
const PolicyDetails* policy_details = details_callback_.Run(policy);
if (policy_details && policy_details->max_external_data_size > 0)
return true;
return false;
}
void PolicyMap::LoadFrom(const base::Value::Dict& policies,
PolicyLevel level,
PolicyScope scope,
PolicySource source) {
for (auto it : policies) {
if (IsPolicyExternal(it.first)) {
LOG_POLICY(WARNING, POLICY_PROCESSING)
<< "Ignoring external policy: " << it.first;
continue;
}
Set(it.first, level, scope, source, it.second.Clone(), nullptr);
}
}
bool PolicyMap::Equals(const PolicyMap& other) const {
return base::ranges::equal(*this, other, MapEntryEquals);
}
bool PolicyMap::empty() const {
return map_.empty();
}
size_t PolicyMap::size() const {
return map_.size();
}
PolicyMap::const_iterator PolicyMap::begin() const {
return map_.begin();
}
PolicyMap::const_iterator PolicyMap::end() const {
return map_.end();
}
PolicyMap::iterator PolicyMap::begin() {
return map_.begin();
}
PolicyMap::iterator PolicyMap::end() {
return map_.end();
}
void PolicyMap::Clear() {
map_.clear();
}
// static
bool PolicyMap::MapEntryEquals(const_reference a, const_reference b) {
bool equals = a.first == b.first && a.second.Equals(b.second);
return equals;
}
bool PolicyMap::EntryHasHigherPriority(const PolicyMap::Entry& lhs,
const PolicyMap::Entry& rhs) const {
return EntryHasHigherPriority(lhs, rhs, false);
}
bool PolicyMap::EntryHasHigherPriority(const PolicyMap::Entry& lhs,
const PolicyMap::Entry& rhs,
bool using_default_precedence) const {
#if BUILDFLAG(IS_CHROMEOS)
return std::tie(lhs.level, lhs.scope, lhs.source) >
std::tie(rhs.level, rhs.scope, rhs.source);
#else // BUILDFLAG(IS_CHROMEOS)
PolicyPriorityBrowser lhs_priority =
using_default_precedence
? GetPriority(lhs.source, lhs.scope, false, false, false)
: GetPriority(lhs.source, lhs.scope,
cloud_policy_overrides_platform_policy_,
cloud_user_policy_overrides_cloud_machine_policy_,
IsUserAffiliated());
PolicyPriorityBrowser rhs_priority =
using_default_precedence
? GetPriority(rhs.source, rhs.scope, false, false, false)
: GetPriority(rhs.source, rhs.scope,
cloud_policy_overrides_platform_policy_,
cloud_user_policy_overrides_cloud_machine_policy_,
IsUserAffiliated());
return std::tie(lhs.level, lhs_priority) > std::tie(rhs.level, rhs_priority);
#endif // BUILDFLAG(IS_CHROMEOS)
}
bool PolicyMap::IsUserAffiliated() const {
return is_user_affiliated_;
}
void PolicyMap::SetUserAffiliationIds(
const base::flat_set<std::string>& user_ids) {
user_affiliation_ids_ = {user_ids.begin(), user_ids.end()};
UpdateStoredUserAffiliation();
}
const base::flat_set<std::string>& PolicyMap::GetUserAffiliationIds() const {
return user_affiliation_ids_;
}
void PolicyMap::SetDeviceAffiliationIds(
const base::flat_set<std::string>& device_ids) {
device_affiliation_ids_ = {device_ids.begin(), device_ids.end()};
UpdateStoredUserAffiliation();
}
const base::flat_set<std::string>& PolicyMap::GetDeviceAffiliationIds() const {
return device_affiliation_ids_;
}
#if !BUILDFLAG(IS_CHROMEOS)
void PolicyMap::UpdateStoredComputedMetapolicies() {
cloud_policy_overrides_platform_policy_ =
GetValue(key::kCloudPolicyOverridesPlatformPolicy,
base::Value::Type::BOOLEAN) &&
GetValue(key::kCloudPolicyOverridesPlatformPolicy,
base::Value::Type::BOOLEAN)
->GetBool();
cloud_user_policy_overrides_cloud_machine_policy_ =
GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
base::Value::Type::BOOLEAN) &&
GetValue(key::kCloudUserPolicyOverridesCloudMachinePolicy,
base::Value::Type::BOOLEAN)
->GetBool();
}
#endif
void PolicyMap::UpdateStoredUserAffiliation() {
is_user_affiliated_ =
IsAffiliated(user_affiliation_ids_, device_affiliation_ids_);
}
} // namespace policy