// 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 #include "base/bind.h" #include "base/location.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/stl_util.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( const NetworkProfile& profile, const GuidToPolicyMap& all_policies, const base::DictionaryValue& global_network_config, ConfigurationHandler* handler, std::set* modified_policies) : handler_(handler), profile_(profile), weak_ptr_factory_(this) { 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())); } } PolicyApplicator::~PolicyApplicator() { STLDeleteValues(&all_policies_); VLOG(1) << "Destroying PolicyApplicator for " << profile_.userhash; } void PolicyApplicator::Run() { DBusThreadManager::Get()->GetShillProfileClient()->GetProperties( dbus::ObjectPath(profile_.path), base::Bind(&PolicyApplicator::GetProfilePropertiesCallback, weak_ptr_factory_.GetWeakPtr()), base::Bind(&PolicyApplicator::GetProfilePropertiesError, weak_ptr_factory_.GetWeakPtr())); } void PolicyApplicator::ProfileEntryFinished(const std::string& entry) { pending_get_entry_calls_.erase(entry); if (pending_get_entry_calls_.empty()) { ApplyRemainingPolicies(); NotifyConfigurationHandlerAndFinish(); } } void PolicyApplicator::GetProfilePropertiesCallback( const base::DictionaryValue& profile_properties) { 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; NotifyConfigurationHandlerAndFinish(); return; } for (base::ListValue::const_iterator it = entries->begin(); it != entries->end(); ++it) { std::string entry; (*it)->GetAsString(&entry); pending_get_entry_calls_.insert(entry); DBusThreadManager::Get()->GetShillProfileClient()->GetEntry( dbus::ObjectPath(profile_.path), entry, base::Bind(&PolicyApplicator::GetEntryCallback, weak_ptr_factory_.GetWeakPtr(), entry), base::Bind(&PolicyApplicator::GetEntryError, weak_ptr_factory_.GetWeakPtr(), entry)); } if (pending_get_entry_calls_.empty()) { ApplyRemainingPolicies(); NotifyConfigurationHandlerAndFinish(); } } void PolicyApplicator::GetProfilePropertiesError( const std::string& error_name, const std::string& error_message) { LOG(ERROR) << "Could not retrieve properties of profile " << profile_.path << ": " << error_message; NotifyConfigurationHandlerAndFinish(); } void PolicyApplicator::GetEntryCallback( const std::string& entry, const base::DictionaryValue& entry_properties) { VLOG(2) << "Received properties for entry " << entry << " of profile " << profile_.ToDebugString(); scoped_ptr onc_part( onc::TranslateShillServiceToONCPart( entry_properties, ::onc::ONC_SOURCE_UNKNOWN, &onc::kNetworkWithStateSignature, nullptr /* network_state */)); 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 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 new_shill_properties = policy_util::CreateShillConfiguration(profile_, new_guid, &global_network_config_, 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 /* write later */); 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; policy_util::SetShillPropertiesForGlobalPolicy( entry_properties, global_network_config_, &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); } } ProfileEntryFinished(entry); } void PolicyApplicator::GetEntryError(const std::string& entry, const std::string& error_name, const std::string& error_message) { LOG(ERROR) << "Could not retrieve entry " << entry << " of profile " << profile_.path << ": " << error_message; ProfileEntryFinished(entry); } 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::kAuthenticationNone) return; } if (write_later) new_shill_configurations_.push_back(shill_dictionary.DeepCopy()); else handler_->CreateConfigurationFromPolicy(shill_dictionary); } void PolicyApplicator::ApplyRemainingPolicies() { DCHECK(pending_get_entry_calls_.empty()); // Write all queued configurations now. for (ScopedVector::const_iterator it = new_shill_configurations_.begin(); it != new_shill_configurations_.end(); ++it) { handler_->CreateConfigurationFromPolicy(**it); } new_shill_configurations_.clear(); VLOG_IF(2, !remaining_policies_.empty()) << "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::iterator it = remaining_policies_.begin(); it != remaining_policies_.end(); ++it) { const base::DictionaryValue* network_policy = GetByGUID(all_policies_, *it); DCHECK(network_policy); VLOG(1) << "Creating new configuration managed by policy " << *it << " in profile " << profile_.ToDebugString() << "."; scoped_ptr shill_dictionary = policy_util::CreateShillConfiguration(profile_, *it, &global_network_config_, network_policy, NULL /* no user settings */); WriteNewShillConfiguration( *shill_dictionary, *network_policy, false /* write now */); } remaining_policies_.clear(); } void PolicyApplicator::NotifyConfigurationHandlerAndFinish() { weak_ptr_factory_.InvalidateWeakPtrs(); handler_->OnPoliciesApplied(profile_); } } // namespace chromeos