| // 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 <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_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 base::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) { |
| auto it = policies.find(guid); |
| if (it == policies.end()) |
| return NULL; |
| return it->second.get(); |
| } |
| |
| } // namespace |
| |
| PolicyApplicator::PolicyApplicator( |
| const NetworkProfile& profile, |
| const GuidToPolicyMap& all_policies, |
| const base::DictionaryValue& global_network_config, |
| ConfigurationHandler* handler, |
| std::set<std::string>* modified_policies) |
| : handler_(handler), profile_(profile), weak_ptr_factory_(this) { |
| global_network_config_.MergeDictionary(&global_network_config); |
| remaining_policies_.swap(*modified_policies); |
| for (const auto& policy_pair : all_policies) { |
| all_policies_.insert(std::make_pair(policy_pair.first, |
| policy_pair.second->CreateDeepCopy())); |
| } |
| } |
| |
| PolicyApplicator::~PolicyApplicator() { |
| 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(); |
| |
| std::unique_ptr<base::DictionaryValue> 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. |
| } |
| |
| std::unique_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->GetUserSettingsDictionary() : nullptr; |
| std::unique_ptr<base::DictionaryValue> 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::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.CreateDeepCopy()); |
| else |
| handler_->CreateConfigurationFromPolicy(shill_dictionary); |
| } |
| |
| void PolicyApplicator::ApplyRemainingPolicies() { |
| DCHECK(pending_get_entry_calls_.empty()); |
| |
| // Write all queued configurations now. |
| for (const auto& configuration : new_shill_configurations_) { |
| handler_->CreateConfigurationFromPolicy(*configuration); |
| } |
| 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<std::string>::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() << "."; |
| |
| std::unique_ptr<base::DictionaryValue> 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 |