// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chromeos/network/policy_applicator.h" #include <utility> #include "base/bind.h" #include "base/location.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/stl_util.h" #include "base/values.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/shill_profile_client.h" #include "chromeos/network/network_type_pattern.h" #include "chromeos/network/network_ui_data.h" #include "chromeos/network/onc/onc_signature.h" #include "chromeos/network/onc/onc_translator.h" #include "chromeos/network/policy_util.h" #include "chromeos/network/shill_property_util.h" #include "components/onc/onc_constants.h" #include "dbus/object_path.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace chromeos { namespace { 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; } const base::DictionaryValue* GetByGUID( const PolicyApplicator::GuidToPolicyMap& policies, const std::string& guid) { PolicyApplicator::GuidToPolicyMap::const_iterator it = policies.find(guid); if (it == policies.end()) return NULL; return it->second; } } // namespace PolicyApplicator::PolicyApplicator( base::WeakPtr<ConfigurationHandler> handler, const NetworkProfile& profile, const GuidToPolicyMap& all_policies, const base::DictionaryValue& global_network_config, std::set<std::string>* modified_policies) : handler_(handler), profile_(profile) { global_network_config_.MergeDictionary(&global_network_config); remaining_policies_.swap(*modified_policies); for (GuidToPolicyMap::const_iterator it = all_policies.begin(); it != all_policies.end(); ++it) { all_policies_.insert(std::make_pair(it->first, it->second->DeepCopy())); } } void PolicyApplicator::Run() { DBusThreadManager::Get()->GetShillProfileClient()->GetProperties( dbus::ObjectPath(profile_.path), base::Bind(&PolicyApplicator::GetProfilePropertiesCallback, this), base::Bind(&LogErrorMessage, FROM_HERE)); } void PolicyApplicator::GetProfilePropertiesCallback( const base::DictionaryValue& profile_properties) { if (!handler_) { LOG(WARNING) << "Handler destructed during policy application to profile " << profile_.ToDebugString(); return; } VLOG(2) << "Received properties for profile " << profile_.ToDebugString(); const base::ListValue* entries = NULL; if (!profile_properties.GetListWithoutPathExpansion( shill::kEntriesProperty, &entries)) { LOG(ERROR) << "Profile " << profile_.ToDebugString() << " doesn't contain the property " << shill::kEntriesProperty; return; } for (base::ListValue::const_iterator it = entries->begin(); it != entries->end(); ++it) { std::string entry; (*it)->GetAsString(&entry); DBusThreadManager::Get()->GetShillProfileClient()->GetEntry( dbus::ObjectPath(profile_.path), entry, base::Bind(&PolicyApplicator::GetEntryCallback, this, entry), base::Bind(&LogErrorMessage, FROM_HERE)); } } void PolicyApplicator::GetEntryCallback( const std::string& entry, const base::DictionaryValue& entry_properties) { if (!handler_) { LOG(WARNING) << "Handler destructed during policy application to profile " << profile_.ToDebugString(); return; } VLOG(2) << "Received properties for entry " << entry << " of profile " << profile_.ToDebugString(); 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)) { VLOG(1) << "Entry " << entry << " of profile " << profile_.ToDebugString() << " doesn't contain a GUID."; // This might be an entry of an older ChromeOS version. Assume it to be // unmanaged. } scoped_ptr<NetworkUIData> ui_data = shill_property_util::GetUIDataFromProperties(entry_properties); if (!ui_data) { VLOG(1) << "Entry " << entry << " of profile " << profile_.ToDebugString() << " contains no or no valid UIData."; // This might be an entry of an older ChromeOS version. Assume it to be // unmanaged. It's an inconsistency if there is a GUID but no UIData, thus // clear the GUID just in case. old_guid.clear(); } bool was_managed = !old_guid.empty() && ui_data && (ui_data->onc_source() == ::onc::ONC_SOURCE_DEVICE_POLICY || ui_data->onc_source() == ::onc::ONC_SOURCE_USER_POLICY); 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). new_policy = GetByGUID(all_policies_, old_guid); } if (!new_policy) { // If we didn't find a policy by GUID, still a new policy might match. new_policy = policy_util::FindMatchingPolicy(all_policies_, *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 { const base::DictionaryValue* user_settings = ui_data ? ui_data->user_settings() : NULL; scoped_ptr<base::DictionaryValue> new_shill_properties = policy_util::CreateShillConfiguration( profile_, new_guid, new_policy, user_settings); // A new policy has to be applied to this profile entry. In order to keep // implicit state of Shill like "connected successfully before", keep the // entry if a policy is reapplied (e.g. after reboot) or is updated. // However, some Shill properties are used to identify the network and // cannot be modified after initial configuration, so we have to delete // the profile entry in these cases. Also, keeping Shill's state if the // SSID changed might not be a good idea anyways. If the policy GUID // changed, or there was no policy before, we delete the entry at first to // ensure that no old configuration remains. if (old_guid == new_guid && shill_property_util::DoIdentifyingPropertiesMatch( *new_shill_properties, entry_properties)) { VLOG(1) << "Updating previously managed configuration with the " << "updated policy " << new_guid << "."; } else { VLOG(1) << "Deleting profile entry before writing new policy " << new_guid << " because of identifying properties changed."; DeleteEntry(entry); } // In general, old entries should at first be deleted before new // configurations are written to prevent inconsistencies. Therefore, we // delay the writing of the new config here until ~PolicyApplicator. // E.g. one problematic case is if a policy { {GUID=X, SSID=Y} } is // applied to the profile entries // { ENTRY1 = {GUID=X, SSID=X, USER_SETTINGS=X}, // ENTRY2 = {SSID=Y, ... } }. // At first ENTRY1 and ENTRY2 should be removed, then the new config be // written and the result should be: // { {GUID=X, SSID=Y, USER_SETTINGS=X} } WriteNewShillConfiguration(*new_shill_properties, *new_policy, true); 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 { // The entry wasn't managed and doesn't match any current policy. Global // network settings have to be applied. base::DictionaryValue shill_properties_to_update; GetPropertiesForUnmanagedEntry(entry_properties, &shill_properties_to_update); if (shill_properties_to_update.empty()) { VLOG(2) << "Ignore unmanaged entry."; // Calling a SetProperties of Shill with an empty dictionary is a no op. } else { VLOG(2) << "Apply global network config to unmanaged entry."; handler_->UpdateExistingConfigurationWithPropertiesFromPolicy( entry_properties, shill_properties_to_update); } } } void PolicyApplicator::DeleteEntry(const std::string& entry) { DBusThreadManager::Get()->GetShillProfileClient()->DeleteEntry( dbus::ObjectPath(profile_.path), entry, base::Bind(&base::DoNothing), base::Bind(&LogErrorMessage, FROM_HERE)); } void PolicyApplicator::WriteNewShillConfiguration( const base::DictionaryValue& shill_dictionary, const base::DictionaryValue& policy, bool write_later) { // Ethernet (non EAP) settings, like GUID or UIData, cannot be stored per // user. Abort in that case. std::string type; policy.GetStringWithoutPathExpansion(::onc::network_config::kType, &type); if (type == ::onc::network_type::kEthernet && profile_.type() == NetworkProfile::TYPE_USER) { const base::DictionaryValue* ethernet = NULL; policy.GetDictionaryWithoutPathExpansion(::onc::network_config::kEthernet, ðernet); std::string auth; ethernet->GetStringWithoutPathExpansion(::onc::ethernet::kAuthentication, &auth); if (auth == ::onc::ethernet::kNone) return; } if (write_later) new_shill_configurations_.push_back(shill_dictionary.DeepCopy()); else handler_->CreateConfigurationFromPolicy(shill_dictionary); } void PolicyApplicator::GetPropertiesForUnmanagedEntry( const base::DictionaryValue& entry_properties, base::DictionaryValue* properties_to_update) const { // kAllowOnlyPolicyNetworksToAutoconnect is currently the only global config. std::string type; entry_properties.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); if (NetworkTypePattern::Ethernet().MatchesType(type)) return; // Autoconnect for Ethernet cannot be configured. // By default all networks are allowed to autoconnect. bool only_policy_autoconnect = false; global_network_config_.GetBooleanWithoutPathExpansion( ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect, &only_policy_autoconnect); if (!only_policy_autoconnect) return; bool old_autoconnect = false; if (entry_properties.GetBooleanWithoutPathExpansion( shill::kAutoConnectProperty, &old_autoconnect) && !old_autoconnect) { // Autoconnect is already explictly disabled. No need to set it again. return; } // If autconnect is not explicitly set yet, it might automatically be enabled // by Shill. To prevent that, disable it explicitly. properties_to_update->SetBooleanWithoutPathExpansion( shill::kAutoConnectProperty, false); } PolicyApplicator::~PolicyApplicator() { ApplyRemainingPolicies(); STLDeleteValues(&all_policies_); // Notify the handler about all policies being applied, so that the network // lists can be updated. if (handler_) handler_->OnPoliciesApplied(); } void PolicyApplicator::ApplyRemainingPolicies() { if (!handler_) { LOG(WARNING) << "Handler destructed during policy application to profile " << profile_.ToDebugString(); return; } // Write all queued configurations now. for (ScopedVector<base::DictionaryValue>::const_iterator it = new_shill_configurations_.begin(); it != new_shill_configurations_.end(); ++it) { handler_->CreateConfigurationFromPolicy(**it); } if (remaining_policies_.empty()) return; VLOG(2) << "Create new managed network configurations in profile" << profile_.ToDebugString() << "."; // All profile entries were compared to policies. |remaining_policies_| // contains all modified policies that didn't match any entry. For these // remaining policies, new configurations have to be created. for (std::set<std::string>::iterator it = remaining_policies_.begin(); it != remaining_policies_.end(); ++it) { const base::DictionaryValue* policy = GetByGUID(all_policies_, *it); DCHECK(policy); VLOG(1) << "Creating new configuration managed by policy " << *it << " in profile " << profile_.ToDebugString() << "."; scoped_ptr<base::DictionaryValue> shill_dictionary = policy_util::CreateShillConfiguration(profile_, *it, policy, NULL); WriteNewShillConfiguration(*shill_dictionary, *policy, false); } } } // namespace chromeos