Chromium Code Reviews| Index: chromeos/network/managed_network_configuration_handler.cc |
| diff --git a/chromeos/network/managed_network_configuration_handler.cc b/chromeos/network/managed_network_configuration_handler.cc |
| index c88a15e79e1e0d41a9beed1154667f1a5e2a645e..b07ee1bb6efa98b53e86c0a803c6eeb4ab28a762 100644 |
| --- a/chromeos/network/managed_network_configuration_handler.cc |
| +++ b/chromeos/network/managed_network_configuration_handler.cc |
| @@ -9,21 +9,30 @@ |
| #include "base/bind.h" |
| #include "base/guid.h" |
| +#include "base/json/json_writer.h" |
| +#include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_ptr.h" |
| +#include "base/stl_util.h" |
| #include "base/values.h" |
| #include "chromeos/dbus/dbus_method_call_status.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/shill_manager_client.h" |
| +#include "chromeos/dbus/shill_profile_client.h" |
| #include "chromeos/dbus/shill_service_client.h" |
| #include "chromeos/network/network_configuration_handler.h" |
| #include "chromeos/network/network_event_log.h" |
| +#include "chromeos/network/network_handler_callbacks.h" |
| #include "chromeos/network/network_state.h" |
| #include "chromeos/network/network_state_handler.h" |
| +#include "chromeos/network/network_ui_data.h" |
| #include "chromeos/network/onc/onc_constants.h" |
| +#include "chromeos/network/onc/onc_merger.h" |
| #include "chromeos/network/onc/onc_signature.h" |
| #include "chromeos/network/onc/onc_translator.h" |
| +#include "chromeos/network/onc/onc_utils.h" |
| +#include "chromeos/network/onc/onc_validator.h" |
| #include "dbus/object_path.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| @@ -37,13 +46,35 @@ const char kLogModule[] = "ManagedNetworkConfigurationHandler"; |
| // These are error strings used for error callbacks. None of these error |
| // messages are user-facing: they should only appear in logs. |
| +const char kInvalidUserSettingsMessage[] = "User settings are invalid."; |
| +const char kInvalidUserSettings[] = "Error.InvalidUserSettings"; |
| +const char kNetworkAlreadyConfiguredMessage[] = |
| + "Network is already configured."; |
| +const char kNetworkAlreadyConfigured[] = "Error.NetworkAlreadyConfigured"; |
| +const char kPoliciesNotInitializedMessage[] = "Policies not initialized."; |
| +const char kPoliciesNotInitialized[] = "Error.PoliciesNotInitialized"; |
| const char kServicePath[] = "servicePath"; |
| const char kSetOnUnconfiguredNetworkMessage[] = |
| "Unable to modify properties of an unconfigured network."; |
| const char kSetOnUnconfiguredNetwork[] = "Error.SetCalledOnUnconfiguredNetwork"; |
| +const char kUIDataErrorMessage[] = "UI data contains errors."; |
| +const char kUIDataError[] = "Error.UIData"; |
| const char kUnknownServicePathMessage[] = "Service path is unknown."; |
| const char kUnknownServicePath[] = "Error.UnknownServicePath"; |
| +enum ProfileType { |
| + PROFILE_NONE, // Not in any profile. |
| + PROFILE_SHARED, // In the shared profile, shared by all users on device. |
| + PROFILE_USER // In the user profile, not visible to other users. |
| +}; |
| + |
| +const char kSharedProfilePath[] = "/profile/default"; |
| +const char kUserProfilePath[] = "/profile/chronos/shill"; |
| + |
| +// This fake credential contains a random postfix which is extremly unlikely to |
| +// be used by any user. |
| +const char kFakeCredential[] = "FAKE_CREDENTIAL_VPaJDV9x"; |
| + |
| void RunErrorCallback(const std::string& service_path, |
| const std::string& error_name, |
| const std::string& error_message, |
| @@ -57,7 +88,213 @@ void RunErrorCallback(const std::string& service_path, |
| error_message))); |
| } |
| -void TranslatePropertiesAndRunCallback( |
| +// Returns the NetworkUIData parsed from the UIData property of |
| +// |shill_dictionary|. If parsing fails or the field doesn't exist, returns |
| +// NULL. |
| +scoped_ptr<NetworkUIData> GetUIData( |
| + const base::DictionaryValue& shill_dictionary) { |
| + std::string ui_data_blob; |
| + if (shill_dictionary.GetStringWithoutPathExpansion( |
| + flimflam::kUIDataProperty, |
| + &ui_data_blob) && |
| + !ui_data_blob.empty()) { |
| + scoped_ptr<base::DictionaryValue> ui_data_dict = |
| + onc::ReadDictionaryFromJson(ui_data_blob); |
| + if (ui_data_dict) |
| + return make_scoped_ptr(new NetworkUIData(*ui_data_dict)); |
| + else |
| + LOG(ERROR) << "UIData is not a valid JSON dictionary."; |
| + } |
| + return scoped_ptr<NetworkUIData>(); |
| +} |
| + |
| +// Sets the UIData property in |shill_dictionary| to the serialization of |
| +// |ui_data|. |
| +void SetUIData(const NetworkUIData& ui_data, |
| + base::DictionaryValue* shill_dictionary) { |
| + base::DictionaryValue ui_data_dict; |
| + ui_data.FillDictionary(&ui_data_dict); |
| + std::string ui_data_blob; |
| + base::JSONWriter::Write(&ui_data_dict, &ui_data_blob); |
| + shill_dictionary->SetStringWithoutPathExpansion(flimflam::kUIDataProperty, |
| + ui_data_blob); |
| +} |
| + |
| +// A dummy callback to ignore the result of Shill calls. |
| +void IgnoreString(const std::string& str) { |
| +} |
| + |
| +void LogErrorWithDict(const tracked_objects::Location& from_where, |
| + const std::string& error_name, |
| + const scoped_ptr<base::DictionaryValue> error_data) { |
| + LOG(ERROR) << from_where.ToString() << ": " << error_name; |
| +} |
| + |
| +void LogErrorMessage(const tracked_objects::Location& from_where, |
| + const std::string& error_name, |
| + const std::string& error_message) { |
| + LOG(ERROR) << from_where.ToString() << ": " << error_message; |
| +} |
| + |
| +// Removes all kFakeCredential values from sensitive fields (determined by |
| +// onc::FieldIsCredential) of |onc_object|. |
| +void RemoveFakeCredentials( |
| + const onc::OncValueSignature& signature, |
| + base::DictionaryValue* onc_object) { |
| + base::DictionaryValue::Iterator it(*onc_object); |
| + while (!it.IsAtEnd()) { |
| + base::Value* value = NULL; |
| + std::string field_name = it.key(); |
| + // We need the non-const entry to remove nested values but DictionaryValue |
| + // has no non-const iterator. |
| + onc_object->GetWithoutPathExpansion(field_name, &value); |
| + // Advance before delete. |
| + it.Advance(); |
| + |
| + // If |value| is a dictionary, recurse. |
| + base::DictionaryValue* nested_object = NULL; |
| + if (value->GetAsDictionary(&nested_object)) { |
| + const onc::OncFieldSignature* field_signature = |
| + onc::GetFieldSignature(signature, field_name); |
| + |
| + RemoveFakeCredentials(*field_signature->value_signature, |
| + nested_object); |
| + continue; |
| + } |
| + |
| + // If |value| is a string and a credential, ... |
|
stevenjb
2013/04/16 16:02:40
Maybe 'If |value| is a string, check if it is a fa
|
| + std::string string_value; |
| + if (value->GetAsString(&string_value) && |
| + onc::FieldIsCredential(signature, field_name) && |
| + string_value == kFakeCredential) { |
| + if (string_value == kFakeCredential) { |
|
stevenjb
2013/04/16 16:02:40
redundant
|
| + // The value wasn't modified by the UI, thus we remove the field to keep |
| + // the existing value that is stored in Shill. |
| + onc_object->RemoveWithoutPathExpansion(field_name, NULL); |
| + } |
| + // Otherwise, the value is set and modified by the UI, thus we keep that |
| + // value to overwrite whatever is stored in Shill. |
| + } |
| + } |
| +} |
| + |
| +// Creates a Shill property dictionary from the given arguments. The resulting |
| +// dictionary will be sent to Shill by the caller. Depending on the profile |
| +// path, |policy| is interpreted as the user or device policy and |settings| as |
| +// the user or shared settings. |
| +scoped_ptr<base::DictionaryValue> CreateShillConfiguration( |
| + const std::string& profile_path, |
| + const std::string& guid, |
| + const base::DictionaryValue* policy, |
| + const base::DictionaryValue* settings) { |
| + scoped_ptr<base::DictionaryValue> effective; |
| + |
| + onc::ONCSource onc_source; |
| + if (policy) { |
| + if (profile_path == kSharedProfilePath) { |
| + effective = onc::MergeSettingsAndPoliciesToEffective( |
| + NULL, // no user policy |
| + policy, // device policy |
| + NULL, // no user settings |
| + settings); // shared settings |
| + onc_source = onc::ONC_SOURCE_DEVICE_POLICY; |
| + } else { |
| + effective = onc::MergeSettingsAndPoliciesToEffective( |
| + policy, // user policy |
| + NULL, // no device policy |
| + settings, // user settings |
| + NULL); // no shared settings |
| + onc_source = onc::ONC_SOURCE_USER_POLICY; |
| + } |
| + } else if (settings) { |
| + effective.reset(settings->DeepCopy()); |
| + // TODO(pneubeck): change to source ONC_SOURCE_USER |
| + onc_source = onc::ONC_SOURCE_NONE; |
| + } else { |
| + NOTREACHED(); |
| + onc_source = onc::ONC_SOURCE_NONE; |
| + } |
| + |
| + RemoveFakeCredentials(onc::kNetworkConfigurationSignature, |
| + effective.get()); |
| + |
| + effective->SetStringWithoutPathExpansion(onc::network_config::kGUID, guid); |
| + |
| + scoped_ptr<base::DictionaryValue> shill_dictionary( |
| + onc::TranslateONCObjectToShill(&onc::kNetworkConfigurationSignature, |
| + *effective)); |
| + |
| + shill_dictionary->SetStringWithoutPathExpansion(flimflam::kProfileProperty, |
| + profile_path); |
| + |
| + scoped_ptr<NetworkUIData> ui_data; |
| + if (policy) |
| + ui_data = CreateUIDataFromONC(onc_source, *policy); |
| + else |
| + ui_data.reset(new NetworkUIData()); |
| + |
| + if (settings) { |
| + // Shill doesn't know that sensitive data is contained in the UIData |
| + // property and might write it into logs or other insecure places. Thus, we |
| + // have to remove or mask credentials. |
| + // |
| + // Shill's GetProperties doesn't return credentials. Masking credentials |
| + // instead of just removing them, allows remembering if a credential is set |
| + // or not. |
| + scoped_ptr<base::DictionaryValue> sanitized_settings( |
| + onc::MaskCredentialsInOncObject(onc::kNetworkConfigurationSignature, |
| + *settings, |
| + kFakeCredential)); |
| + ui_data->set_user_settings(sanitized_settings.Pass()); |
| + } |
| + |
| + SetUIData(*ui_data, shill_dictionary.get()); |
| + |
| + VLOG(2) << "Created Shill properties: " << *shill_dictionary; |
| + |
| + return shill_dictionary.Pass(); |
| +} |
| + |
| +// Returns true if |policy| matches |onc_network_part|. This is should be the |
| +// only such matching function within Chrome. Shill does such matching in |
| +// several functions for network identification. For compatibility, we currently |
| +// should stick to Shill's matching behavior. |
| +bool IsPolicyMatching(const base::DictionaryValue& policy, |
| + const base::DictionaryValue& onc_network_part) { |
| + std::string policy_type; |
| + policy.GetStringWithoutPathExpansion(onc::network_config::kType, |
| + &policy_type); |
| + std::string network_type; |
| + onc_network_part.GetStringWithoutPathExpansion(onc::network_config::kType, |
| + &network_type); |
| + if (policy_type != network_type) |
| + return false; |
| + |
| + if (network_type != onc::network_type::kWiFi) |
| + return false; |
| + |
| + std::string policy_ssid; |
| + policy.GetStringWithoutPathExpansion(onc::wifi::kSSID, &policy_ssid); |
| + std::string network_ssid; |
| + onc_network_part.GetStringWithoutPathExpansion(onc::wifi::kSSID, |
| + &network_ssid); |
| + return (policy_ssid == network_ssid); |
| +} |
| + |
| +// Returns the policy of |policies| matching |onc_network_part|, if any |
| +// exists. Returns NULL otherwise. |
| +const base::DictionaryValue* FindMatchingPolicy( |
| + const ManagedNetworkConfigurationHandler::PolicyMap &policies, |
| + const base::DictionaryValue& onc_network_part) { |
| + for (ManagedNetworkConfigurationHandler::PolicyMap::const_iterator it = |
| + policies.begin(); it != policies.end(); ++it) { |
| + if (IsPolicyMatching(*it->second, onc_network_part)) |
| + return it->second; |
| + } |
| + return NULL; |
| +} |
| + |
| +void TranslatePropertiesToOncAndRunCallback( |
| const network_handler::DictionaryResultCallback& callback, |
| const std::string& service_path, |
| const base::DictionaryValue& shill_properties) { |
| @@ -94,20 +331,107 @@ ManagedNetworkConfigurationHandler* ManagedNetworkConfigurationHandler::Get() { |
| return g_configuration_handler_instance; |
| } |
| +void ManagedNetworkConfigurationHandler::GetManagedProperties( |
| + const std::string& service_path, |
| + const network_handler::DictionaryResultCallback& callback, |
| + const network_handler::ErrorCallback& error_callback) { |
| + if (!user_policies_initialized_ || !device_policies_initialized_) { |
| + RunErrorCallback(service_path, |
| + kPoliciesNotInitialized, |
| + kPoliciesNotInitializedMessage, |
| + error_callback); |
| + return; |
| + } |
| + NetworkConfigurationHandler::Get()->GetProperties( |
| + service_path, |
| + base::Bind( |
| + &ManagedNetworkConfigurationHandler::GetManagedPropertiesCallback, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + callback, |
| + error_callback), |
| + error_callback); |
| +} |
| + |
| +void ManagedNetworkConfigurationHandler::GetManagedPropertiesCallback( |
| + const network_handler::DictionaryResultCallback& callback, |
| + const network_handler::ErrorCallback& error_callback, |
| + const std::string& service_path, |
| + const base::DictionaryValue& shill_properties) { |
| + std::string profile_path; |
| + ProfileType profile_type = PROFILE_NONE; |
| + if (shill_properties.GetStringWithoutPathExpansion( |
| + flimflam::kProfileProperty, &profile_path)) { |
| + if (profile_path == kSharedProfilePath) |
| + profile_type = PROFILE_SHARED; |
| + else if (!profile_path.empty()) |
| + profile_type = PROFILE_USER; |
| + } else { |
| + VLOG(1) << "No profile path for service " << service_path << "."; |
| + } |
| + |
| + scoped_ptr<NetworkUIData> ui_data = GetUIData(shill_properties); |
| + |
| + const base::DictionaryValue* user_settings = NULL; |
| + const base::DictionaryValue* shared_settings = NULL; |
| + |
| + if (ui_data) { |
| + if (profile_type == PROFILE_SHARED) |
| + shared_settings = ui_data->user_settings(); |
| + else if (profile_type == PROFILE_USER) |
| + user_settings = ui_data->user_settings(); |
| + } else if (profile_type != PROFILE_NONE) { |
| + LOG(WARNING) << "Service " << service_path << " of profile " |
| + << profile_path << " contains no or no valid UIData."; |
| + // TODO(pneubeck): add a conversion of user configured entries of old |
| + // ChromeOS versions. We will have to use a heuristic to determine which |
| + // properties _might_ be user configured. |
| + } |
| + |
| + scoped_ptr<base::DictionaryValue> active_settings( |
| + onc::TranslateShillServiceToONCPart( |
| + shill_properties, |
| + &onc::kNetworkWithStateSignature)); |
| + |
| + std::string guid; |
| + active_settings->GetStringWithoutPathExpansion(onc::network_config::kGUID, |
| + &guid); |
| + |
| + const base::DictionaryValue* user_policy = NULL; |
| + const base::DictionaryValue* device_policy = NULL; |
| + if (!guid.empty()) { |
| + // We already checked that the policies were initialized. No need to do that |
| + // again. |
| + if (profile_type == PROFILE_SHARED) |
| + device_policy = device_policies_by_guid_[guid]; |
| + else if (profile_type == PROFILE_USER) |
| + user_policy = user_policies_by_guid_[guid]; |
| + } |
| + |
| + // This call also removes credentials from policies. |
| + scoped_ptr<base::DictionaryValue> augmented_properties = |
| + onc::MergeSettingsAndPoliciesToAugmented( |
| + onc::kNetworkConfigurationSignature, |
| + user_policy, |
| + device_policy, |
| + user_settings, |
| + shared_settings, |
| + active_settings.get()); |
| + callback.Run(service_path, *augmented_properties); |
| +} |
| + |
| void ManagedNetworkConfigurationHandler::GetProperties( |
| const std::string& service_path, |
| const network_handler::DictionaryResultCallback& callback, |
| const network_handler::ErrorCallback& error_callback) const { |
| - // TODO(pneubeck): Merge with policies. |
| NetworkConfigurationHandler::Get()->GetProperties( |
| service_path, |
| - base::Bind(&TranslatePropertiesAndRunCallback, callback), |
| + base::Bind(&TranslatePropertiesToOncAndRunCallback, callback), |
| error_callback); |
| } |
| void ManagedNetworkConfigurationHandler::SetProperties( |
| const std::string& service_path, |
| - const base::DictionaryValue& properties, |
| + const base::DictionaryValue& user_settings, |
| const base::Closure& callback, |
| const network_handler::ErrorCallback& error_callback) const { |
| const NetworkState* state = |
| @@ -118,20 +442,70 @@ void ManagedNetworkConfigurationHandler::SetProperties( |
| kUnknownServicePath, |
| kUnknownServicePathMessage, |
| error_callback); |
| + return; |
| } |
| + |
| std::string guid = state->guid(); |
| if (guid.empty()) { |
| + // TODO(pneubeck): create an initial configuration in this case. As for |
| + // CreateConfiguration, user settings from older ChromeOS versions have to |
| + // determined here. |
| RunErrorCallback(service_path, |
| kSetOnUnconfiguredNetwork, |
| kSetOnUnconfiguredNetworkMessage, |
| error_callback); |
| + return; |
| + } |
| + |
| + // Validate the ONC dictionary. We are liberal and ignore unknown field |
| + // names. User settings are only partial ONC, thus we ignore missing fields. |
| + onc::Validator validator(false, // Ignore unknown fields. |
| + false, // Ignore invalid recommended field names. |
| + false, // Ignore missing fields. |
| + false); // This ONC does not comes from policy. |
| + |
| + onc::Validator::Result validation_result; |
| + scoped_ptr<base::DictionaryValue> validated_user_settings = |
| + validator.ValidateAndRepairObject( |
| + &onc::kNetworkConfigurationSignature, |
| + user_settings, |
| + &validation_result); |
| + |
| + if (validation_result == onc::Validator::INVALID) { |
| + LOG(ERROR) << "ONC user settings are invalid and couldn't be repaired."; |
| + RunErrorCallback(service_path, |
| + kInvalidUserSettings, |
| + kInvalidUserSettingsMessage, |
| + error_callback); |
| + return; |
| } |
| + if (validation_result == onc::Validator::VALID_WITH_WARNINGS) |
| + LOG(WARNING) << "Validation of ONC user settings produced warnings."; |
| - // TODO(pneubeck): Enforce policies. |
| + VLOG(2) << "SetProperties: Found GUID " << guid << " and profile " |
| + << state->profile_path(); |
| + |
| + const PolicyMap* policies_by_guid = |
| + GetPoliciesForProfile(state->profile_path()); |
| + |
| + if (!policies_by_guid) { |
| + RunErrorCallback(service_path, |
| + kPoliciesNotInitialized, |
| + kPoliciesNotInitializedMessage, |
| + error_callback); |
| + return; |
| + } |
| + |
| + const base::DictionaryValue* policy = NULL; |
| + PolicyMap::const_iterator it = policies_by_guid->find(guid); |
| + if (it != policies_by_guid->end()) |
| + policy = it->second; |
| + |
| + VLOG(2) << "This configuration is " << (policy ? "" : "not ") << "managed."; |
| scoped_ptr<base::DictionaryValue> shill_dictionary( |
| - onc::TranslateONCObjectToShill(&onc::kNetworkConfigurationSignature, |
| - properties)); |
| + CreateShillConfiguration(state->profile_path(), guid, policy, |
| + &user_settings)); |
| NetworkConfigurationHandler::Get()->SetProperties(service_path, |
| *shill_dictionary, |
| @@ -143,8 +517,6 @@ void ManagedNetworkConfigurationHandler::Connect( |
| const std::string& service_path, |
| const base::Closure& callback, |
| const network_handler::ErrorCallback& error_callback) const { |
| - // TODO(pneubeck): Update the user profile with tracked/followed settings of |
| - // the shared profile. |
| NetworkConfigurationHandler::Get()->Connect(service_path, |
| callback, |
| error_callback); |
| @@ -163,25 +535,35 @@ void ManagedNetworkConfigurationHandler::CreateConfiguration( |
| const base::DictionaryValue& properties, |
| const network_handler::StringResultCallback& callback, |
| const network_handler::ErrorCallback& error_callback) const { |
| - scoped_ptr<base::DictionaryValue> modified_properties( |
| - properties.DeepCopy()); |
| + std::string profile_path = kUserProfilePath; |
| + const PolicyMap* policies_by_guid = GetPoliciesForProfile(profile_path); |
| - // If there isn't already a GUID attached to these properties, then |
| - // generate one and add it. |
| - std::string guid; |
| - if (!properties.GetString(onc::network_config::kGUID, &guid)) { |
| - guid = base::GenerateGUID(); |
| - modified_properties->SetStringWithoutPathExpansion( |
| - onc::network_config::kGUID, guid); |
| - } else { |
| - NOTREACHED(); // TODO(pneubeck): Return an error using error_callback. |
| + if (!policies_by_guid) { |
| + RunErrorCallback("", |
| + kPoliciesNotInitialized, |
| + kPoliciesNotInitializedMessage, |
| + error_callback); |
| + return; |
| + } |
| + |
| + if (FindMatchingPolicy(*policies_by_guid, properties)) { |
| + RunErrorCallback("", |
| + kNetworkAlreadyConfigured, |
| + kNetworkAlreadyConfiguredMessage, |
| + error_callback); |
| } |
| - // TODO(pneubeck): Enforce policies. |
| + // TODO(pneubeck): In case of WiFi, check that no other configuration for the |
| + // same {SSID, mode, security} exists. We don't support such multiple |
| + // configurations, yet. |
| + |
| + // Generate a new GUID for this configuration. Ignore the maybe provided GUID |
| + // in |properties| as it is not our own and from an untrusted source. |
| + std::string guid = base::GenerateGUID(); |
| scoped_ptr<base::DictionaryValue> shill_dictionary( |
| - onc::TranslateONCObjectToShill(&onc::kNetworkConfigurationSignature, |
| - properties)); |
| + CreateShillConfiguration(profile_path, guid, NULL /*no policy*/, |
| + &properties)); |
| NetworkConfigurationHandler::Get()->CreateConfiguration(*shill_dictionary, |
| callback, |
| @@ -197,10 +579,337 @@ void ManagedNetworkConfigurationHandler::RemoveConfiguration( |
| error_callback); |
| } |
| -ManagedNetworkConfigurationHandler::ManagedNetworkConfigurationHandler() { |
| +// This class compares (entry point is Run()) |modified_policies| with the |
| +// existing entries in the provided Shill profile |profile|. It fetches all |
| +// entries in parallel (GetProfileProperties), compares each entry with the |
| +// current policies (GetEntry) and adds all missing policies |
| +// (~PolicyApplicator). |
| +class ManagedNetworkConfigurationHandler::PolicyApplicator |
| + : public base::RefCounted<PolicyApplicator> { |
| + public: |
| + typedef ManagedNetworkConfigurationHandler::PolicyMap PolicyMap; |
| + |
| + // |modified_policies| must not be NULL and will be empty afterwards. |
| + PolicyApplicator(base::WeakPtr<ManagedNetworkConfigurationHandler> handler, |
| + const std::string& profile, |
| + std::set<std::string>* modified_policies) |
| + : handler_(handler), |
| + profile_path_(profile) { |
| + remaining_policies_.swap(*modified_policies); |
| + } |
| + |
| + void Run() { |
| + DBusThreadManager::Get()->GetShillProfileClient()->GetProperties( |
| + dbus::ObjectPath(profile_path_), |
| + base::Bind(&PolicyApplicator::GetProfileProperties, this), |
| + base::Bind(&LogErrorMessage, FROM_HERE)); |
| + } |
| + |
| + private: |
| + friend class base::RefCounted<PolicyApplicator>; |
| + |
| + void GetProfileProperties(const base::DictionaryValue& profile_properties) { |
| + if (!handler_) { |
| + LOG(WARNING) << "Handler destructed during policy application to profile " |
| + << profile_path_; |
| + return; |
| + } |
| + |
| + VLOG(2) << "Received properties for profile " << profile_path_; |
| + const base::ListValue* entries = NULL; |
| + if (!profile_properties.GetListWithoutPathExpansion( |
| + flimflam::kEntriesProperty, &entries)) { |
| + LOG(ERROR) << "Profile " << profile_path_ |
| + << " doesn't contain the property " |
| + << flimflam::kEntriesProperty; |
| + return; |
| + } |
| + |
| + for (base::ListValue::const_iterator it = entries->begin(); |
| + it != entries->end(); ++it) { |
| + std::string entry; |
| + (*it)->GetAsString(&entry); |
| + |
| + std::ostringstream entry_failure; |
| + DBusThreadManager::Get()->GetShillProfileClient()->GetEntry( |
| + dbus::ObjectPath(profile_path_), |
| + entry, |
| + base::Bind(&PolicyApplicator::GetEntry, this, entry), |
| + base::Bind(&LogErrorMessage, FROM_HERE)); |
| + } |
| + } |
| + |
| + void GetEntry(const std::string& entry, |
| + const base::DictionaryValue& entry_properties) { |
| + if (!handler_) { |
| + LOG(WARNING) << "Handler destructed during policy application to profile " |
| + << profile_path_; |
| + return; |
| + } |
| + |
| + VLOG(2) << "Received properties for entry " << entry << " of profile " |
| + << profile_path_; |
| + |
| + scoped_ptr<base::DictionaryValue> onc_part( |
| + onc::TranslateShillServiceToONCPart( |
| + entry_properties, |
| + &onc::kNetworkWithStateSignature)); |
| + |
| + std::string old_guid; |
| + if (!onc_part->GetStringWithoutPathExpansion(onc::network_config::kGUID, |
| + &old_guid)) { |
| + LOG(WARNING) << "Entry " << entry << " of profile " << profile_path_ |
| + << " doesn't contain a GUID."; |
| + // This might be an entry of an older ChromeOS version. Assume it to be |
| + // unmanaged. |
| + return; |
| + } |
| + |
| + scoped_ptr<NetworkUIData> ui_data = GetUIData(entry_properties); |
| + if (!ui_data) { |
| + VLOG(1) << "Entry " << entry << " of profile " << profile_path_ |
| + << " contains no or no valid UIData."; |
| + // This might be an entry of an older ChromeOS version. Assume it to be |
| + // unmanaged. |
| + return; |
| + } |
| + |
| + bool was_managed = |
| + (ui_data->onc_source() == onc::ONC_SOURCE_DEVICE_POLICY || |
| + ui_data->onc_source() == onc::ONC_SOURCE_USER_POLICY); |
| + |
| + // The relevant policy must have been initialized, otherwise we hadn't Run |
| + // this PolicyApplicator. |
| + const PolicyMap& policies_by_guid = |
| + *handler_->GetPoliciesForProfile(profile_path_); |
| + |
| + const base::DictionaryValue* new_policy = NULL; |
| + if (was_managed) { |
| + // If we have a GUID that might match a current policy, do a lookup using |
| + // that GUID at first. In particular this is necessary, as some networks |
| + // can't be matched to policies by properties (e.g. VPN). |
| + PolicyMap::const_iterator it = policies_by_guid.find(old_guid); |
| + if (it != policies_by_guid.end()) |
| + new_policy = it->second; |
| + } |
| + |
| + if (!new_policy) { |
| + // If we didn't find a policy by GUID, still a new policy might match. |
| + new_policy = FindMatchingPolicy(policies_by_guid, *onc_part); |
| + } |
| + |
| + if (new_policy) { |
| + std::string new_guid; |
| + new_policy->GetStringWithoutPathExpansion(onc::network_config::kGUID, |
| + &new_guid); |
| + |
| + VLOG_IF(1, was_managed && old_guid != new_guid) |
| + << "Updating configuration previously managed by policy " << old_guid |
| + << " with new policy " << new_guid << "."; |
| + VLOG_IF(1, !was_managed) |
| + << "Applying policy " << new_guid << " to previously unmanaged " |
| + << "configuration."; |
| + |
| + if (old_guid == new_guid && |
| + remaining_policies_.find(new_guid) == remaining_policies_.end()) { |
| + VLOG(1) << "Not updating existing managed configuration with guid " |
| + << new_guid << " because the policy didn't change."; |
| + } else { |
| + VLOG_IF(1, old_guid == new_guid) |
| + << "Updating previously managed configuration with the updated " |
| + << "policy " << new_guid << "."; |
| + |
| + // Update the existing configuration with the maybe changed |
| + // policy. Thereby the GUID might change. |
| + scoped_ptr<base::DictionaryValue> shill_dictionary = |
| + CreateShillConfiguration(profile_path_, new_guid, new_policy, |
| + ui_data->user_settings()); |
| + NetworkConfigurationHandler::Get()->CreateConfiguration( |
| + *shill_dictionary, |
| + base::Bind(&IgnoreString), |
| + base::Bind(&LogErrorWithDict, FROM_HERE)); |
| + remaining_policies_.erase(new_guid); |
| + } |
| + } else if (was_managed) { |
| + VLOG(1) << "Removing configuration previously managed by policy " |
| + << old_guid << ", because the policy was removed."; |
| + |
| + // Remove the entry, because the network was managed but isn't anymore. |
| + // Note: An alternative might be to preserve the user settings, but it's |
| + // unclear which values originating the policy should be removed. |
| + DeleteEntry(entry); |
| + } else { |
| + VLOG(2) << "Ignore unmanaged entry."; |
| + |
| + // The entry wasn't managed and doesn't match any current policy. Thus |
| + // leave it as it is. |
| + } |
| + } |
| + |
| + void DeleteEntry(const std::string& entry) { |
| + DBusThreadManager::Get()->GetShillProfileClient()->DeleteEntry( |
| + dbus::ObjectPath(profile_path_), |
| + entry, |
| + base::Bind(&base::DoNothing), |
| + base::Bind(&LogErrorMessage, FROM_HERE)); |
| + } |
| + |
| + virtual ~PolicyApplicator() { |
| + if (remaining_policies_.empty()) |
| + return; |
| + |
| + VLOG(2) << "Create new managed network configurations in profile" |
| + << profile_path_ << "."; |
| + // All profile entries were compared to policies. |configureGUIDs_| contains |
| + // all matched policies. From the remainder of policies, new configurations |
| + // have to be created. |
| + |
| + // The relevant policy must have been initialized, otherwise we hadn't Run |
| + // this PolicyApplicator. |
| + const PolicyMap& policies_by_guid = |
| + *handler_->GetPoliciesForProfile(profile_path_); |
| + |
| + for (std::set<std::string>::iterator it = remaining_policies_.begin(); |
| + it != remaining_policies_.end(); ++it) { |
| + PolicyMap::const_iterator policy_it = policies_by_guid.find(*it); |
| + if (policy_it == policies_by_guid.end()) { |
| + LOG(ERROR) << "Policy " << *it << " doesn't exist anymore."; |
| + continue; |
| + } |
| + |
| + const base::DictionaryValue* policy = policy_it->second; |
| + |
| + VLOG(1) << "Creating new configuration managed by policy " << *it |
| + << " in profile " << profile_path_ << "."; |
| + |
| + scoped_ptr<base::DictionaryValue> shill_dictionary = |
| + CreateShillConfiguration(profile_path_, *it, policy, NULL); |
| + NetworkConfigurationHandler::Get()->CreateConfiguration( |
| + *shill_dictionary, |
| + base::Bind(&IgnoreString), |
| + base::Bind(&LogErrorWithDict, FROM_HERE)); |
| + } |
| + } |
| + |
| + std::set<std::string> remaining_policies_; |
| + base::WeakPtr<ManagedNetworkConfigurationHandler> handler_; |
| + std::string profile_path_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(PolicyApplicator); |
| +}; |
| + |
| +void ManagedNetworkConfigurationHandler::SetPolicy( |
| + onc::ONCSource onc_source, |
| + const base::DictionaryValue& toplevel_onc) { |
| + VLOG(1) << "Setting policies for ONC source " |
| + << onc::GetSourceAsString(onc_source) << "."; |
| + |
| + // Validate the ONC dictionary. We are liberal and ignore unknown field |
| + // names and ignore invalid field names in kRecommended arrays. |
| + onc::Validator validator(false, // Ignore unknown fields. |
| + false, // Ignore invalid recommended field names. |
| + true, // Fail on missing fields. |
| + true); // This ONC comes from policy. |
| + validator.SetOncSource(onc_source); |
| + |
| + onc::Validator::Result validation_result; |
| + scoped_ptr<base::DictionaryValue> onc_validated = |
| + validator.ValidateAndRepairObject( |
| + &onc::kToplevelConfigurationSignature, |
| + toplevel_onc, |
| + &validation_result); |
| + |
| + if (validation_result == onc::Validator::VALID_WITH_WARNINGS) { |
| + LOG(WARNING) << "ONC from " << onc::GetSourceAsString(onc_source) |
| + << " produced warnings."; |
| + } else if (validation_result == onc::Validator::INVALID || |
| + onc_validated == NULL) { |
| + LOG(ERROR) << "ONC from " << onc::GetSourceAsString(onc_source) |
| + << " is invalid and couldn't be repaired."; |
| + return; |
| + } |
| + |
| + PolicyMap* policies; |
| + std::string profile; |
| + if (onc_source == chromeos::onc::ONC_SOURCE_USER_POLICY) { |
| + policies = &user_policies_by_guid_; |
| + profile = kUserProfilePath; |
| + user_policies_initialized_ = true; |
| + } else { |
| + policies = &device_policies_by_guid_; |
| + profile = kSharedProfilePath; |
| + device_policies_initialized_ = true; |
| + } |
| + |
| + PolicyMap old_policies; |
| + policies->swap(old_policies); |
| + |
| + // This stores all GUIDs of policies that have changed or are new. |
| + std::set<std::string> modified_policies; |
| + |
| + base::ListValue* network_configurations = NULL; |
| + onc_validated->GetListWithoutPathExpansion( |
| + onc::toplevel_config::kNetworkConfigurations, |
| + &network_configurations); |
| + |
| + if (network_configurations) { |
| + while (!network_configurations->empty()) { |
| + base::Value* network_value = NULL; |
| + // Passes ownership of network_value. |
| + network_configurations->Remove(network_configurations->GetSize() - 1, |
| + &network_value); |
| + const base::DictionaryValue* network = NULL; |
| + network_value->GetAsDictionary(&network); |
| + std::string guid; |
| + network->GetStringWithoutPathExpansion(onc::network_config::kGUID, |
| + &guid); |
| + |
| + const base::DictionaryValue* old_entry = old_policies[guid]; |
| + const base::DictionaryValue*& new_entry = (*policies)[guid]; |
| + if (new_entry) { |
| + LOG(ERROR) << "ONC from " << onc::GetSourceAsString(onc_source) |
| + << " contains several entries for the same GUID " |
| + << guid << "."; |
| + delete new_entry; |
| + } |
| + new_entry = network; |
| + |
| + if (!old_entry || !old_entry->Equals(new_entry)) { |
| + modified_policies.insert(guid); |
| + } |
| + } |
| + } |
| + |
| + STLDeleteValues(&old_policies); |
| + |
| + scoped_refptr<PolicyApplicator> applicator = new PolicyApplicator( |
| + weak_ptr_factory_.GetWeakPtr(), |
| + profile, |
| + &modified_policies); |
| + applicator->Run(); |
| +} |
| + |
| +const ManagedNetworkConfigurationHandler::PolicyMap* |
| +ManagedNetworkConfigurationHandler::GetPoliciesForProfile( |
| + const std::string& profile) const { |
| + if (profile == kSharedProfilePath) { |
| + if (device_policies_initialized_) |
| + return &device_policies_by_guid_; |
| + } else if (user_policies_initialized_) { |
| + return &user_policies_by_guid_; |
| + } |
| + return NULL; |
| +} |
| + |
| +ManagedNetworkConfigurationHandler::ManagedNetworkConfigurationHandler() |
| + : user_policies_initialized_(false), |
| + device_policies_initialized_(false), |
| + weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { |
| } |
| ManagedNetworkConfigurationHandler::~ManagedNetworkConfigurationHandler() { |
| + STLDeleteValues(&user_policies_by_guid_); |
| + STLDeleteValues(&device_policies_by_guid_); |
| } |
| } // namespace chromeos |