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