Home | History | Annotate | Download | only in network
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chromeos/network/policy_applicator.h"
      6 
      7 #include <utility>
      8 
      9 #include "base/bind.h"
     10 #include "base/location.h"
     11 #include "base/logging.h"
     12 #include "base/memory/scoped_ptr.h"
     13 #include "base/stl_util.h"
     14 #include "base/values.h"
     15 #include "chromeos/dbus/dbus_thread_manager.h"
     16 #include "chromeos/dbus/shill_profile_client.h"
     17 #include "chromeos/network/network_type_pattern.h"
     18 #include "chromeos/network/network_ui_data.h"
     19 #include "chromeos/network/onc/onc_signature.h"
     20 #include "chromeos/network/onc/onc_translator.h"
     21 #include "chromeos/network/policy_util.h"
     22 #include "chromeos/network/shill_property_util.h"
     23 #include "components/onc/onc_constants.h"
     24 #include "dbus/object_path.h"
     25 #include "third_party/cros_system_api/dbus/service_constants.h"
     26 
     27 namespace chromeos {
     28 
     29 namespace {
     30 
     31 void LogErrorMessage(const tracked_objects::Location& from_where,
     32                      const std::string& error_name,
     33                      const std::string& error_message) {
     34   LOG(ERROR) << from_where.ToString() << ": " << error_message;
     35 }
     36 
     37 const base::DictionaryValue* GetByGUID(
     38     const PolicyApplicator::GuidToPolicyMap& policies,
     39     const std::string& guid) {
     40   PolicyApplicator::GuidToPolicyMap::const_iterator it = policies.find(guid);
     41   if (it == policies.end())
     42     return NULL;
     43   return it->second;
     44 }
     45 
     46 }  // namespace
     47 
     48 PolicyApplicator::PolicyApplicator(
     49     base::WeakPtr<ConfigurationHandler> handler,
     50     const NetworkProfile& profile,
     51     const GuidToPolicyMap& all_policies,
     52     const base::DictionaryValue& global_network_config,
     53     std::set<std::string>* modified_policies)
     54     : handler_(handler), profile_(profile) {
     55   global_network_config_.MergeDictionary(&global_network_config);
     56   remaining_policies_.swap(*modified_policies);
     57   for (GuidToPolicyMap::const_iterator it = all_policies.begin();
     58        it != all_policies.end(); ++it) {
     59     all_policies_.insert(std::make_pair(it->first, it->second->DeepCopy()));
     60   }
     61 }
     62 
     63 void PolicyApplicator::Run() {
     64   DBusThreadManager::Get()->GetShillProfileClient()->GetProperties(
     65       dbus::ObjectPath(profile_.path),
     66       base::Bind(&PolicyApplicator::GetProfilePropertiesCallback, this),
     67       base::Bind(&LogErrorMessage, FROM_HERE));
     68 }
     69 
     70 void PolicyApplicator::GetProfilePropertiesCallback(
     71     const base::DictionaryValue& profile_properties) {
     72   if (!handler_) {
     73     LOG(WARNING) << "Handler destructed during policy application to profile "
     74                  << profile_.ToDebugString();
     75     return;
     76   }
     77 
     78   VLOG(2) << "Received properties for profile " << profile_.ToDebugString();
     79   const base::ListValue* entries = NULL;
     80   if (!profile_properties.GetListWithoutPathExpansion(
     81            shill::kEntriesProperty, &entries)) {
     82     LOG(ERROR) << "Profile " << profile_.ToDebugString()
     83                << " doesn't contain the property "
     84                << shill::kEntriesProperty;
     85     return;
     86   }
     87 
     88   for (base::ListValue::const_iterator it = entries->begin();
     89        it != entries->end(); ++it) {
     90     std::string entry;
     91     (*it)->GetAsString(&entry);
     92 
     93     DBusThreadManager::Get()->GetShillProfileClient()->GetEntry(
     94         dbus::ObjectPath(profile_.path),
     95         entry,
     96         base::Bind(&PolicyApplicator::GetEntryCallback, this, entry),
     97         base::Bind(&LogErrorMessage, FROM_HERE));
     98   }
     99 }
    100 
    101 void PolicyApplicator::GetEntryCallback(
    102     const std::string& entry,
    103     const base::DictionaryValue& entry_properties) {
    104   if (!handler_) {
    105     LOG(WARNING) << "Handler destructed during policy application to profile "
    106                  << profile_.ToDebugString();
    107     return;
    108   }
    109 
    110   VLOG(2) << "Received properties for entry " << entry << " of profile "
    111           << profile_.ToDebugString();
    112 
    113   scoped_ptr<base::DictionaryValue> onc_part(
    114       onc::TranslateShillServiceToONCPart(entry_properties,
    115                                           ::onc::ONC_SOURCE_UNKNOWN,
    116                                           &onc::kNetworkWithStateSignature));
    117 
    118   std::string old_guid;
    119   if (!onc_part->GetStringWithoutPathExpansion(::onc::network_config::kGUID,
    120                                                &old_guid)) {
    121     VLOG(1) << "Entry " << entry << " of profile " << profile_.ToDebugString()
    122             << " doesn't contain a GUID.";
    123     // This might be an entry of an older ChromeOS version. Assume it to be
    124     // unmanaged.
    125   }
    126 
    127   scoped_ptr<NetworkUIData> ui_data =
    128       shill_property_util::GetUIDataFromProperties(entry_properties);
    129   if (!ui_data) {
    130     VLOG(1) << "Entry " << entry << " of profile " << profile_.ToDebugString()
    131             << " contains no or no valid UIData.";
    132     // This might be an entry of an older ChromeOS version. Assume it to be
    133     // unmanaged. It's an inconsistency if there is a GUID but no UIData, thus
    134     // clear the GUID just in case.
    135     old_guid.clear();
    136   }
    137 
    138   bool was_managed = !old_guid.empty() && ui_data &&
    139                      (ui_data->onc_source() ==
    140                           ::onc::ONC_SOURCE_DEVICE_POLICY ||
    141                       ui_data->onc_source() == ::onc::ONC_SOURCE_USER_POLICY);
    142 
    143   const base::DictionaryValue* new_policy = NULL;
    144   if (was_managed) {
    145     // If we have a GUID that might match a current policy, do a lookup using
    146     // that GUID at first. In particular this is necessary, as some networks
    147     // can't be matched to policies by properties (e.g. VPN).
    148     new_policy = GetByGUID(all_policies_, old_guid);
    149   }
    150 
    151   if (!new_policy) {
    152     // If we didn't find a policy by GUID, still a new policy might match.
    153     new_policy = policy_util::FindMatchingPolicy(all_policies_, *onc_part);
    154   }
    155 
    156   if (new_policy) {
    157     std::string new_guid;
    158     new_policy->GetStringWithoutPathExpansion(::onc::network_config::kGUID,
    159                                               &new_guid);
    160 
    161     VLOG_IF(1, was_managed && old_guid != new_guid)
    162         << "Updating configuration previously managed by policy " << old_guid
    163         << " with new policy " << new_guid << ".";
    164     VLOG_IF(1, !was_managed) << "Applying policy " << new_guid
    165                              << " to previously unmanaged "
    166                              << "configuration.";
    167 
    168     if (old_guid == new_guid &&
    169         remaining_policies_.find(new_guid) == remaining_policies_.end()) {
    170       VLOG(1) << "Not updating existing managed configuration with guid "
    171               << new_guid << " because the policy didn't change.";
    172     } else {
    173       const base::DictionaryValue* user_settings =
    174           ui_data ? ui_data->user_settings() : NULL;
    175       scoped_ptr<base::DictionaryValue> new_shill_properties =
    176           policy_util::CreateShillConfiguration(profile_,
    177                                                 new_guid,
    178                                                 &global_network_config_,
    179                                                 new_policy,
    180                                                 user_settings);
    181       // A new policy has to be applied to this profile entry. In order to keep
    182       // implicit state of Shill like "connected successfully before", keep the
    183       // entry if a policy is reapplied (e.g. after reboot) or is updated.
    184       // However, some Shill properties are used to identify the network and
    185       // cannot be modified after initial configuration, so we have to delete
    186       // the profile entry in these cases. Also, keeping Shill's state if the
    187       // SSID changed might not be a good idea anyways. If the policy GUID
    188       // changed, or there was no policy before, we delete the entry at first to
    189       // ensure that no old configuration remains.
    190       if (old_guid == new_guid &&
    191           shill_property_util::DoIdentifyingPropertiesMatch(
    192               *new_shill_properties, entry_properties)) {
    193         VLOG(1) << "Updating previously managed configuration with the "
    194                 << "updated policy " << new_guid << ".";
    195       } else {
    196         VLOG(1) << "Deleting profile entry before writing new policy "
    197                 << new_guid << " because of identifying properties changed.";
    198         DeleteEntry(entry);
    199       }
    200 
    201       // In general, old entries should at first be deleted before new
    202       // configurations are written to prevent inconsistencies. Therefore, we
    203       // delay the writing of the new config here until ~PolicyApplicator.
    204       // E.g. one problematic case is if a policy { {GUID=X, SSID=Y} } is
    205       // applied to the profile entries
    206       // { ENTRY1 = {GUID=X, SSID=X, USER_SETTINGS=X},
    207       //   ENTRY2 = {SSID=Y, ... } }.
    208       // At first ENTRY1 and ENTRY2 should be removed, then the new config be
    209       // written and the result should be:
    210       // { {GUID=X, SSID=Y, USER_SETTINGS=X} }
    211       WriteNewShillConfiguration(
    212           *new_shill_properties, *new_policy, true /* write later */);
    213       remaining_policies_.erase(new_guid);
    214     }
    215   } else if (was_managed) {
    216     VLOG(1) << "Removing configuration previously managed by policy "
    217             << old_guid << ", because the policy was removed.";
    218 
    219     // Remove the entry, because the network was managed but isn't anymore.
    220     // Note: An alternative might be to preserve the user settings, but it's
    221     // unclear which values originating the policy should be removed.
    222     DeleteEntry(entry);
    223   } else {
    224     // The entry wasn't managed and doesn't match any current policy. Global
    225     // network settings have to be applied.
    226     base::DictionaryValue shill_properties_to_update;
    227     policy_util::SetShillPropertiesForGlobalPolicy(
    228         entry_properties, global_network_config_, &shill_properties_to_update);
    229     if (shill_properties_to_update.empty()) {
    230       VLOG(2) << "Ignore unmanaged entry.";
    231       // Calling a SetProperties of Shill with an empty dictionary is a no op.
    232     } else {
    233       VLOG(2) << "Apply global network config to unmanaged entry.";
    234       handler_->UpdateExistingConfigurationWithPropertiesFromPolicy(
    235           entry_properties, shill_properties_to_update);
    236     }
    237   }
    238 }
    239 
    240 void PolicyApplicator::DeleteEntry(const std::string& entry) {
    241   DBusThreadManager::Get()->GetShillProfileClient()->DeleteEntry(
    242       dbus::ObjectPath(profile_.path),
    243       entry,
    244       base::Bind(&base::DoNothing),
    245       base::Bind(&LogErrorMessage, FROM_HERE));
    246 }
    247 
    248 void PolicyApplicator::WriteNewShillConfiguration(
    249     const base::DictionaryValue& shill_dictionary,
    250     const base::DictionaryValue& policy,
    251     bool write_later) {
    252   // Ethernet (non EAP) settings, like GUID or UIData, cannot be stored per
    253   // user. Abort in that case.
    254   std::string type;
    255   policy.GetStringWithoutPathExpansion(::onc::network_config::kType, &type);
    256   if (type == ::onc::network_type::kEthernet &&
    257       profile_.type() == NetworkProfile::TYPE_USER) {
    258     const base::DictionaryValue* ethernet = NULL;
    259     policy.GetDictionaryWithoutPathExpansion(::onc::network_config::kEthernet,
    260                                              &ethernet);
    261     std::string auth;
    262     ethernet->GetStringWithoutPathExpansion(::onc::ethernet::kAuthentication,
    263                                             &auth);
    264     if (auth == ::onc::ethernet::kAuthenticationNone)
    265       return;
    266   }
    267 
    268   if (write_later)
    269     new_shill_configurations_.push_back(shill_dictionary.DeepCopy());
    270   else
    271     handler_->CreateConfigurationFromPolicy(shill_dictionary);
    272 }
    273 
    274 PolicyApplicator::~PolicyApplicator() {
    275   ApplyRemainingPolicies();
    276   STLDeleteValues(&all_policies_);
    277   // Notify the handler about all policies being applied, so that the network
    278   // lists can be updated.
    279   if (handler_)
    280     handler_->OnPoliciesApplied();
    281 }
    282 
    283 void PolicyApplicator::ApplyRemainingPolicies() {
    284   if (!handler_) {
    285     LOG(WARNING) << "Handler destructed during policy application to profile "
    286                  << profile_.ToDebugString();
    287     return;
    288   }
    289 
    290   // Write all queued configurations now.
    291   for (ScopedVector<base::DictionaryValue>::const_iterator it =
    292            new_shill_configurations_.begin();
    293        it != new_shill_configurations_.end();
    294        ++it) {
    295     handler_->CreateConfigurationFromPolicy(**it);
    296   }
    297 
    298   if (remaining_policies_.empty())
    299     return;
    300 
    301   VLOG(2) << "Create new managed network configurations in profile"
    302           << profile_.ToDebugString() << ".";
    303   // All profile entries were compared to policies. |remaining_policies_|
    304   // contains all modified policies that didn't match any entry. For these
    305   // remaining policies, new configurations have to be created.
    306   for (std::set<std::string>::iterator it = remaining_policies_.begin();
    307        it != remaining_policies_.end(); ++it) {
    308     const base::DictionaryValue* network_policy = GetByGUID(all_policies_, *it);
    309     DCHECK(network_policy);
    310 
    311     VLOG(1) << "Creating new configuration managed by policy " << *it
    312             << " in profile " << profile_.ToDebugString() << ".";
    313 
    314     scoped_ptr<base::DictionaryValue> shill_dictionary =
    315         policy_util::CreateShillConfiguration(profile_,
    316                                               *it,
    317                                               &global_network_config_,
    318                                               network_policy,
    319                                               NULL /* no user settings */);
    320     WriteNewShillConfiguration(
    321         *shill_dictionary, *network_policy, false /* write now */);
    322   }
    323 }
    324 
    325 }  // namespace chromeos
    326