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
27namespace chromeos {
28
29namespace {
30
31void 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
37const 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
48PolicyApplicator::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
63void 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
70void 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
101void 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
240void 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
248void 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
274PolicyApplicator::~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
283void 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