diff options
27 files changed, 671 insertions, 311 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 6c88948..d725063 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -5969,8 +5969,14 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_POLICY_VALUE_DEPRECATED" desc="The text displayed in the status column when a specific value for a policy is deprecated."> This value is deprecated for this policy. </message> - <message name="IDS_POLICY_NETWORK_CONFIG_PARSE_ERROR" desc="The text displayed in the status column when the corresponding network configuration policy failed to parse."> - Network configuration failed to be parsed: <ph name="PARSE_ERROR_MESSAGE">$1<ex>Line: 1, column: 1, Root value must be an array or object.</ex></ph> + <message name="IDS_POLICY_NETWORK_CONFIG_PARSE_FAILED" desc="The text displayed in the status column when the corresponding network configuration policy failed to parse."> + Network configuration failed to be parsed. + </message> + <message name="IDS_POLICY_NETWORK_CONFIG_IMPORT_PARTIAL" desc="The text displayed in the status column when the corresponding network configuration policy is not standard conform and was imported partially."> + The network configuration doesn't comply to the ONC standard. Parts of the configuration may not be imported. + </message> + <message name="IDS_POLICY_NETWORK_CONFIG_IMPORT_FAILED" desc="The text displayed in the status column when the corresponding network configuration policy is invalid."> + The network configuration is invalid and couldn't be imported. </message> <message name="IDS_POLICY_NOT_SET" desc="The text displayed in the status column when the corresponding policy has not been set by the administrator."> Not set. diff --git a/chrome/browser/chromeos/cros/network_library_impl_base.cc b/chrome/browser/chromeos/cros/network_library_impl_base.cc index c870d9c..fdce28c 100644 --- a/chrome/browser/chromeos/cros/network_library_impl_base.cc +++ b/chrome/browser/chromeos/cros/network_library_impl_base.cc @@ -1026,6 +1026,7 @@ bool NetworkLibraryImplBase::LoadOncNetworks(const std::string& onc_blob, const std::string& passphrase, onc::ONCSource source, bool allow_web_trust_from_policy) { + VLOG(2) << __func__ << ": called on " << onc_blob; NetworkProfile* profile = NULL; bool from_policy = (source == onc::ONC_SOURCE_USER_POLICY || source == onc::ONC_SOURCE_DEVICE_POLICY); @@ -1037,19 +1038,17 @@ bool NetworkLibraryImplBase::LoadOncNetworks(const std::string& onc_blob, if (from_policy) { profile = GetProfileForType(GetProfileTypeForSource(source)); if (profile == NULL) { - DLOG(WARNING) << "Profile for ONC source " - << onc::GetSourceAsString(source) - << " doesn't exist."; - return false; + VLOG(2) << "Profile for ONC source " << onc::GetSourceAsString(source) + << " doesn't exist."; + return true; } } - VLOG(2) << __func__ << ": called on " << onc_blob; scoped_ptr<base::DictionaryValue> root_dict = onc::ReadDictionaryFromJson(onc_blob); if (root_dict.get() == NULL) { - LOG(WARNING) << "ONC loaded from " << onc::GetSourceAsString(source) - << " is not a valid JSON dictionary."; + LOG(ERROR) << "ONC loaded from " << onc::GetSourceAsString(source) + << " is not a valid JSON dictionary."; return false; } @@ -1059,8 +1058,8 @@ bool NetworkLibraryImplBase::LoadOncNetworks(const std::string& onc_blob, if (onc_type == onc::kEncryptedConfiguration) { root_dict = onc::Decrypt(passphrase, *root_dict); if (root_dict.get() == NULL) { - LOG(WARNING) << "Couldn't decrypt the ONC from " - << onc::GetSourceAsString(source); + LOG(ERROR) << "Couldn't decrypt the ONC from " + << onc::GetSourceAsString(source); return false; } } @@ -1073,19 +1072,22 @@ bool NetworkLibraryImplBase::LoadOncNetworks(const std::string& onc_blob, from_policy); // Unknown fields are removed from the result. - scoped_ptr<base::DictionaryValue> validation_result = - validator.ValidateAndRepairObject( - &onc::kUnencryptedConfigurationSignature, - *root_dict); + onc::Validator::Result validation_result; + validator.ValidateAndRepairObject(&onc::kToplevelConfigurationSignature, + *root_dict, + &validation_result); if (from_policy) { UMA_HISTOGRAM_BOOLEAN("Enterprise.ONC.PolicyValidation", - validation_result.get() != NULL); + validation_result == onc::Validator::VALID); } - if (validation_result.get() == NULL) { - LOG(WARNING) << "ONC from source " << source - << " is invalid and couldn't be repaired."; + if (validation_result == onc::Validator::VALID_WITH_WARNINGS) { + LOG(WARNING) << "ONC from " << onc::GetSourceAsString(source) + << " produced warnings."; + } else if (validation_result == onc::Validator::INVALID) { + LOG(ERROR) << "ONC from " << onc::GetSourceAsString(source) + << " is invalid and couldn't be repaired."; } const base::ListValue* certificates; @@ -1097,19 +1099,14 @@ bool NetworkLibraryImplBase::LoadOncNetworks(const std::string& onc_blob, onc::kNetworkConfigurations, &network_configs); - // At least one of NetworkConfigurations or Certificates is required. - LOG_IF(WARNING, (!has_network_configurations && !has_certificates)) - << "ONC from source " << source - << " has neither NetworkConfigurations nor Certificates."; - if (has_certificates) { VLOG(2) << "ONC file has " << certificates->GetSize() << " certificates"; onc::CertificateImporter cert_importer(source, allow_web_trust_from_policy); if (cert_importer.ParseAndStoreCertificates(*certificates) != onc::CertificateImporter::IMPORT_OK) { - LOG(WARNING) << "Cannot parse some of the certificates in the ONC from " - << onc::GetSourceAsString(source); + LOG(ERROR) << "Cannot parse some of the certificates in the ONC from " + << onc::GetSourceAsString(source); return false; } } @@ -1129,8 +1126,8 @@ bool NetworkLibraryImplBase::LoadOncNetworks(const std::string& onc_blob, bool marked_for_removal = false; Network* network = parser.ParseNetwork(i, &marked_for_removal); if (!network) { - LOG(WARNING) << "Error during parsing network at index " << i - << " from ONC source " << onc::GetSourceAsString(source); + LOG(ERROR) << "Error during ONC parsing network at index " << i + << " from " << onc::GetSourceAsString(source); return false; } @@ -1201,8 +1198,8 @@ bool NetworkLibraryImplBase::LoadOncNetworks(const std::string& onc_blob, if (ethernet) { CallConfigureService(ethernet->unique_id(), &dict); } else { - DLOG(WARNING) << "Tried to import ONC with an Ethernet network when " - << "there is no active Ethernet connection."; + LOG(WARNING) << "Tried to import ONC with an Ethernet network when " + << "there is no active Ethernet connection."; } } else { CallConfigureService(network->unique_id(), &dict); diff --git a/chrome/browser/policy/configuration_policy_handler.cc b/chrome/browser/policy/configuration_policy_handler.cc index 2164762..68536d3 100644 --- a/chrome/browser/policy/configuration_policy_handler.cc +++ b/chrome/browser/policy/configuration_policy_handler.cc @@ -123,7 +123,7 @@ std::string ValueTypeToString(Value::Type type) { "dictionary", "list" }; - DCHECK(static_cast<size_t>(type) < arraysize(strings)); + CHECK(static_cast<size_t>(type) < arraysize(strings)); return std::string(strings[type]); } diff --git a/chrome/browser/policy/configuration_policy_handler_chromeos.cc b/chrome/browser/policy/configuration_policy_handler_chromeos.cc index 63a6874..8dffe13 100644 --- a/chrome/browser/policy/configuration_policy_handler_chromeos.cc +++ b/chrome/browser/policy/configuration_policy_handler_chromeos.cc @@ -52,7 +52,7 @@ bool NetworkConfigurationPolicyHandler::CheckPolicySettings( scoped_ptr<base::DictionaryValue> root_dict = onc::ReadDictionaryFromJson(onc_blob); if (root_dict.get() == NULL) { - errors->AddError(policy_name(), IDS_POLICY_NETWORK_CONFIG_PARSE_ERROR); + errors->AddError(policy_name(), IDS_POLICY_NETWORK_CONFIG_PARSE_FAILED); return false; } @@ -64,16 +64,18 @@ bool NetworkConfigurationPolicyHandler::CheckPolicySettings( true); // Validate for managed ONC // ONC policies are always unencrypted. + onc::Validator::Result validation_result; root_dict = validator.ValidateAndRepairObject( - &onc::kUnencryptedConfigurationSignature, - *root_dict); - - if (root_dict.get() == NULL) { - errors->AddError(policy_name(), IDS_POLICY_NETWORK_CONFIG_PARSE_ERROR); - // Don't reject the policy, as some networks or certificates could still - // be applied. - return true; + &onc::kToplevelConfigurationSignature, *root_dict, &validation_result); + if (validation_result == onc::Validator::VALID_WITH_WARNINGS) { + errors->AddError(policy_name(), + IDS_POLICY_NETWORK_CONFIG_IMPORT_PARTIAL); + } else if (validation_result == onc::Validator::INVALID) { + errors->AddError(policy_name(), IDS_POLICY_NETWORK_CONFIG_IMPORT_FAILED); } + + // In any case, don't reject the policy as some networks or certificates + // could still be applied. } return true; diff --git a/chrome/browser/policy/network_configuration_updater.cc b/chrome/browser/policy/network_configuration_updater.cc index dbb5faf..55c4502 100644 --- a/chrome/browser/policy/network_configuration_updater.cc +++ b/chrome/browser/policy/network_configuration_updater.cc @@ -11,13 +11,11 @@ #include "chrome/browser/chromeos/cros/network_library.h" #include "chrome/browser/policy/policy_map.h" #include "chromeos/network/onc/onc_constants.h" +#include "chromeos/network/onc/onc_utils.h" #include "policy/policy_constants.h" namespace policy { -const char NetworkConfigurationUpdater::kEmptyConfiguration[] = - "{\"NetworkConfigurations\":[],\"Certificates\":[]}"; - NetworkConfigurationUpdater::NetworkConfigurationUpdater( PolicyService* policy_service, chromeos::NetworkLibrary* network_library) @@ -96,7 +94,7 @@ void NetworkConfigurationUpdater::ApplyNetworkConfiguration( // An empty string is not a valid ONC and generates warnings and // errors. Replace by a valid empty configuration. if (new_network_config.empty()) - new_network_config = kEmptyConfiguration; + new_network_config = chromeos::onc::kEmptyUnencryptedConfiguration; network_library_->LoadOncNetworks(new_network_config, "", onc_source, allow_web_trust_); diff --git a/chrome/browser/policy/network_configuration_updater.h b/chrome/browser/policy/network_configuration_updater.h index 7bc265b..fcd46ebd 100644 --- a/chrome/browser/policy/network_configuration_updater.h +++ b/chrome/browser/policy/network_configuration_updater.h @@ -49,9 +49,6 @@ class NetworkConfigurationUpdater // request it. void set_allow_web_trust(bool allow) { allow_web_trust_ = allow; } - // Empty network configuration blob. - static const char kEmptyConfiguration[]; - private: // Callback that's called by |policy_service_| if the respective ONC policy // changed. diff --git a/chrome/browser/policy/network_configuration_updater_unittest.cc b/chrome/browser/policy/network_configuration_updater_unittest.cc index e1c120f..404c957 100644 --- a/chrome/browser/policy/network_configuration_updater_unittest.cc +++ b/chrome/browser/policy/network_configuration_updater_unittest.cc @@ -10,6 +10,7 @@ #include "chrome/browser/policy/policy_map.h" #include "chrome/browser/policy/policy_service_impl.h" #include "chromeos/network/onc/onc_constants.h" +#include "chromeos/network/onc/onc_utils.h" #include "policy/policy_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -23,8 +24,6 @@ using testing::_; namespace policy { static const char kFakeONC[] = "{ \"GUID\": \"1234\" }"; -static const char* kEmptyConfiguration = - NetworkConfigurationUpdater::kEmptyConfiguration; class NetworkConfigurationUpdaterTest : public testing::TestWithParam<const char*>{ @@ -68,7 +67,7 @@ TEST_P(NetworkConfigurationUpdaterTest, InitialUpdates) { // Initially, only the device policy is applied. The user policy is only // applied after the user profile was initialized. const char* device_onc = GetParam() == key::kDeviceOpenNetworkConfiguration ? - kFakeONC : kEmptyConfiguration; + kFakeONC : chromeos::onc::kEmptyUnencryptedConfiguration; EXPECT_CALL(network_library_, LoadOncNetworks( device_onc, "", chromeos::onc::ONC_SOURCE_DEVICE_POLICY, _)); @@ -82,7 +81,10 @@ TEST_P(NetworkConfigurationUpdaterTest, InitialUpdates) { EXPECT_CALL(network_library_, LoadOncNetworks( kFakeONC, "", NameToONCSource(GetParam()), _)); EXPECT_CALL(network_library_, LoadOncNetworks( - kEmptyConfiguration, "", Ne(NameToONCSource(GetParam())), _)); + chromeos::onc::kEmptyUnencryptedConfiguration, + "", + Ne(NameToONCSource(GetParam())), + _)); EXPECT_CALL(network_library_, RemoveNetworkProfileObserver(_)); @@ -138,7 +140,10 @@ TEST_P(NetworkConfigurationUpdaterTest, PolicyChange) { // In the current implementation, we always apply both policies. EXPECT_CALL(network_library_, LoadOncNetworks( - kEmptyConfiguration, "", Ne(NameToONCSource(GetParam())), _)); + chromeos::onc::kEmptyUnencryptedConfiguration, + "", + Ne(NameToONCSource(GetParam())), + _)); PolicyMap policy; policy.Set(GetParam(), POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, @@ -149,11 +154,11 @@ TEST_P(NetworkConfigurationUpdaterTest, PolicyChange) { // Another update is expected if the policy goes away. In the current // implementation, we always apply both policies. EXPECT_CALL(network_library_, LoadOncNetworks( - kEmptyConfiguration, "", + chromeos::onc::kEmptyUnencryptedConfiguration, "", chromeos::onc::ONC_SOURCE_DEVICE_POLICY, _)); EXPECT_CALL(network_library_, LoadOncNetworks( - kEmptyConfiguration, "", + chromeos::onc::kEmptyUnencryptedConfiguration, "", chromeos::onc::ONC_SOURCE_USER_POLICY, _)); EXPECT_CALL(network_library_, RemoveNetworkProfileObserver(_)); diff --git a/chrome/browser/ui/webui/net_internals/net_internals_ui.cc b/chrome/browser/ui/webui/net_internals/net_internals_ui.cc index 6c85194..c0e832d 100644 --- a/chrome/browser/ui/webui/net_internals/net_internals_ui.cc +++ b/chrome/browser/ui/webui/net_internals/net_internals_ui.cc @@ -1493,8 +1493,8 @@ void NetInternalsMessageHandler::OnImportONCFile(const ListValue* list) { chromeos::NetworkLibrary* cros_network = chromeos::CrosLibrary::Get()->GetNetworkLibrary(); if (!cros_network->LoadOncNetworks(onc_blob, passcode, - chromeos::onc::ONC_SOURCE_USER_IMPORT, - false)) { // allow web trust from policy + chromeos::onc::ONC_SOURCE_USER_IMPORT, + false)) { // allow web trust from policy LOG(ERROR) << "Unable to load ONC."; error = "Unable to load ONC configuration."; } diff --git a/chromeos/network/onc/onc_constants.cc b/chromeos/network/onc/onc_constants.cc index f77e991..3798d64 100644 --- a/chromeos/network/onc/onc_constants.cc +++ b/chromeos/network/onc/onc_constants.cc @@ -99,7 +99,6 @@ const char kPBKDF2[] = "PBKDF2"; const char kSHA1[] = "SHA1"; const char kSalt[] = "Salt"; const char kStretch[] = "Stretch"; -const char kType[] = "Type"; } // namespace encrypted namespace eap { diff --git a/chromeos/network/onc/onc_mapper.cc b/chromeos/network/onc/onc_mapper.cc index 8d5d723..768f0f6 100644 --- a/chromeos/network/onc/onc_mapper.cc +++ b/chromeos/network/onc/onc_mapper.cc @@ -17,28 +17,25 @@ Mapper::Mapper() { Mapper::~Mapper() { } -scoped_ptr<base::Value> Mapper::MapValue( - const OncValueSignature& signature, - const base::Value& onc_value) { +scoped_ptr<base::Value> Mapper::MapValue(const OncValueSignature& signature, + const base::Value& onc_value, + bool* error) { scoped_ptr<base::Value> result_value; switch (onc_value.GetType()) { case base::Value::TYPE_DICTIONARY: { const base::DictionaryValue* dict = NULL; onc_value.GetAsDictionary(&dict); - result_value = MapObject(signature, *dict); + result_value = MapObject(signature, *dict, error); break; } case base::Value::TYPE_LIST: { const base::ListValue* list = NULL; onc_value.GetAsList(&list); - bool nested_error_occured = false; - result_value = MapArray(signature, *list, &nested_error_occured); - if (nested_error_occured) - result_value.reset(); + result_value = MapArray(signature, *list, error); break; } default: { - result_value = MapPrimitive(signature, onc_value); + result_value = MapPrimitive(signature, onc_value, error); break; } } @@ -48,44 +45,44 @@ scoped_ptr<base::Value> Mapper::MapValue( scoped_ptr<base::DictionaryValue> Mapper::MapObject( const OncValueSignature& signature, - const base::DictionaryValue& onc_object) { + const base::DictionaryValue& onc_object, + bool* error) { scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue); bool found_unknown_field = false; - bool nested_error_occured = false; - MapFields(signature, onc_object, &found_unknown_field, &nested_error_occured, - result.get()); - if (!nested_error_occured && !found_unknown_field) - return result.Pass(); - else - return scoped_ptr<base::DictionaryValue>(); + MapFields(signature, onc_object, &found_unknown_field, error, result.get()); + if (found_unknown_field) + *error = true; + return result.Pass(); } -scoped_ptr<base::Value> Mapper::MapPrimitive( - const OncValueSignature& signature, - const base::Value& onc_primitive) { +scoped_ptr<base::Value> Mapper::MapPrimitive(const OncValueSignature& signature, + const base::Value& onc_primitive, + bool* error) { return make_scoped_ptr(onc_primitive.DeepCopy()); } -void Mapper::MapFields( - const OncValueSignature& object_signature, - const base::DictionaryValue& onc_object, - bool* found_unknown_field, - bool* nested_error_occured, - base::DictionaryValue* result) { +void Mapper::MapFields(const OncValueSignature& object_signature, + const base::DictionaryValue& onc_object, + bool* found_unknown_field, + bool* nested_error, + base::DictionaryValue* result) { for (base::DictionaryValue::Iterator it(onc_object); it.HasNext(); it.Advance()) { bool current_field_unknown = false; - scoped_ptr<base::Value> result_value = MapField( - it.key(), object_signature, it.value(), ¤t_field_unknown); + scoped_ptr<base::Value> result_value = MapField(it.key(), + object_signature, + it.value(), + ¤t_field_unknown, + nested_error); if (current_field_unknown) *found_unknown_field = true; else if (result_value.get() != NULL) result->SetWithoutPathExpansion(it.key(), result_value.release()); else - *nested_error_occured = true; + DCHECK(*nested_error); } } @@ -93,18 +90,16 @@ scoped_ptr<base::Value> Mapper::MapField( const std::string& field_name, const OncValueSignature& object_signature, const base::Value& onc_value, - bool* found_unknown_field) { + bool* found_unknown_field, + bool* error) { const OncFieldSignature* field_signature = GetFieldSignature(object_signature, field_name); if (field_signature != NULL) { - if (field_signature->value_signature == NULL) { - NOTREACHED() << "Found missing value signature at field '" - << field_name << "'."; - return scoped_ptr<base::Value>(); - } + DCHECK(field_signature->value_signature != NULL) + << "Found missing value signature at field '" << field_name << "'."; - return MapValue(*field_signature->value_signature, onc_value); + return MapValue(*field_signature->value_signature, onc_value, error); } else { DVLOG(1) << "Found unknown field name: '" << field_name << "'"; *found_unknown_field = true; @@ -115,26 +110,35 @@ scoped_ptr<base::Value> Mapper::MapField( scoped_ptr<base::ListValue> Mapper::MapArray( const OncValueSignature& array_signature, const base::ListValue& onc_array, - bool* nested_error_occured) { - if (array_signature.onc_array_entry_signature == NULL) { - NOTREACHED() << "Found missing onc_array_entry_signature."; - return scoped_ptr<base::ListValue>(); - } + bool* nested_error) { + DCHECK(array_signature.onc_array_entry_signature != NULL) + << "Found missing onc_array_entry_signature."; scoped_ptr<base::ListValue> result_array(new base::ListValue); + int original_index = 0; for (base::ListValue::const_iterator it = onc_array.begin(); - it != onc_array.end(); ++it) { + it != onc_array.end(); ++it, ++original_index) { const base::Value* entry = *it; scoped_ptr<base::Value> result_entry; - result_entry = MapValue(*array_signature.onc_array_entry_signature, *entry); + result_entry = MapEntry(original_index, + *array_signature.onc_array_entry_signature, + *entry, + nested_error); if (result_entry.get() != NULL) result_array->Append(result_entry.release()); else - *nested_error_occured = true; + DCHECK(*nested_error); } return result_array.Pass(); } +scoped_ptr<base::Value> Mapper::MapEntry(int index, + const OncValueSignature& signature, + const base::Value& onc_value, + bool* error) { + return MapValue(signature, onc_value, error); +} + } // namespace onc } // namespace chromeos diff --git a/chromeos/network/onc/onc_mapper.h b/chromeos/network/onc/onc_mapper.h index e8d42a9..142af70 100644 --- a/chromeos/network/onc/onc_mapper.h +++ b/chromeos/network/onc/onc_mapper.h @@ -11,9 +11,9 @@ #include "chromeos/chromeos_export.h" namespace base { -class Value; class DictionaryValue; class ListValue; +class Value; } namespace chromeos { @@ -41,58 +41,66 @@ class Mapper { protected: // Calls |MapObject|, |MapArray| and |MapPrimitive| according to |onc_value|'s - // type. By default aborts on nested errors in arrays. Result of the mapping - // is returned. On error returns NULL. - virtual scoped_ptr<base::Value> MapValue( - const OncValueSignature& signature, - const base::Value& onc_value); + // type, which always return an object of the according type. Result of the + // mapping is returned. On error sets |error| to true. + virtual scoped_ptr<base::Value> MapValue(const OncValueSignature& signature, + const base::Value& onc_value, + bool* error); // Maps objects/dictionaries. By default calls |MapFields|, which recurses - // into each field of |onc_object|, and aborts on unknown fields. Result of - // the mapping is returned. On error returns NULL. + // into each field of |onc_object|, and drops unknown fields. Result of the + // mapping is returned. On error sets |error| to true. In this implementation + // only unknown fields are errors. virtual scoped_ptr<base::DictionaryValue> MapObject( const OncValueSignature& signature, - const base::DictionaryValue& onc_object); + const base::DictionaryValue& onc_object, + bool* error); // Maps primitive values like BinaryValue, StringValue, IntegerValue... (all // but dictionaries and lists). By default copies |onc_primitive|. Result of - // the mapping is returned. On error returns NULL. + // the mapping is returned. On error sets |error| to true. virtual scoped_ptr<base::Value> MapPrimitive( - const OncValueSignature& signature, - const base::Value& onc_primitive); - - // Maps each field of the given |onc_object| according to - // |object_signature|. Adds the mapping of each field to |result| using - // |MapField| and drops unknown fields by default. Sets - // |found_unknown_field| to true if this dictionary contains any unknown - // fields. Set |nested_error_occured| to true if nested errors occured. - virtual void MapFields( - const OncValueSignature& object_signature, - const base::DictionaryValue& onc_object, - bool* found_unknown_field, - bool* nested_error_occured, - base::DictionaryValue* result); + const OncValueSignature& signature, + const base::Value& onc_primitive, + bool* error); + + // Maps each field of the given |onc_object| according to |object_signature|. + // Adds the mapping of each field to |result| using |MapField| and drops + // unknown fields by default. Sets |found_unknown_field| to true if this + // dictionary contains any unknown fields. Set |nested_error| to true if + // nested errors occured. + virtual void MapFields(const OncValueSignature& object_signature, + const base::DictionaryValue& onc_object, + bool* found_unknown_field, + bool* nested_error, + base::DictionaryValue* result); // Maps the value |onc_value| of field |field_name| according to its field // signature in |object_signature| using |MapValue|. Sets - // |found_unknown_field| to true if |field_name| cannot be found in - // |object_signature|, which by default is an error. Result of the mapping is - // returned. On error returns NULL. + // |found_unknown_field| to true and returns NULL if |field_name| cannot be + // found in |object_signature|. Otherwise returns the mapping of |onc_value|. virtual scoped_ptr<base::Value> MapField( const std::string& field_name, const OncValueSignature& object_signature, const base::Value& onc_value, - bool* found_unknown_field); + bool* found_unknown_field, + bool* error); // Maps the array |onc_array| according to |array_signature|, which defines // the type of the entries. Maps each entry by calling |MapValue|. If any of - // the nested mappings failed, the flag |nested_error_occured| is set to true - // and the entry is dropped from the result. The resulting array is - // returned. On error returns NULL. + // the nested mappings failed, the flag |nested_error| is set to true and the + // entry is dropped from the result. The resulting array is returned. virtual scoped_ptr<base::ListValue> MapArray( const OncValueSignature& array_signature, const base::ListValue& onc_array, - bool* nested_error_occured); + bool* nested_error); + + // Calls |MapValue| and returns its result. Called by |MapArray| for each + // entry and its index in the enclosing array. + virtual scoped_ptr<base::Value> MapEntry(int index, + const OncValueSignature& signature, + const base::Value& onc_value, + bool* error); private: DISALLOW_COPY_AND_ASSIGN(Mapper); diff --git a/chromeos/network/onc/onc_merger_unittest.cc b/chromeos/network/onc/onc_merger_unittest.cc index c7d5190..21f8287 100644 --- a/chromeos/network/onc/onc_merger_unittest.cc +++ b/chromeos/network/onc/onc_merger_unittest.cc @@ -54,9 +54,9 @@ class ONCMergerTest : public testing::Test { scoped_ptr<const base::DictionaryValue> device_policy_; virtual void SetUp() { - policy_ = test_utils::ReadTestDictionary("policy.onc"); + policy_ = test_utils::ReadTestDictionary("managed_vpn.onc"); policy_without_recommended_ = - test_utils::ReadTestDictionary("policy_without_recommended.onc"); + test_utils::ReadTestDictionary("managed_vpn_without_recommended.onc"); user_ = test_utils::ReadTestDictionary("user.onc"); device_policy_ = test_utils::ReadTestDictionary("device_policy.onc"); } diff --git a/chromeos/network/onc/onc_normalizer.cc b/chromeos/network/onc/onc_normalizer.cc index 4f99867..a1f1743 100644 --- a/chromeos/network/onc/onc_normalizer.cc +++ b/chromeos/network/onc/onc_normalizer.cc @@ -25,14 +25,19 @@ scoped_ptr<base::DictionaryValue> Normalizer::NormalizeObject( const OncValueSignature* object_signature, const base::DictionaryValue& onc_object) { CHECK(object_signature != NULL); - return MapObject(*object_signature, onc_object); + bool error = false; + scoped_ptr<base::DictionaryValue> result = + MapObject(*object_signature, onc_object, &error); + DCHECK(!error); + return result.Pass(); } scoped_ptr<base::DictionaryValue> Normalizer::MapObject( const OncValueSignature& signature, - const base::DictionaryValue& onc_object) { + const base::DictionaryValue& onc_object, + bool* error) { scoped_ptr<base::DictionaryValue> normalized = - Mapper::MapObject(signature, onc_object); + Mapper::MapObject(signature, onc_object, error); if (normalized.get() == NULL) return scoped_ptr<base::DictionaryValue>(); diff --git a/chromeos/network/onc/onc_normalizer.h b/chromeos/network/onc/onc_normalizer.h index 5d00f00..0b4468b 100644 --- a/chromeos/network/onc/onc_normalizer.h +++ b/chromeos/network/onc/onc_normalizer.h @@ -33,7 +33,8 @@ class CHROMEOS_EXPORT Normalizer : public Mapper { // Dispatch to the right normalization function according to |signature|. virtual scoped_ptr<base::DictionaryValue> MapObject( const OncValueSignature& signature, - const base::DictionaryValue& onc_object) OVERRIDE; + const base::DictionaryValue& onc_object, + bool* error) OVERRIDE; void NormalizeIPsec(base::DictionaryValue* ipsec); void NormalizeVPN(base::DictionaryValue* vpn); diff --git a/chromeos/network/onc/onc_signature.cc b/chromeos/network/onc/onc_signature.cc index f71328c..974df42 100644 --- a/chromeos/network/onc/onc_signature.cc +++ b/chromeos/network/onc/onc_signature.cc @@ -28,12 +28,6 @@ const OncValueSignature kStringListSignature = { const OncValueSignature kIPConfigListSignature = { Value::TYPE_LIST, NULL, &kIPConfigSignature }; -const OncValueSignature kCertificateListSignature = { - Value::TYPE_LIST, NULL, &kCertificateSignature -}; -const OncValueSignature kNetworkConfigurationListSignature = { - Value::TYPE_LIST, NULL, &kNetworkConfigurationSignature -}; const OncFieldSignature issuer_subject_pattern_fields[] = { { certificate::kCommonName, NULL, &kStringSignature }, @@ -236,10 +230,18 @@ const OncFieldSignature certificate_fields[] = { { NULL } }; -const OncFieldSignature unencrypted_configuration_fields[] = { +const OncFieldSignature toplevel_configuration_fields[] = { { kCertificates, NULL, &kCertificateListSignature }, { kNetworkConfigurations, NULL, &kNetworkConfigurationListSignature }, { kType, NULL, &kStringSignature }, + { encrypted::kCipher, NULL, &kStringSignature }, + { encrypted::kCiphertext, NULL, &kStringSignature }, + { encrypted::kHMAC, NULL, &kStringSignature }, + { encrypted::kHMACMethod, NULL, &kStringSignature }, + { encrypted::kIV, NULL, &kStringSignature }, + { encrypted::kIterations, NULL, &kIntegerSignature }, + { encrypted::kSalt, NULL, &kStringSignature }, + { encrypted::kStretch, NULL, &kStringSignature }, { NULL } }; @@ -293,8 +295,14 @@ const OncValueSignature kCertificateSignature = { const OncValueSignature kNetworkConfigurationSignature = { Value::TYPE_DICTIONARY, network_configuration_fields, NULL }; -const OncValueSignature kUnencryptedConfigurationSignature = { - Value::TYPE_DICTIONARY, unencrypted_configuration_fields, NULL +const OncValueSignature kCertificateListSignature = { + Value::TYPE_LIST, NULL, &kCertificateSignature +}; +const OncValueSignature kNetworkConfigurationListSignature = { + Value::TYPE_LIST, NULL, &kNetworkConfigurationSignature +}; +const OncValueSignature kToplevelConfigurationSignature = { + Value::TYPE_DICTIONARY, toplevel_configuration_fields, NULL }; const OncFieldSignature* GetFieldSignature(const OncValueSignature& signature, diff --git a/chromeos/network/onc/onc_signature.h b/chromeos/network/onc/onc_signature.h index 1a9b507..7ef17c0 100644 --- a/chromeos/network/onc/onc_signature.h +++ b/chromeos/network/onc/onc_signature.h @@ -46,8 +46,10 @@ CHROMEOS_EXPORT extern const OncValueSignature kProxySettingsSignature; CHROMEOS_EXPORT extern const OncValueSignature kWiFiSignature; CHROMEOS_EXPORT extern const OncValueSignature kCertificateSignature; CHROMEOS_EXPORT extern const OncValueSignature kNetworkConfigurationSignature; +CHROMEOS_EXPORT extern const OncValueSignature kCertificateListSignature; CHROMEOS_EXPORT extern const OncValueSignature - kUnencryptedConfigurationSignature; + kNetworkConfigurationListSignature; +CHROMEOS_EXPORT extern const OncValueSignature kToplevelConfigurationSignature; } // namespace onc } // namespace chromeos diff --git a/chromeos/network/onc/onc_translator_unittest.cc b/chromeos/network/onc/onc_translator_unittest.cc index a9b8fb5..da9e658 100644 --- a/chromeos/network/onc/onc_translator_unittest.cc +++ b/chromeos/network/onc/onc_translator_unittest.cc @@ -41,7 +41,7 @@ INSTANTIATE_TEST_CASE_P( ONCTranslatorOncToShillTest, ONCTranslatorOncToShillTest, ::testing::Values( - std::make_pair("valid.onc", "shill_ethernet.json"), + std::make_pair("managed_ethernet.onc", "shill_ethernet.json"), std::make_pair("valid_l2tpipsec.onc", "shill_l2tpipsec.json"), std::make_pair("valid_openvpn.onc", "shill_openvpn.json"))); diff --git a/chromeos/network/onc/onc_utils.cc b/chromeos/network/onc/onc_utils.cc index 62e0d46..85cfe64 100644 --- a/chromeos/network/onc/onc_utils.cc +++ b/chromeos/network/onc/onc_utils.cc @@ -27,6 +27,10 @@ const char kUnableToDecode[] = "Unable to decode encrypted ONC"; } // namespace +const char kEmptyUnencryptedConfiguration[] = + "{\"Type\":\"UnencryptedConfiguration\",\"NetworkConfigurations\":[]," + "\"Certificates\":[]}"; + scoped_ptr<base::DictionaryValue> ReadDictionaryFromJson( const std::string& json) { std::string error; @@ -64,7 +68,7 @@ scoped_ptr<base::DictionaryValue> Decrypt(const std::string& passphrase, !root.GetInteger(encrypted::kIterations, &iterations) || !root.GetString(encrypted::kSalt, &salt) || !root.GetString(encrypted::kStretch, &stretch_method) || - !root.GetString(encrypted::kType, &onc_type) || + !root.GetString(kType, &onc_type) || onc_type != kEncryptedConfiguration) { ONC_LOG_ERROR("Encrypted ONC malformed."); diff --git a/chromeos/network/onc/onc_utils.h b/chromeos/network/onc/onc_utils.h index 96b8a1e..13263a8 100644 --- a/chromeos/network/onc/onc_utils.h +++ b/chromeos/network/onc/onc_utils.h @@ -18,6 +18,10 @@ class DictionaryValue; namespace chromeos { namespace onc { +// A valid but empty (no networks and no certificates) and unencrypted +// configuration. +CHROMEOS_EXPORT extern const char kEmptyUnencryptedConfiguration[]; + // Parses |json| according to the JSON format. If |json| is a JSON formatted // dictionary, the function returns the dictionary as a DictionaryValue. // Otherwise returns NULL. diff --git a/chromeos/network/onc/onc_validator.cc b/chromeos/network/onc/onc_validator.cc index 684eca3..a661d51 100644 --- a/chromeos/network/onc/onc_validator.cc +++ b/chromeos/network/onc/onc_validator.cc @@ -7,7 +7,9 @@ #include <algorithm> #include <string> +#include "base/json/json_writer.h" #include "base/logging.h" +#include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/values.h" #include "chromeos/network/onc/onc_constants.h" @@ -16,6 +18,33 @@ namespace chromeos { namespace onc { +namespace { + +std::string ValueToString(const base::Value& value) { + std::string json; + base::JSONWriter::Write(&value, &json); + return json; +} + +// Copied from policy/configuration_policy_handler.cc. +// TODO(pneubeck): move to a common place like base/. +std::string ValueTypeToString(Value::Type type) { + static const char* strings[] = { + "null", + "boolean", + "integer", + "double", + "string", + "binary", + "dictionary", + "list" + }; + CHECK(static_cast<size_t>(type) < arraysize(strings)); + return strings[type]; +} + +} // namespace + Validator::Validator( bool error_on_unknown_field, bool error_on_wrong_recommended, @@ -32,10 +61,23 @@ Validator::~Validator() { scoped_ptr<base::DictionaryValue> Validator::ValidateAndRepairObject( const OncValueSignature* object_signature, - const base::DictionaryValue& onc_object) { + const base::DictionaryValue& onc_object, + Result* result) { CHECK(object_signature != NULL); + *result = VALID; + error_or_warning_found_ = false; + bool error = false; scoped_ptr<base::Value> result_value = - MapValue(*object_signature, onc_object); + MapValue(*object_signature, onc_object, &error); + if (error) { + *result = INVALID; + result_value.reset(); + } else if (error_or_warning_found_) { + *result = VALID_WITH_WARNINGS; + } + // The return value should be NULL if, and only if, |result| equals INVALID. + DCHECK_EQ(result_value.get() == NULL, *result == INVALID); + base::DictionaryValue* result_dict = NULL; if (result_value.get() != NULL) { result_value.release()->GetAsDictionary(&result_dict); @@ -47,14 +89,19 @@ scoped_ptr<base::DictionaryValue> Validator::ValidateAndRepairObject( scoped_ptr<base::Value> Validator::MapValue( const OncValueSignature& signature, - const base::Value& onc_value) { + const base::Value& onc_value, + bool* error) { if (onc_value.GetType() != signature.onc_type) { - DVLOG(1) << "Wrong type. Expected " << signature.onc_type - << ", but found " << onc_value.GetType(); + LOG(ERROR) << ErrorHeader() << "Found value '" << onc_value + << "' of type '" << ValueTypeToString(onc_value.GetType()) + << "', but type '" << ValueTypeToString(signature.onc_type) + << "' is required."; + error_or_warning_found_ = *error = true; return scoped_ptr<base::Value>(); } - scoped_ptr<base::Value> repaired = Mapper::MapValue(signature, onc_value); + scoped_ptr<base::Value> repaired = + Mapper::MapValue(signature, onc_value, error); if (repaired.get() != NULL) CHECK_EQ(repaired->GetType(), signature.onc_type); return repaired.Pass(); @@ -62,11 +109,14 @@ scoped_ptr<base::Value> Validator::MapValue( scoped_ptr<base::DictionaryValue> Validator::MapObject( const OncValueSignature& signature, - const base::DictionaryValue& onc_object) { + const base::DictionaryValue& onc_object, + bool* error) { scoped_ptr<base::DictionaryValue> repaired(new base::DictionaryValue); bool valid; - if (&signature == &kNetworkConfigurationSignature) + if (&signature == &kToplevelConfigurationSignature) + valid = ValidateToplevelConfiguration(onc_object, repaired.get()); + else if (&signature == &kNetworkConfigurationSignature) valid = ValidateNetworkConfiguration(onc_object, repaired.get()); else if (&signature == &kEthernetSignature) valid = ValidateEthernet(onc_object, repaired.get()); @@ -93,10 +143,52 @@ scoped_ptr<base::DictionaryValue> Validator::MapObject( else valid = ValidateObjectDefault(signature, onc_object, repaired.get()); - if (valid) + if (valid) { return repaired.Pass(); - else + } else { + error_or_warning_found_ = *error = true; return scoped_ptr<base::DictionaryValue>(); + } +} + +scoped_ptr<base::Value> Validator::MapField( + const std::string& field_name, + const OncValueSignature& object_signature, + const base::Value& onc_value, + bool* found_unknown_field, + bool* error) { + path_.push_back(field_name); + bool current_field_unknown = false; + scoped_ptr<base::Value> result = Mapper::MapField( + field_name, object_signature, onc_value, ¤t_field_unknown, error); + + DCHECK_EQ(field_name, path_.back()); + path_.pop_back(); + + if (current_field_unknown) { + error_or_warning_found_ = *found_unknown_field = true; + std::string message = MessageHeader(error_on_unknown_field_) + + "Field name '" + field_name + "' is unknown."; + if (error_on_unknown_field_) + LOG(ERROR) << message; + else + LOG(WARNING) << message; + } + + return result.Pass(); +} + +scoped_ptr<base::Value> Validator::MapEntry(int index, + const OncValueSignature& signature, + const base::Value& onc_value, + bool* error) { + std::string str = base::IntToString(index); + path_.push_back(str); + scoped_ptr<base::Value> result = + Mapper::MapEntry(index, signature, onc_value, error); + DCHECK_EQ(str, path_.back()); + path_.pop_back(); + return result.Pass(); } bool Validator::ValidateObjectDefault( @@ -107,17 +199,15 @@ bool Validator::ValidateObjectDefault( bool nested_error_occured = false; MapFields(signature, onc_object, &found_unknown_field, &nested_error_occured, result); - if (nested_error_occured) - return false; - if (found_unknown_field) { - if (error_on_unknown_field_) { - DVLOG(1) << "Unknown field name. Aborting."; - return false; - } - DVLOG(1) << "Unknown field name. Ignoring."; + if (found_unknown_field && error_on_unknown_field_) { + DVLOG(1) << "Unknown field names are errors: Aborting."; + return false; } + if (nested_error_occured) + return false; + return ValidateRecommendedField(signature, result); } @@ -140,8 +230,8 @@ bool Validator::ValidateRecommendedField( recommended.reset(recommended_list); if (!managed_onc_) { - DVLOG(1) << "Found a " << onc::kRecommended - << " field in unmanaged ONC. Removing it."; + LOG(WARNING) << WarningHeader() << "Found the field '" << onc::kRecommended + << "' in an unmanaged ONC. Removing it."; return true; } @@ -169,13 +259,19 @@ bool Validator::ValidateRecommendedField( } if (found_error) { - DVLOG(1) << "Found " << error_cause << " field name '" << field_name - << "' in kRecommended array. " - << (error_on_wrong_recommended_ ? "Aborting." : "Ignoring."); - if (error_on_wrong_recommended_) + error_or_warning_found_ = true; + path_.push_back(onc::kRecommended); + std::string message = MessageHeader(error_on_wrong_recommended_) + + "The " + error_cause + " field '" + field_name + + "' cannot be recommended."; + path_.pop_back(); + if (error_on_wrong_recommended_) { + LOG(ERROR) << message; return false; - else + } else { + LOG(WARNING) << message; continue; + } } repaired_recommended->Append((*it)->DeepCopy()); @@ -195,33 +291,94 @@ std::string JoinStringRange(const char** range_begin, return JoinString(string_vector, separator); } -bool RequireAnyOf(const std::string &actual, const char** valid_values) { +} // namespace + +bool Validator::FieldExistsAndHasNoValidValue( + const base::DictionaryValue& object, + const std::string &field_name, + const char** valid_values) { + std::string actual_value; + if (!object.GetStringWithoutPathExpansion(field_name, &actual_value)) + return false; + const char** it = valid_values; for (; *it != NULL; ++it) { - if (actual == *it) - return true; + if (actual_value == *it) + return false; } - DVLOG(1) << "Found " << actual << ", but expected one of " - << JoinStringRange(valid_values, it, ", "); - return false; + error_or_warning_found_ = true; + std::string valid_values_str = + "[" + JoinStringRange(valid_values, it, ", ") + "]"; + path_.push_back(field_name); + LOG(ERROR) << ErrorHeader() << "Found value '" << actual_value << + "', but expected one of the values " << valid_values_str; + path_.pop_back(); + return true; } -bool IsInRange(int actual, int lower_bound, int upper_bound) { - if (lower_bound <= actual && actual <= upper_bound) - return true; - DVLOG(1) << "Found " << actual << ", which is out of range [" << lower_bound - << ", " << upper_bound << "]"; - return false; +bool Validator::FieldExistsAndIsNotInRange(const base::DictionaryValue& object, + const std::string &field_name, + int lower_bound, + int upper_bound) { + int actual_value; + if (!object.GetIntegerWithoutPathExpansion(field_name, &actual_value) || + (lower_bound <= actual_value && actual_value <= upper_bound)) { + return false; + } + error_or_warning_found_ = true; + path_.push_back(field_name); + LOG(ERROR) << ErrorHeader() << "Found value '" << actual_value + << "', but expected a value in the range [" << lower_bound + << ", " << upper_bound << "] (boundaries inclusive)"; + path_.pop_back(); + return true; } -bool RequireField(const base::DictionaryValue& dict, std::string key) { - if (dict.HasKey(key)) +bool Validator::RequireField(const base::DictionaryValue& dict, + const std::string& field_name) { + if (dict.HasKey(field_name)) return true; - DVLOG(1) << "Required field " << key << " missing."; + error_or_warning_found_ = true; + LOG(ERROR) << ErrorHeader() << "The required field '" << field_name + << "' is missing."; return false; } -} // namespace +bool Validator::ValidateToplevelConfiguration( + const base::DictionaryValue& onc_object, + base::DictionaryValue* result) { + if (!ValidateObjectDefault(kToplevelConfigurationSignature, + onc_object, result)) { + return false; + } + + static const char* kValidTypes[] = + { kUnencryptedConfiguration, kEncryptedConfiguration, NULL }; + if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes)) + return false; + + bool allRequiredExist = true; + + // Not part of the ONC spec yet: + // We don't require the type field and default to UnencryptedConfiguration. + std::string type = kUnencryptedConfiguration; + result->GetStringWithoutPathExpansion(kType, &type); + if (type == kUnencryptedConfiguration && + !result->HasKey(kNetworkConfigurations) && + !result->HasKey(kCertificates)) { + error_or_warning_found_ = true; + std::string message = MessageHeader(error_on_missing_field_) + + "Neither the field '" + kNetworkConfigurations + "' nor '" + + kCertificates + "is present, but at least one is required."; + if (error_on_missing_field_) + LOG(ERROR) << message; + else + LOG(WARNING) << message; + allRequiredExist = false; + } + + return !error_on_missing_field_ || allRequiredExist; +} bool Validator::ValidateNetworkConfiguration( const base::DictionaryValue& onc_object, @@ -231,12 +388,9 @@ bool Validator::ValidateNetworkConfiguration( return false; } - std::string type; static const char* kValidTypes[] = { kEthernet, kVPN, kWiFi, NULL }; - if (result->GetStringWithoutPathExpansion(kType, &type) && - !RequireAnyOf(type, kValidTypes)) { + if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes)) return false; - } bool allRequiredExist = RequireField(*result, kGUID); @@ -245,6 +399,9 @@ bool Validator::ValidateNetworkConfiguration( if (!remove) { allRequiredExist &= RequireField(*result, kName); allRequiredExist &= RequireField(*result, kType); + + std::string type; + result->GetStringWithoutPathExpansion(kType, &type); allRequiredExist &= type.empty() || RequireField(*result, type); } @@ -258,14 +415,15 @@ bool Validator::ValidateEthernet( if (!ValidateObjectDefault(kEthernetSignature, onc_object, result)) return false; - std::string auth; static const char* kValidAuthentications[] = { kNone, k8021X, NULL }; - if (result->GetStringWithoutPathExpansion(kAuthentication, &auth) && - !RequireAnyOf(auth, kValidAuthentications)) { + if (FieldExistsAndHasNoValidValue(*result, kAuthentication, + kValidAuthentications)) { return false; } bool allRequiredExist = true; + std::string auth; + result->GetStringWithoutPathExpansion(kAuthentication, &auth); if (auth == k8021X) allRequiredExist &= RequireField(*result, kEAP); @@ -279,19 +437,17 @@ bool Validator::ValidateIPConfig( if (!ValidateObjectDefault(kIPConfigSignature, onc_object, result)) return false; - std::string type; static const char* kValidTypes[] = { kIPv4, kIPv6, NULL }; - if (result->GetStringWithoutPathExpansion(ipconfig::kType, &type) && - !RequireAnyOf(type, kValidTypes)) { + if (FieldExistsAndHasNoValidValue(*result, ipconfig::kType, kValidTypes)) return false; - } - int routing_prefix; + std::string type; + result->GetStringWithoutPathExpansion(ipconfig::kType, &type); int lower_bound = 1; // In case of missing type, choose higher upper_bound. int upper_bound = (type == kIPv4) ? 32 : 128; - if (result->GetIntegerWithoutPathExpansion(kRoutingPrefix, &routing_prefix) && - !IsInRange(routing_prefix, lower_bound, upper_bound)) { + if (FieldExistsAndIsNotInRange(*result, kRoutingPrefix, + lower_bound, upper_bound)) { return false; } @@ -309,16 +465,16 @@ bool Validator::ValidateWiFi( if (!ValidateObjectDefault(kWiFiSignature, onc_object, result)) return false; - std::string security; static const char* kValidSecurities[] = { kNone, kWEP_PSK, kWEP_8021X, kWPA_PSK, kWPA_EAP, NULL }; - if (result->GetStringWithoutPathExpansion(kSecurity, &security) && - !RequireAnyOf(security, kValidSecurities)) { + if (FieldExistsAndHasNoValidValue(*result, kSecurity, kValidSecurities)) return false; - } bool allRequiredExist = RequireField(*result, kSecurity) & RequireField(*result, kSSID); + + std::string security; + result->GetStringWithoutPathExpansion(kSecurity, &security); if (security == kWEP_8021X || security == kWPA_EAP) allRequiredExist &= RequireField(*result, kEAP); else if (security == kWEP_PSK || security == kWPA_PSK) @@ -334,16 +490,14 @@ bool Validator::ValidateVPN( if (!ValidateObjectDefault(kVPNSignature, onc_object, result)) return false; - std::string type; static const char* kValidTypes[] = { kIPsec, kTypeL2TP_IPsec, kOpenVPN, NULL }; - if (result->GetStringWithoutPathExpansion(vpn::kType, &type) && - !RequireAnyOf(type, kValidTypes)) { + if (FieldExistsAndHasNoValidValue(*result, vpn::kType, kValidTypes)) return false; - } bool allRequiredExist = RequireField(*result, vpn::kType); - + std::string type; + result->GetStringWithoutPathExpansion(vpn::kType, &type); if (type == kOpenVPN) { allRequiredExist &= RequireField(*result, kOpenVPN); } else if (type == kIPsec) { @@ -364,26 +518,26 @@ bool Validator::ValidateIPsec( if (!ValidateObjectDefault(kIPsecSignature, onc_object, result)) return false; - std::string auth; static const char* kValidAuthentications[] = { kPSK, kCert, NULL }; - if (result->GetStringWithoutPathExpansion(kAuthenticationType, &auth) && - !RequireAnyOf(auth, kValidAuthentications)) { - return false; - } - - std::string cert_type; static const char* kValidCertTypes[] = { kRef, kPattern, NULL }; - if (result->GetStringWithoutPathExpansion(kClientCertType, &cert_type) && - !RequireAnyOf(cert_type, kValidCertTypes)) { + // Using strict bit-wise OR to check all conditions. + if (FieldExistsAndHasNoValidValue(*result, kAuthenticationType, + kValidAuthentications) | + FieldExistsAndHasNoValidValue(*result, kClientCertType, + kValidCertTypes)) { return false; } bool allRequiredExist = RequireField(*result, kAuthenticationType) & RequireField(*result, kIKEVersion); + std::string auth; + result->GetStringWithoutPathExpansion(kAuthenticationType, &auth); if (auth == kCert) { allRequiredExist &= RequireField(*result, kClientCertType) & RequireField(*result, kServerCARef); } + std::string cert_type; + result->GetStringWithoutPathExpansion(kClientCertType, &cert_type); if (cert_type == kPattern) allRequiredExist &= RequireField(*result, kClientCertPattern); else if (cert_type == kRef) @@ -401,31 +555,25 @@ bool Validator::ValidateOpenVPN( if (!ValidateObjectDefault(kOpenVPNSignature, onc_object, result)) return false; - std::string auth_retry; static const char* kValidAuthRetryValues[] = { openvpn::kNone, kInteract, kNoInteract, NULL }; - if (result->GetStringWithoutPathExpansion(kAuthRetry, &auth_retry) && - !RequireAnyOf(auth_retry, kValidAuthRetryValues)) { - return false; - } - - std::string cert_type; static const char* kValidCertTypes[] = { certificate::kNone, kRef, kPattern, NULL }; - if (result->GetStringWithoutPathExpansion(kClientCertType, &cert_type) && - !RequireAnyOf(cert_type, kValidCertTypes)) { - return false; - } - - std::string cert_tls; static const char* kValidCertTlsValues[] = { openvpn::kNone, openvpn::kServer, NULL }; - if (result->GetStringWithoutPathExpansion(kRemoteCertTLS, &cert_tls) && - !RequireAnyOf(cert_tls, kValidCertTlsValues)) { + + // Using strict bit-wise OR to check all conditions. + if (FieldExistsAndHasNoValidValue(*result, kAuthRetry, + kValidAuthRetryValues) | + FieldExistsAndHasNoValidValue(*result, kClientCertType, kValidCertTypes) | + FieldExistsAndHasNoValidValue(*result, kRemoteCertTLS, + kValidCertTlsValues)) { return false; } bool allRequiredExist = RequireField(*result, kClientCertType); + std::string cert_type; + result->GetStringWithoutPathExpansion(kClientCertType, &cert_type); if (cert_type == kPattern) allRequiredExist &= RequireField(*result, kClientCertPattern); else if (cert_type == kRef) @@ -444,9 +592,15 @@ bool Validator::ValidateCertificatePattern( bool allRequiredExist = true; if (!result->HasKey(kSubject) && !result->HasKey(kIssuer) && !result->HasKey(kIssuerCARef)) { + error_or_warning_found_ = true; allRequiredExist = false; - DVLOG(1) << "None of the fields " << kSubject << ", " << kIssuer << ", and " - << kIssuerCARef << " exists, but at least one is required."; + std::string message = MessageHeader(error_on_missing_field_) + + "None of the fields '" + kSubject + "', '" + kIssuer + "', and '" + + kIssuerCARef + "' is present, but at least one is required."; + if (error_on_missing_field_) + LOG(ERROR) << message; + else + LOG(WARNING) << message; } return !error_on_missing_field_ || allRequiredExist; @@ -458,15 +612,13 @@ bool Validator::ValidateProxySettings(const base::DictionaryValue& onc_object, if (!ValidateObjectDefault(kProxySettingsSignature, onc_object, result)) return false; - std::string type; static const char* kValidTypes[] = { kDirect, kManual, kPAC, kWPAD, NULL }; - if (result->GetStringWithoutPathExpansion(proxy::kType, &type) && - !RequireAnyOf(type, kValidTypes)) { + if (FieldExistsAndHasNoValidValue(*result, proxy::kType, kValidTypes)) return false; - } bool allRequiredExist = RequireField(*result, proxy::kType); - + std::string type; + result->GetStringWithoutPathExpansion(proxy::kType, &type); if (type == kManual) allRequiredExist &= RequireField(*result, kManual); else if (type == kPAC) @@ -494,32 +646,24 @@ bool Validator::ValidateEAP(const base::DictionaryValue& onc_object, if (!ValidateObjectDefault(kEAPSignature, onc_object, result)) return false; - std::string inner; static const char* kValidInnerValues[] = { kAutomatic, kMD5, kMSCHAPv2, kPAP, NULL }; - if (result->GetStringWithoutPathExpansion(kInner, &inner) && - !RequireAnyOf(inner, kValidInnerValues)) { - return false; - } - - std::string outer; static const char* kValidOuterValues[] = { kPEAP, kEAP_TLS, kEAP_TTLS, kLEAP, kEAP_SIM, kEAP_FAST, kEAP_AKA, NULL }; - if (result->GetStringWithoutPathExpansion(kOuter, &outer) && - !RequireAnyOf(outer, kValidOuterValues)) { - return false; - } - - std::string cert_type; static const char* kValidCertTypes[] = { kRef, kPattern, NULL }; - if (result->GetStringWithoutPathExpansion(kClientCertType, &cert_type) && - !RequireAnyOf(cert_type, kValidCertTypes )) { + + // Using strict bit-wise OR to check all conditions. + if (FieldExistsAndHasNoValidValue(*result, kInner, kValidInnerValues) | + FieldExistsAndHasNoValidValue(*result, kOuter, kValidOuterValues) | + FieldExistsAndHasNoValidValue(*result, kClientCertType, + kValidCertTypes)) { return false; } bool allRequiredExist = RequireField(*result, kOuter); - + std::string cert_type; + result->GetStringWithoutPathExpansion(kClientCertType, &cert_type); if (cert_type == kPattern) allRequiredExist &= RequireField(*result, kClientCertPattern); else if (cert_type == kRef) @@ -535,12 +679,9 @@ bool Validator::ValidateCertificate( if (!ValidateObjectDefault(kCertificateSignature, onc_object, result)) return false; - std::string type; static const char* kValidTypes[] = { kClient, kServer, kAuthority, NULL }; - if (result->GetStringWithoutPathExpansion(certificate::kType, &type) && - !RequireAnyOf(type, kValidTypes)) { + if (FieldExistsAndHasNoValidValue(*result, certificate::kType, kValidTypes)) return false; - } bool allRequiredExist = RequireField(*result, kGUID); @@ -549,6 +690,8 @@ bool Validator::ValidateCertificate( if (!remove) { allRequiredExist &= RequireField(*result, certificate::kType); + std::string type; + result->GetStringWithoutPathExpansion(certificate::kType, &type); if (type == kClient) allRequiredExist &= RequireField(*result, kPKCS12); else if (type == kServer || type == kAuthority) @@ -558,5 +701,19 @@ bool Validator::ValidateCertificate( return !error_on_missing_field_ || allRequiredExist; } +std::string Validator::WarningHeader() { + return MessageHeader(false); +} + +std::string Validator::ErrorHeader() { + return MessageHeader(true); +} + +std::string Validator::MessageHeader(bool is_error) { + std::string path = path_.empty() ? "toplevel" : JoinString(path_, "."); + std::string message = "At " + path + ": "; + return message; +} + } // namespace onc } // namespace chromeos diff --git a/chromeos/network/onc/onc_validator.h b/chromeos/network/onc/onc_validator.h index 4a74272..cef7ef5 100644 --- a/chromeos/network/onc/onc_validator.h +++ b/chromeos/network/onc/onc_validator.h @@ -5,13 +5,16 @@ #ifndef CHROMEOS_NETWORK_ONC_ONC_VALIDATOR_H_ #define CHROMEOS_NETWORK_ONC_ONC_VALIDATOR_H_ +#include <string> +#include <vector> + #include "base/memory/scoped_ptr.h" #include "chromeos/chromeos_export.h" #include "chromeos/network/onc/onc_mapper.h" namespace base { -class Value; class DictionaryValue; +class Value; } namespace chromeos { @@ -19,27 +22,44 @@ namespace onc { struct OncValueSignature; +// The ONC Validator searches for the following invalid cases: +// - a value is found that has the wrong type or is not expected according to +// the ONC spec (always an error) +// +// - a field name is found that is not part of the signature +// (controlled by flag |error_on_unknown_field|) +// +// - a kRecommended array contains a field name that is not part of the +// enclosing object's signature or if that field is dictionary typed +// (controlled by flag |error_on_wrong_recommended|) +// +// - |managed_onc| is false and a field with name kRecommended is found +// (always ignored) +// +// - a required field is missing (controlled by flag |error_on_missing_field|) +// +// If one of these invalid cases occurs and, in case of a controlling flag, that +// flag is true, then it is an error. The function ValidateAndRepairObject sets +// |result| to INVALID and returns NULL. +// +// Otherwise, a DeepCopy of the validated object is created, which contains +// all but the invalid fields and values. +// +// If one of the invalid cases occurs and the controlling flag is false, then +// it is a warning. The function ValidateAndRepairObject sets |result| to +// VALID_WITH_WARNINGS and returns the repaired copy. +// +// If no error occurred, |result| is set to VALID and an exact DeepCopy is +// returned. class CHROMEOS_EXPORT Validator : public Mapper { public: - // Creates a Validator that searches for the following invalid cases: - // - a field name is found that is not part of the signature - // (controlled by |error_on_unknown_field|) - // - // - a kRecommended array contains a field name that is not part of the - // enclosing object's signature or if that field is dictionary typed - // (controlled by |error_on_wrong_recommended|) - // - // - |managed_onc| is false and a field with name kRecommended is found - // (always ignored) - // - // - a required field is missing (controlled by |error_on_missing_field|) - // - // If one of these invalid cases occurs and the controlling flag is true, then - // it is an error and the validation stops. The function - // ValidateAndRepairObject returns NULL. - // - // If no error occurred, then a DeepCopy of the validated object is created, - // which contains all but the invalid fields and values. + enum Result { + VALID, + VALID_WITH_WARNINGS, + INVALID + }; + + // See the class comment. Validator(bool error_on_unknown_field, bool error_on_wrong_recommended, bool error_on_missing_field, @@ -49,25 +69,30 @@ class CHROMEOS_EXPORT Validator : public Mapper { // Validate the given |onc_object| according to |object_signature|. The // |object_signature| has to be a pointer to one of the signatures in - // |onc_signature.h|. If an error is found, the function returns NULL. If - // possible (no error encountered) a DeepCopy is created that contains all but - // the invalid fields and values and returns this "repaired" object. - // That means, if not handled as an error, then the following are ignored: + // |onc_signature.h|. If an error is found, the function returns NULL and sets + // |result| to INVALID. If possible (no error encountered) a DeepCopy is + // created that contains all but the invalid fields and values and returns + // this "repaired" object. That means, if not handled as an error, then the + // following are dropped from the copy: // - unknown fields // - invalid field names in kRecommended arrays // - kRecommended fields in an unmanaged ONC - // For details, see the comment at the Constructor. + // If any of these cases occurred, sets |result| to VALID_WITH_WARNINGS and + // otherwise to VALID. + // For details, see the class comment. scoped_ptr<base::DictionaryValue> ValidateAndRepairObject( const OncValueSignature* object_signature, - const base::DictionaryValue& onc_object); + const base::DictionaryValue& onc_object, + Result* result); private: - // Overriden from Mapper: + // Overridden from Mapper: // Compare |onc_value|s type with |onc_type| and validate/repair according to // |signature|. On error returns NULL. virtual scoped_ptr<base::Value> MapValue( const OncValueSignature& signature, - const base::Value& onc_value) OVERRIDE; + const base::Value& onc_value, + bool* error) OVERRIDE; // Dispatch to the right validation function according to // |signature|. Iterates over all fields and recursively validates/repairs @@ -75,7 +100,23 @@ class CHROMEOS_EXPORT Validator : public Mapper { // repaired dictionary. On error returns NULL. virtual scoped_ptr<base::DictionaryValue> MapObject( const OncValueSignature& signature, - const base::DictionaryValue& onc_object) OVERRIDE; + const base::DictionaryValue& onc_object, + bool* error) OVERRIDE; + + // Pushes/pops the |field_name| to |path_|, otherwise like |Mapper::MapField|. + virtual scoped_ptr<base::Value> MapField( + const std::string& field_name, + const OncValueSignature& object_signature, + const base::Value& onc_value, + bool* found_unknown_field, + bool* error) OVERRIDE; + + // Pushes/pops the index to |path_|, otherwise like |Mapper::MapEntry|. + virtual scoped_ptr<base::Value> MapEntry( + int index, + const OncValueSignature& signature, + const base::Value& onc_value, + bool* error) OVERRIDE; // This is the default validation of objects/dictionaries. Validates // |onc_object| according to |object_signature|. |result| must point to a @@ -91,6 +132,10 @@ class CHROMEOS_EXPORT Validator : public Mapper { const OncValueSignature& object_signature, base::DictionaryValue* result); + bool ValidateToplevelConfiguration( + const base::DictionaryValue& onc_object, + base::DictionaryValue* result); + bool ValidateNetworkConfiguration( const base::DictionaryValue& onc_object, base::DictionaryValue* result); @@ -139,11 +184,34 @@ class CHROMEOS_EXPORT Validator : public Mapper { const base::DictionaryValue& onc_object, base::DictionaryValue* result); + bool FieldExistsAndHasNoValidValue(const base::DictionaryValue& object, + const std::string &field_name, + const char** valid_values); + + bool FieldExistsAndIsNotInRange(const base::DictionaryValue& object, + const std::string &field_name, + int lower_bound, + int upper_bound); + + bool RequireField(const base::DictionaryValue& dict, const std::string& key); + + std::string WarningHeader(); + std::string ErrorHeader(); + std::string MessageHeader(bool is_error); + const bool error_on_unknown_field_; const bool error_on_wrong_recommended_; const bool error_on_missing_field_; const bool managed_onc_; + // The path of field names and indices to the current value. Indices + // are stored as strings in decimal notation. + std::vector<std::string> path_; + + // Tracks if an error or warning occurred within validation initiated by + // function ValidateAndRepairObject. + bool error_or_warning_found_; + DISALLOW_COPY_AND_ASSIGN(Validator); }; diff --git a/chromeos/network/onc/onc_validator_unittest.cc b/chromeos/network/onc/onc_validator_unittest.cc index f02e9cf..5d543fe 100644 --- a/chromeos/network/onc/onc_validator_unittest.cc +++ b/chromeos/network/onc/onc_validator_unittest.cc @@ -5,12 +5,14 @@ #include "chromeos/network/onc/onc_validator.h" #include <string> +#include <utility> #include "base/memory/scoped_ptr.h" #include "base/values.h" #include "chromeos/network/onc/onc_constants.h" #include "chromeos/network/onc/onc_signature.h" #include "chromeos/network/onc/onc_test_utils.h" +#include "chromeos/network/onc/onc_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace chromeos { @@ -28,25 +30,60 @@ scoped_ptr<Validator> CreateLiberalValidator(bool managed_onc) { } } // namespace +TEST(ONCValidatorValidTest, EmptyUnencryptedConfiguration) { + scoped_ptr<Validator> validator(CreateStrictValidator(true)); + scoped_ptr<const base::DictionaryValue> original( + ReadDictionaryFromJson(kEmptyUnencryptedConfiguration)); + + Validator::Result result; + scoped_ptr<const base::DictionaryValue> repaired( + validator->ValidateAndRepairObject(&kToplevelConfigurationSignature, + *original, &result)); + EXPECT_EQ(Validator::VALID, result); + EXPECT_TRUE(test_utils::Equals(original.get(), repaired.get())); +} + // This test case is about validating valid ONC objects. -TEST(ONCValidatorValidTest, ValidPolicyOnc) { +class ONCValidatorValidTest + : public ::testing::TestWithParam< + std::pair<std::string, const OncValueSignature*> > { + protected: + std::string GetFilename() const { + return GetParam().first; + } + + const OncValueSignature* GetSignature() const { + return GetParam().second; + } +}; + +TEST_P(ONCValidatorValidTest, ValidPolicyOnc) { scoped_ptr<Validator> validator(CreateStrictValidator(true)); - scoped_ptr<const base::DictionaryValue> network; - scoped_ptr<base::DictionaryValue> repaired; - - network = test_utils::ReadTestDictionary("policy.onc"); - repaired = validator->ValidateAndRepairObject( - &kNetworkConfigurationSignature, - *network); - EXPECT_TRUE(test_utils::Equals(network.get(), repaired.get())); - - network = test_utils::ReadTestDictionary("valid.onc"); - repaired = validator->ValidateAndRepairObject( - &kNetworkConfigurationSignature, - *network); - EXPECT_TRUE(test_utils::Equals(network.get(), repaired.get())); + scoped_ptr<const base::DictionaryValue> original( + test_utils::ReadTestDictionary(GetFilename())); + + Validator::Result result; + scoped_ptr<const base::DictionaryValue> repaired( + validator->ValidateAndRepairObject(GetSignature(), *original, &result)); + EXPECT_EQ(Validator::VALID, result); + EXPECT_TRUE(test_utils::Equals(original.get(), repaired.get())); } +INSTANTIATE_TEST_CASE_P( + ONCValidatorValidTest, + ONCValidatorValidTest, + ::testing::Values(std::make_pair("managed_toplevel.onc", + &kToplevelConfigurationSignature), + std::make_pair("encrypted.onc", + &kToplevelConfigurationSignature), + std::make_pair("managed_vpn.onc", + &kNetworkConfigurationSignature), + std::make_pair("managed_ethernet.onc", + &kNetworkConfigurationSignature), + // Ignore recommended arrays in unmanaged ONC: + std::make_pair("recommended_in_unmanaged.onc", + &kNetworkConfigurationSignature))); + // Validate invalid ONC objects and check the resulting repaired object. This // test fixture loads a test json file into |invalid_| containing several test // objects which can be accessed by their path. The test boolean parameter @@ -71,11 +108,14 @@ class ONCValidatorInvalidTest : public ::testing::TestWithParam<bool> { const base::DictionaryValue* object = NULL; ASSERT_TRUE(invalid_->GetDictionary(path_to_object, &object)); + Validator::Result result; scoped_ptr<base::DictionaryValue> actual_repaired = - validator->ValidateAndRepairObject(signature, *object); + validator->ValidateAndRepairObject(signature, *object, &result); if (GetParam() || path_to_repaired == "") { + EXPECT_EQ(Validator::INVALID, result); EXPECT_EQ(NULL, actual_repaired.get()); } else { + EXPECT_EQ(Validator::VALID_WITH_WARNINGS, result); const base::DictionaryValue* expected_repaired = NULL; invalid_->GetDictionary(path_to_repaired, &expected_repaired); EXPECT_TRUE(test_utils::Equals(expected_repaired, actual_repaired.get())); diff --git a/chromeos/test/data/network/valid.onc b/chromeos/test/data/network/managed_ethernet.onc index 56828bc..56828bc 100644 --- a/chromeos/test/data/network/valid.onc +++ b/chromeos/test/data/network/managed_ethernet.onc diff --git a/chromeos/test/data/network/managed_toplevel.onc b/chromeos/test/data/network/managed_toplevel.onc new file mode 100644 index 0000000..a8feabd --- /dev/null +++ b/chromeos/test/data/network/managed_toplevel.onc @@ -0,0 +1,47 @@ +{ "Type": "UnencryptedConfiguration", + "NetworkConfigurations": + [ { "GUID": "123", + "Type": "VPN", + "Name": "testopenvpn", + "IPConfigs": [ + { "Type": "IPv4", + "IPAddress": "127.0.0.1", + "RoutingPrefix": 32 } + ], + "VPN": { + "Host": "policys host", + "Recommended": ["Host"], + "Type": "OpenVPN", + "OpenVPN": { + "Port": 1194, + "Username": "policy user", + "Recommended": [ "Username", "Password" ], + "ClientCertType": "Pattern", + "ClientCertPattern": { + "IssuerCARef": [ "openvpn-test-ca" ], + "Recommended": [ "EnrollmentURI", "IssuerCARef" ] + } + }, + "IPsec": { + "AuthenticationType": "PSK", + "PSK": "sharedkey", + "IKEVersion": 1 + } + } + }, + { "GUID": "guid", + "Type": "Ethernet", + "Name": "name", + "Ethernet": { + "Authentication": "None" + } + } + ], + "Certificates": [ + { + "GUID": "{f998f760-272b-6939-4c2beffe428697ac}", + "PKCS12": "MIIGUQIBAzCCBhcGCSqGSIb3DQEHAaCCBggEggYEMIIGADCCAv8GCSqGSIb3DQEHBqCCAvAwggLsAgEAMIIC5QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIHnFaWM2Y0BgCAggAgIICuG4ou9mxkhpus8WictLJe+JOnSQrdNXV3FMQr4pPJ6aJJFBMKZ80W2GpR8XNY/SSKkdaNr1puDm1bDBFGaHQuCKXYcWO8ynBQ1uoZaFaTTFxWbbHo89Jrvw+gIrgpoOHQ0KECEbh5vOZCjGHoaQb4QZOkw/6Cuc4QRoCPJAI3pbSPG44kRbOuOaTZvBHSIPkGf3+R6byTvZ3Yiuw7IIzxUp2fYjtpCWd/NvtI70heJCWdb5hwCeNafIEpX+MTVuhUegysIFkOMMlUBIQSI5ky8kjx0Yi82BT/dpz9QgrqFL8NnTMXp0JlKFGLQwsIQhvGjw/E52fEWRy85B5eezgNsD4QOLeZkF0bQAz8kXfLi+0djxsHvH9W9X2pwaFiAveXR15/v+wfCwQGSsRhISGLzg/gO1agbQdaexI9GlEeZW0FEY7TblarKh8TVGNrauU7GCGDmD2w7wx2HTXfo9SbViFoYVKuxcrpHGGEtBffnIeAwN6BBee4v11jxv0i/QUdK5G6FbHqlD1AhHsm0YvidYKqJ0cnN262xIJH7dhKq/qUiAT+qk3+d3/obqxbvVY+bDoJQ10Gzj1ASMy4zcSL7KW1l99xxMr6OlKr4Sr23oGw4BIN73FB8S8qMzz/VzL4azDUyGpPkzWl0yXPsHpFWh1nZlsQehyknyWDH/waKrrG8tVWxHZLgq+zrFxQTh63UHXSD+TXB+AQg2xmQMeWlfvRcsKL8titZ6PnWCHTmZY+3ibv5avDsg7He6OcZOi9ZmYMx82QHuzb4aZ/T+OC05oA97nVNbTN6t8okkRtBamMvVhtTJANVpsdPi8saEaVF8e9liwmpq2w7pqXnzgdzvjSUpPAa4dZBjWnZJvFOHuxZqiRzQdZbeh9+bXwsQJhRNe+d4EgFwuqebQOczeUi4NVTHTFiuPEjCCAvkGCSqGSIb3DQEHAaCCAuoEggLmMIIC4jCCAt4GCyqGSIb3DQEMCgECoIICpjCCAqIwHAYKKoZIhvcNAQwBAzAOBAi0znbEekG/MgICCAAEggKAJfFPaQyYYLohEA1ruAZfepwMVrR8eLMx00kkfXN9EoZeFPj2q7TGdqmbkUSqXnZK1ums7pFCPLgP1CsPlsq/4ZPDT2LLVFZNLOgmdQBOSTvycfsj0iKYrwRC55wJI2OXsc062sT7oa99apkgrEyHq7JbOhszfnv5+aVy/6O115dncqFPW2ei4CBzLEZyYa+Mka6CGqSdm97WVmv0emDKTFEP/FN4TH/tS8Qm6Y7DTKGCujC+hb6lTRFYJAD4uld132dv0xQFkwDZGfdnuGJuNZBDC0gZk3BYvOaCUD8Y9UB5IjfGJax2yrurY1wSGSlTurafDTPrKqIdBovwCPsad2xz1YHC2Yy0h1FyR+2uitDyNfTiETfug3bFbjwodu9wmt31A2ZFn4JpUrTYoZ3LZXngC3nNTayU0Tkd1ICMep2GbCReL3ajOlgOKGFVoOm/qDnhiH6W/ebtAQXqVpuKut8uY0X0Ocmx7mTpmxlfDSRiBY9rvnrGfnpfLMxtFeF9jv3n8vSwvA0Xn0okAv1FWYLStiCpNxnD6lmXQvcmL/skAlJJpHY9/58qt/e5sGYrkKBw3jnX40zaK4W7GeJvhij0MRr6yUL2lvaEcWDnK6K1F90G/ybKRCTHBCJzyBe7yHhZCc+ZcvKK6DTi83fELTyupy08BkXt7oPdapxmKlZxTldo9FpPXSqrdRtAWhDkEkIEf8dMf8QrQr3glCWfbcQ047URYX45AHRnLTLLkJfdY8+Y3KsHoqL2UrOrct+J1u0mmnLbonN3pB2B4nd9X9vf9/uSFrgvk0iPO0Ro3UPRUIIYEP2Kx51pZZVDd++hl5gXtqe0NIpphGhxLycIdzElMCMGCSqGSIb3DQEJFTEWBBR1uVpGjHRddIEYuJhz/FgG4Onh6jAxMCEwCQYFKw4DAhoFAAQU1M+0WRDkoVGbGg1jj7q2fI67qHIECBzRYESpgt5iAgIIAA==", + "Type": "Client" + } + ] +} diff --git a/chromeos/test/data/network/policy.onc b/chromeos/test/data/network/managed_vpn.onc index 4c52e97..4c52e97 100644 --- a/chromeos/test/data/network/policy.onc +++ b/chromeos/test/data/network/managed_vpn.onc diff --git a/chromeos/test/data/network/policy_without_recommended.onc b/chromeos/test/data/network/managed_vpn_without_recommended.onc index 037bf53..037bf53 100644 --- a/chromeos/test/data/network/policy_without_recommended.onc +++ b/chromeos/test/data/network/managed_vpn_without_recommended.onc diff --git a/chromeos/test/data/network/recommended_in_unmanaged.onc b/chromeos/test/data/network/recommended_in_unmanaged.onc new file mode 100644 index 0000000..33a5010 --- /dev/null +++ b/chromeos/test/data/network/recommended_in_unmanaged.onc @@ -0,0 +1,8 @@ +{ "GUID": "guid", + "Recommended": ["Name"], + "Type": "Ethernet", + "Name": "name", + "Ethernet": { + "Authentication": "None" + } +} |