// 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 #include #include #include "base/basictypes.h" #include "base/logging.h" #include "base/values.h" #include "chromeos/network/onc/onc_signature.h" #include "components/onc/onc_constants.h" namespace chromeos { namespace onc { namespace { typedef scoped_ptr DictionaryPtr; // Returns true if the field is the identifier of a configuration, i.e. the GUID // of a network or a certificate. These can be special handled during merging // because they are always identical for the various setting sources. 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; } // 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->SetBooleanWithoutPathExpansion(entry, 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).release()); } return result_editable.Pass(); } // This is the base class for merging a list of DictionaryValues in // parallel. See MergeDictionaries function. class MergeListOfDictionaries { public: typedef std::vector DictPtrs; MergeListOfDictionaries() { } virtual ~MergeListOfDictionaries() { } // 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 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; scoped_ptr merged_value; if (field.value().IsType(base::Value::TYPE_DICTIONARY)) { 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 = merged_dict.Pass(); } else { std::vector 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, merged_value.release()); } } return result.Pass(); } 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 scoped_ptr MergeListOfValues( const std::string& key, const std::vector& 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() {} // 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 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 scoped_ptr 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. virtual scoped_ptr MergeListOfValues( const std::string& key, const std::vector& 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() {} 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. scoped_ptr 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 make_scoped_ptr(result->DeepCopy()); return scoped_ptr(); } // MergeSettingsAndPolicies override. virtual scoped_ptr 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() {} 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. virtual scoped_ptr 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. return make_scoped_ptr(values.active_setting->DeepCopy()); } // This field is part of the provided ONCSignature, thus it can be // controlled by policy. std::string which_effective; scoped_ptr effective_value = MergeToEffective::MergeValues(key, values, &which_effective); if (IsIdentifierField(*signature_, key)) { // Don't augment the GUID but write the plain value. if (!effective_value) { LOG(ERROR) << "GUID field has no effective value"; return nullptr; } // DCHECK that all provided GUIDs are identical. DCHECK(AllPresentValuesEqual(values, *effective_value)); // Return the un-augmented GUID. return effective_value.Pass(); } scoped_ptr augmented_value( new base::DictionaryValue); if (values.active_setting) { augmented_value->SetWithoutPathExpansion( ::onc::kAugmentationActiveSetting, values.active_setting->DeepCopy()); } if (!which_effective.empty()) { augmented_value->SetStringWithoutPathExpansion( ::onc::kAugmentationEffectiveSetting, which_effective); } // Prevent credentials from being forwarded in cleartext to // UI. User/shared credentials are not stored separately, so they cannot // leak here. bool is_credential = onc::FieldIsCredential(*signature_, key); if (!is_credential) { if (values.user_policy) { augmented_value->SetWithoutPathExpansion( ::onc::kAugmentationUserPolicy, values.user_policy->DeepCopy()); } if (values.device_policy) { augmented_value->SetWithoutPathExpansion( ::onc::kAugmentationDevicePolicy, values.device_policy->DeepCopy()); } } if (values.user_setting) { augmented_value->SetWithoutPathExpansion( ::onc::kAugmentationUserSetting, values.user_setting->DeepCopy()); } if (values.shared_setting) { augmented_value->SetWithoutPathExpansion( ::onc::kAugmentationSharedSetting, values.shared_setting->DeepCopy()); } if (HasUserPolicy() && values.user_editable) { augmented_value->SetBooleanWithoutPathExpansion( ::onc::kAugmentationUserEditable, true); } if (HasDevicePolicy() && values.device_editable) { augmented_value->SetBooleanWithoutPathExpansion( ::onc::kAugmentationDeviceEditable, true); } if (augmented_value->empty()) augmented_value.reset(); return augmented_value.Pass(); } // MergeListOfDictionaries override. virtual 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.Pass(); } 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