blob: 0da72ab9f9fbbc070e11fdaf1f4015eb04c682db [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/network/onc/onc_merger.h"
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/values.h"
#include "chromeos/network/onc/onc_signature.h"
#include "chromeos/network/policy_util.h"
#include "components/onc/onc_constants.h"
namespace chromeos {
namespace onc {
namespace {
typedef std::unique_ptr<base::DictionaryValue> DictionaryPtr;
// Returns true if the field is the identifier of a configuration, i.e. the GUID
// of a network or a certificate.
bool IsIdentifierField(const OncValueSignature& value_signature,
const std::string& field_name) {
if (&value_signature == &kNetworkConfigurationSignature)
return field_name == ::onc::network_config::kGUID;
if (&value_signature == &kCertificateSignature)
return field_name == ::onc::certificate::kGUID;
return false;
}
// Identifier fields and other read-only fields (specifically Type) are
// handled specially during merging because they are always identical for the
// various setting sources.
bool IsReadOnlyField(const OncValueSignature& value_signature,
const std::string& field_name) {
if (IsIdentifierField(value_signature, field_name))
return true;
if (&value_signature == &kNetworkConfigurationSignature)
return field_name == ::onc::network_config::kType;
return false;
}
// Inserts |true| at every field name in |result| that is recommended in
// |policy|.
void MarkRecommendedFieldnames(const base::DictionaryValue& policy,
base::DictionaryValue* result) {
const base::ListValue* recommended_value = NULL;
if (!policy.GetListWithoutPathExpansion(::onc::kRecommended,
&recommended_value))
return;
for (base::ListValue::const_iterator it = recommended_value->begin();
it != recommended_value->end(); ++it) {
std::string entry;
if (it->GetAsString(&entry))
result->SetKey(entry, base::Value(true));
}
}
// Returns a dictionary which contains |true| at each path that is editable by
// the user. No other fields are set.
DictionaryPtr GetEditableFlags(const base::DictionaryValue& policy) {
DictionaryPtr result_editable(new base::DictionaryValue);
MarkRecommendedFieldnames(policy, result_editable.get());
// Recurse into nested dictionaries.
for (base::DictionaryValue::Iterator it(policy); !it.IsAtEnd();
it.Advance()) {
const base::DictionaryValue* child_policy = NULL;
if (it.key() == ::onc::kRecommended ||
!it.value().GetAsDictionary(&child_policy)) {
continue;
}
result_editable->SetWithoutPathExpansion(it.key(),
GetEditableFlags(*child_policy));
}
return result_editable;
}
// This is the base class for merging a list of DictionaryValues in
// parallel. See MergeDictionaries function.
class MergeListOfDictionaries {
public:
typedef std::vector<const base::DictionaryValue*> DictPtrs;
MergeListOfDictionaries() = default;
virtual ~MergeListOfDictionaries() = default;
// For each path in any of the dictionaries |dicts|, the function
// MergeListOfValues is called with the list of values that are located at
// that path in each of the dictionaries. This function returns a new
// dictionary containing all results of MergeListOfValues at the respective
// paths. The resulting dictionary doesn't contain empty dictionaries.
DictionaryPtr MergeDictionaries(const DictPtrs &dicts) {
DictionaryPtr result(new base::DictionaryValue);
std::set<std::string> visited;
for (DictPtrs::const_iterator it_outer = dicts.begin();
it_outer != dicts.end(); ++it_outer) {
if (!*it_outer)
continue;
for (base::DictionaryValue::Iterator field(**it_outer); !field.IsAtEnd();
field.Advance()) {
const std::string& key = field.key();
if (key == ::onc::kRecommended || !visited.insert(key).second)
continue;
std::unique_ptr<base::Value> merged_value;
if (field.value().is_dict()) {
DictPtrs nested_dicts;
for (DictPtrs::const_iterator it_inner = dicts.begin();
it_inner != dicts.end(); ++it_inner) {
const base::DictionaryValue* nested_dict = NULL;
if (*it_inner)
(*it_inner)->GetDictionaryWithoutPathExpansion(key, &nested_dict);
nested_dicts.push_back(nested_dict);
}
DictionaryPtr merged_dict(MergeNestedDictionaries(key, nested_dicts));
if (!merged_dict->empty())
merged_value = std::move(merged_dict);
} else {
std::vector<const base::Value*> values;
for (DictPtrs::const_iterator it_inner = dicts.begin();
it_inner != dicts.end(); ++it_inner) {
const base::Value* value = NULL;
if (*it_inner)
(*it_inner)->GetWithoutPathExpansion(key, &value);
values.push_back(value);
}
merged_value = MergeListOfValues(key, values);
}
if (merged_value)
result->SetWithoutPathExpansion(key, std::move(merged_value));
}
}
return result;
}
protected:
// This function is called by MergeDictionaries for each list of values that
// are located at the same path in each of the dictionaries. The order of the
// values is the same as of the given dictionaries |dicts|. If a dictionary
// doesn't contain a path then it's value is NULL.
virtual std::unique_ptr<base::Value> MergeListOfValues(
const std::string& key,
const std::vector<const base::Value*>& values) = 0;
virtual DictionaryPtr MergeNestedDictionaries(const std::string& key,
const DictPtrs &dicts) {
return MergeDictionaries(dicts);
}
private:
DISALLOW_COPY_AND_ASSIGN(MergeListOfDictionaries);
};
// This is the base class for merging policies and user settings.
class MergeSettingsAndPolicies : public MergeListOfDictionaries {
public:
struct ValueParams {
const base::Value* user_policy;
const base::Value* device_policy;
const base::Value* user_setting;
const base::Value* shared_setting;
const base::Value* active_setting;
bool user_editable;
bool device_editable;
};
MergeSettingsAndPolicies() = default;
// Merge the provided dictionaries. For each path in any of the dictionaries,
// MergeValues is called. Its results are collected in a new dictionary which
// is then returned. The resulting dictionary never contains empty
// dictionaries.
DictionaryPtr MergeDictionaries(
const base::DictionaryValue* user_policy,
const base::DictionaryValue* device_policy,
const base::DictionaryValue* user_settings,
const base::DictionaryValue* shared_settings,
const base::DictionaryValue* active_settings) {
hasUserPolicy_ = (user_policy != NULL);
hasDevicePolicy_ = (device_policy != NULL);
DictionaryPtr user_editable;
if (user_policy != NULL)
user_editable = GetEditableFlags(*user_policy);
DictionaryPtr device_editable;
if (device_policy != NULL)
device_editable = GetEditableFlags(*device_policy);
std::vector<const base::DictionaryValue*> dicts(kLastIndex, NULL);
dicts[kUserPolicyIndex] = user_policy;
dicts[kDevicePolicyIndex] = device_policy;
dicts[kUserSettingsIndex] = user_settings;
dicts[kSharedSettingsIndex] = shared_settings;
dicts[kActiveSettingsIndex] = active_settings;
dicts[kUserEditableIndex] = user_editable.get();
dicts[kDeviceEditableIndex] = device_editable.get();
return MergeListOfDictionaries::MergeDictionaries(dicts);
}
protected:
// This function is called by MergeDictionaries for each list of values that
// are located at the same path in each of the dictionaries. Implementations
// can use the Has*Policy functions.
virtual std::unique_ptr<base::Value> MergeValues(
const std::string& key,
const ValueParams& values) = 0;
// Whether a user policy was provided.
bool HasUserPolicy() {
return hasUserPolicy_;
}
// Whether a device policy was provided.
bool HasDevicePolicy() {
return hasDevicePolicy_;
}
// MergeListOfDictionaries override.
std::unique_ptr<base::Value> MergeListOfValues(
const std::string& key,
const std::vector<const base::Value*>& values) override {
bool user_editable = !HasUserPolicy();
if (values[kUserEditableIndex])
values[kUserEditableIndex]->GetAsBoolean(&user_editable);
bool device_editable = !HasDevicePolicy();
if (values[kDeviceEditableIndex])
values[kDeviceEditableIndex]->GetAsBoolean(&device_editable);
ValueParams params;
params.user_policy = values[kUserPolicyIndex];
params.device_policy = values[kDevicePolicyIndex];
params.user_setting = values[kUserSettingsIndex];
params.shared_setting = values[kSharedSettingsIndex];
params.active_setting = values[kActiveSettingsIndex];
params.user_editable = user_editable;
params.device_editable = device_editable;
return MergeValues(key, params);
}
private:
enum {
kUserPolicyIndex,
kDevicePolicyIndex,
kUserSettingsIndex,
kSharedSettingsIndex,
kActiveSettingsIndex,
kUserEditableIndex,
kDeviceEditableIndex,
kLastIndex
};
bool hasUserPolicy_, hasDevicePolicy_;
DISALLOW_COPY_AND_ASSIGN(MergeSettingsAndPolicies);
};
// Call MergeDictionaries to merge policies and settings to the effective
// values. This ignores the active settings of Shill. See the description of
// MergeSettingsAndPoliciesToEffective.
class MergeToEffective : public MergeSettingsAndPolicies {
public:
MergeToEffective() = default;
protected:
// Merges |values| to the effective value (Mandatory policy overwrites user
// settings overwrites shared settings overwrites recommended policy). |which|
// is set to the respective onc::kAugmentation* constant that indicates which
// source of settings is effective. Note that this function may return a NULL
// pointer and set |which| to ::onc::kAugmentationUserPolicy, which means that
// the
// user policy didn't set a value but also didn't recommend it, thus enforcing
// the empty value.
std::unique_ptr<base::Value> MergeValues(const std::string& key,
const ValueParams& values,
std::string* which) {
const base::Value* result = NULL;
which->clear();
if (!values.user_editable) {
result = values.user_policy;
*which = ::onc::kAugmentationUserPolicy;
} else if (!values.device_editable) {
result = values.device_policy;
*which = ::onc::kAugmentationDevicePolicy;
} else if (values.user_setting) {
result = values.user_setting;
*which = ::onc::kAugmentationUserSetting;
} else if (values.shared_setting) {
result = values.shared_setting;
*which = ::onc::kAugmentationSharedSetting;
} else if (values.user_policy) {
result = values.user_policy;
*which = ::onc::kAugmentationUserPolicy;
} else if (values.device_policy) {
result = values.device_policy;
*which = ::onc::kAugmentationDevicePolicy;
} else {
// Can be reached if the current field is recommended, but none of the
// dictionaries contained a value for it.
}
if (result)
return base::WrapUnique(result->DeepCopy());
return std::unique_ptr<base::Value>();
}
// MergeSettingsAndPolicies override.
std::unique_ptr<base::Value> MergeValues(const std::string& key,
const ValueParams& values) override {
std::string which;
return MergeValues(key, values, &which);
}
private:
DISALLOW_COPY_AND_ASSIGN(MergeToEffective);
};
namespace {
// Returns true if all not-null values in |values| are equal to |value|.
bool AllPresentValuesEqual(const MergeSettingsAndPolicies::ValueParams& values,
const base::Value& value) {
if (values.user_policy && !value.Equals(values.user_policy))
return false;
if (values.device_policy && !value.Equals(values.device_policy))
return false;
if (values.user_setting && !value.Equals(values.user_setting))
return false;
if (values.shared_setting && !value.Equals(values.shared_setting))
return false;
if (values.active_setting && !value.Equals(values.active_setting))
return false;
return true;
}
} // namespace
// Call MergeDictionaries to merge policies and settings to an augmented
// dictionary which contains a dictionary for each value in the original
// dictionaries. See the description of MergeSettingsAndPoliciesToAugmented.
class MergeToAugmented : public MergeToEffective {
public:
MergeToAugmented() = default;
DictionaryPtr MergeDictionaries(
const OncValueSignature& signature,
const base::DictionaryValue* user_policy,
const base::DictionaryValue* device_policy,
const base::DictionaryValue* user_settings,
const base::DictionaryValue* shared_settings,
const base::DictionaryValue* active_settings) {
signature_ = &signature;
return MergeToEffective::MergeDictionaries(user_policy,
device_policy,
user_settings,
shared_settings,
active_settings);
}
protected:
// MergeSettingsAndPolicies override.
std::unique_ptr<base::Value> MergeValues(const std::string& key,
const ValueParams& values) override {
const OncFieldSignature* field = NULL;
if (signature_)
field = GetFieldSignature(*signature_, key);
if (!field) {
// This field is not part of the provided ONCSignature, thus it cannot be
// controlled by policy. Return the plain active value instead of an
// augmented dictionary.
if (values.active_setting)
return base::WrapUnique(values.active_setting->DeepCopy());
return nullptr;
}
// This field is part of the provided ONCSignature, thus it can be
// controlled by policy.
std::string which_effective;
std::unique_ptr<base::Value> effective_value =
MergeToEffective::MergeValues(key, values, &which_effective);
if (IsReadOnlyField(*signature_, key)) {
// Don't augment read-only fields (GUID and Type).
if (effective_value) {
// DCHECK that all provided fields are identical.
DCHECK(AllPresentValuesEqual(values, *effective_value))
<< "Values do not match: " << key
<< " Effective: " << *effective_value;
// Return the un-augmented field.
return effective_value;
}
if (values.active_setting) {
// Unmanaged networks have assigned (active) values.
return base::WrapUnique(values.active_setting->DeepCopy());
}
LOG(ERROR) << "Field has no effective value: " << key;
return nullptr;
}
std::unique_ptr<base::DictionaryValue> augmented_value(
new base::DictionaryValue);
if (values.active_setting) {
augmented_value->SetKey(::onc::kAugmentationActiveSetting,
values.active_setting->Clone());
}
if (!which_effective.empty()) {
augmented_value->SetKey(::onc::kAugmentationEffectiveSetting,
base::Value(which_effective));
}
// Prevent credentials from being forwarded in cleartext to UI.
// User/shared credentials are not stored separately, so they cannot
// leak here.
// User and Shared settings are already replaced with |kFakeCredential|.
bool is_credential = onc::FieldIsCredential(*signature_, key);
if (is_credential) {
// Set |kFakeCredential| to notify UI that credential is saved.
if (values.user_policy) {
augmented_value->SetKey(
::onc::kAugmentationUserPolicy,
base::Value(chromeos::policy_util::kFakeCredential));
}
if (values.device_policy) {
augmented_value->SetKey(
::onc::kAugmentationDevicePolicy,
base::Value(chromeos::policy_util::kFakeCredential));
}
if (values.active_setting) {
augmented_value->SetKey(
::onc::kAugmentationActiveSetting,
base::Value(chromeos::policy_util::kFakeCredential));
}
} else {
if (values.user_policy) {
augmented_value->SetKey(::onc::kAugmentationUserPolicy,
values.user_policy->Clone());
}
if (values.device_policy) {
augmented_value->SetKey(::onc::kAugmentationDevicePolicy,
values.device_policy->Clone());
}
}
if (values.user_setting) {
augmented_value->SetKey(::onc::kAugmentationUserSetting,
values.user_setting->Clone());
}
if (values.shared_setting) {
augmented_value->SetKey(::onc::kAugmentationSharedSetting,
values.shared_setting->Clone());
}
if (HasUserPolicy() && values.user_editable) {
augmented_value->SetKey(::onc::kAugmentationUserEditable,
base::Value(true));
}
if (HasDevicePolicy() && values.device_editable) {
augmented_value->SetKey(::onc::kAugmentationDeviceEditable,
base::Value(true));
}
if (augmented_value->empty())
augmented_value.reset();
return std::move(augmented_value);
}
// MergeListOfDictionaries override.
DictionaryPtr MergeNestedDictionaries(const std::string& key,
const DictPtrs& dicts) override {
DictionaryPtr result;
if (signature_) {
const OncValueSignature* enclosing_signature = signature_;
signature_ = NULL;
const OncFieldSignature* field =
GetFieldSignature(*enclosing_signature, key);
if (field)
signature_ = field->value_signature;
result = MergeToEffective::MergeNestedDictionaries(key, dicts);
signature_ = enclosing_signature;
} else {
result = MergeToEffective::MergeNestedDictionaries(key, dicts);
}
return result;
}
private:
const OncValueSignature* signature_;
DISALLOW_COPY_AND_ASSIGN(MergeToAugmented);
};
} // namespace
DictionaryPtr MergeSettingsAndPoliciesToEffective(
const base::DictionaryValue* user_policy,
const base::DictionaryValue* device_policy,
const base::DictionaryValue* user_settings,
const base::DictionaryValue* shared_settings) {
MergeToEffective merger;
return merger.MergeDictionaries(
user_policy, device_policy, user_settings, shared_settings, NULL);
}
DictionaryPtr MergeSettingsAndPoliciesToAugmented(
const OncValueSignature& signature,
const base::DictionaryValue* user_policy,
const base::DictionaryValue* device_policy,
const base::DictionaryValue* user_settings,
const base::DictionaryValue* shared_settings,
const base::DictionaryValue* active_settings) {
MergeToAugmented merger;
return merger.MergeDictionaries(
signature, user_policy, device_policy, user_settings, shared_settings,
active_settings);
}
} // namespace onc
} // namespace chromeos