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.
5 #include "chromeos/network/policy_applicator.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"
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
;
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())
48 PolicyApplicator::PolicyApplicator(
49 const NetworkProfile
& profile
,
50 const GuidToPolicyMap
& all_policies
,
51 const base::DictionaryValue
& global_network_config
,
52 ConfigurationHandler
* handler
,
53 std::set
<std::string
>* modified_policies
)
54 : handler_(handler
), profile_(profile
), weak_ptr_factory_(this) {
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()));
63 PolicyApplicator::~PolicyApplicator() {
64 STLDeleteValues(&all_policies_
);
65 VLOG(1) << "Destroying PolicyApplicator for " << profile_
.userhash
;
68 void PolicyApplicator::Run() {
69 DBusThreadManager::Get()->GetShillProfileClient()->GetProperties(
70 dbus::ObjectPath(profile_
.path
),
71 base::Bind(&PolicyApplicator::GetProfilePropertiesCallback
,
72 weak_ptr_factory_
.GetWeakPtr()),
73 base::Bind(&PolicyApplicator::GetProfilePropertiesError
,
74 weak_ptr_factory_
.GetWeakPtr()));
77 void PolicyApplicator::ProfileEntryFinished(const std::string
& entry
) {
78 pending_get_entry_calls_
.erase(entry
);
79 if (pending_get_entry_calls_
.empty()) {
80 ApplyRemainingPolicies();
81 NotifyConfigurationHandlerAndFinish();
85 void PolicyApplicator::GetProfilePropertiesCallback(
86 const base::DictionaryValue
& profile_properties
) {
87 VLOG(2) << "Received properties for profile " << profile_
.ToDebugString();
88 const base::ListValue
* entries
= NULL
;
89 if (!profile_properties
.GetListWithoutPathExpansion(
90 shill::kEntriesProperty
, &entries
)) {
91 LOG(ERROR
) << "Profile " << profile_
.ToDebugString()
92 << " doesn't contain the property "
93 << shill::kEntriesProperty
;
94 NotifyConfigurationHandlerAndFinish();
98 for (base::ListValue::const_iterator it
= entries
->begin();
99 it
!= entries
->end(); ++it
) {
101 (*it
)->GetAsString(&entry
);
103 pending_get_entry_calls_
.insert(entry
);
104 DBusThreadManager::Get()->GetShillProfileClient()->GetEntry(
105 dbus::ObjectPath(profile_
.path
),
107 base::Bind(&PolicyApplicator::GetEntryCallback
,
108 weak_ptr_factory_
.GetWeakPtr(),
110 base::Bind(&PolicyApplicator::GetEntryError
,
111 weak_ptr_factory_
.GetWeakPtr(),
114 if (pending_get_entry_calls_
.empty()) {
115 ApplyRemainingPolicies();
116 NotifyConfigurationHandlerAndFinish();
120 void PolicyApplicator::GetProfilePropertiesError(
121 const std::string
& error_name
,
122 const std::string
& error_message
) {
123 LOG(ERROR
) << "Could not retrieve properties of profile " << profile_
.path
124 << ": " << error_message
;
125 NotifyConfigurationHandlerAndFinish();
128 void PolicyApplicator::GetEntryCallback(
129 const std::string
& entry
,
130 const base::DictionaryValue
& entry_properties
) {
131 VLOG(2) << "Received properties for entry " << entry
<< " of profile "
132 << profile_
.ToDebugString();
134 scoped_ptr
<base::DictionaryValue
> onc_part(
135 onc::TranslateShillServiceToONCPart(entry_properties
,
136 ::onc::ONC_SOURCE_UNKNOWN
,
137 &onc::kNetworkWithStateSignature
));
139 std::string old_guid
;
140 if (!onc_part
->GetStringWithoutPathExpansion(::onc::network_config::kGUID
,
142 VLOG(1) << "Entry " << entry
<< " of profile " << profile_
.ToDebugString()
143 << " doesn't contain a GUID.";
144 // This might be an entry of an older ChromeOS version. Assume it to be
148 scoped_ptr
<NetworkUIData
> ui_data
=
149 shill_property_util::GetUIDataFromProperties(entry_properties
);
151 VLOG(1) << "Entry " << entry
<< " of profile " << profile_
.ToDebugString()
152 << " contains no or no valid UIData.";
153 // This might be an entry of an older ChromeOS version. Assume it to be
154 // unmanaged. It's an inconsistency if there is a GUID but no UIData, thus
155 // clear the GUID just in case.
159 bool was_managed
= !old_guid
.empty() && ui_data
&&
160 (ui_data
->onc_source() ==
161 ::onc::ONC_SOURCE_DEVICE_POLICY
||
162 ui_data
->onc_source() == ::onc::ONC_SOURCE_USER_POLICY
);
164 const base::DictionaryValue
* new_policy
= NULL
;
166 // If we have a GUID that might match a current policy, do a lookup using
167 // that GUID at first. In particular this is necessary, as some networks
168 // can't be matched to policies by properties (e.g. VPN).
169 new_policy
= GetByGUID(all_policies_
, old_guid
);
173 // If we didn't find a policy by GUID, still a new policy might match.
174 new_policy
= policy_util::FindMatchingPolicy(all_policies_
, *onc_part
);
178 std::string new_guid
;
179 new_policy
->GetStringWithoutPathExpansion(::onc::network_config::kGUID
,
182 VLOG_IF(1, was_managed
&& old_guid
!= new_guid
)
183 << "Updating configuration previously managed by policy " << old_guid
184 << " with new policy " << new_guid
<< ".";
185 VLOG_IF(1, !was_managed
) << "Applying policy " << new_guid
186 << " to previously unmanaged "
189 if (old_guid
== new_guid
&&
190 remaining_policies_
.find(new_guid
) == remaining_policies_
.end()) {
191 VLOG(1) << "Not updating existing managed configuration with guid "
192 << new_guid
<< " because the policy didn't change.";
194 const base::DictionaryValue
* user_settings
=
195 ui_data
? ui_data
->user_settings() : NULL
;
196 scoped_ptr
<base::DictionaryValue
> new_shill_properties
=
197 policy_util::CreateShillConfiguration(profile_
,
199 &global_network_config_
,
202 // A new policy has to be applied to this profile entry. In order to keep
203 // implicit state of Shill like "connected successfully before", keep the
204 // entry if a policy is reapplied (e.g. after reboot) or is updated.
205 // However, some Shill properties are used to identify the network and
206 // cannot be modified after initial configuration, so we have to delete
207 // the profile entry in these cases. Also, keeping Shill's state if the
208 // SSID changed might not be a good idea anyways. If the policy GUID
209 // changed, or there was no policy before, we delete the entry at first to
210 // ensure that no old configuration remains.
211 if (old_guid
== new_guid
&&
212 shill_property_util::DoIdentifyingPropertiesMatch(
213 *new_shill_properties
, entry_properties
)) {
214 VLOG(1) << "Updating previously managed configuration with the "
215 << "updated policy " << new_guid
<< ".";
217 VLOG(1) << "Deleting profile entry before writing new policy "
218 << new_guid
<< " because of identifying properties changed.";
222 // In general, old entries should at first be deleted before new
223 // configurations are written to prevent inconsistencies. Therefore, we
224 // delay the writing of the new config here until ~PolicyApplicator.
225 // E.g. one problematic case is if a policy { {GUID=X, SSID=Y} } is
226 // applied to the profile entries
227 // { ENTRY1 = {GUID=X, SSID=X, USER_SETTINGS=X},
228 // ENTRY2 = {SSID=Y, ... } }.
229 // At first ENTRY1 and ENTRY2 should be removed, then the new config be
230 // written and the result should be:
231 // { {GUID=X, SSID=Y, USER_SETTINGS=X} }
232 WriteNewShillConfiguration(
233 *new_shill_properties
, *new_policy
, true /* write later */);
234 remaining_policies_
.erase(new_guid
);
236 } else if (was_managed
) {
237 VLOG(1) << "Removing configuration previously managed by policy "
238 << old_guid
<< ", because the policy was removed.";
240 // Remove the entry, because the network was managed but isn't anymore.
241 // Note: An alternative might be to preserve the user settings, but it's
242 // unclear which values originating the policy should be removed.
245 // The entry wasn't managed and doesn't match any current policy. Global
246 // network settings have to be applied.
247 base::DictionaryValue shill_properties_to_update
;
248 policy_util::SetShillPropertiesForGlobalPolicy(
249 entry_properties
, global_network_config_
, &shill_properties_to_update
);
250 if (shill_properties_to_update
.empty()) {
251 VLOG(2) << "Ignore unmanaged entry.";
252 // Calling a SetProperties of Shill with an empty dictionary is a no op.
254 VLOG(2) << "Apply global network config to unmanaged entry.";
255 handler_
->UpdateExistingConfigurationWithPropertiesFromPolicy(
256 entry_properties
, shill_properties_to_update
);
260 ProfileEntryFinished(entry
);
263 void PolicyApplicator::GetEntryError(const std::string
& entry
,
264 const std::string
& error_name
,
265 const std::string
& error_message
) {
266 LOG(ERROR
) << "Could not retrieve entry " << entry
<< " of profile "
267 << profile_
.path
<< ": " << error_message
;
268 ProfileEntryFinished(entry
);
271 void PolicyApplicator::DeleteEntry(const std::string
& entry
) {
272 DBusThreadManager::Get()->GetShillProfileClient()->DeleteEntry(
273 dbus::ObjectPath(profile_
.path
),
275 base::Bind(&base::DoNothing
),
276 base::Bind(&LogErrorMessage
, FROM_HERE
));
279 void PolicyApplicator::WriteNewShillConfiguration(
280 const base::DictionaryValue
& shill_dictionary
,
281 const base::DictionaryValue
& policy
,
283 // Ethernet (non EAP) settings, like GUID or UIData, cannot be stored per
284 // user. Abort in that case.
286 policy
.GetStringWithoutPathExpansion(::onc::network_config::kType
, &type
);
287 if (type
== ::onc::network_type::kEthernet
&&
288 profile_
.type() == NetworkProfile::TYPE_USER
) {
289 const base::DictionaryValue
* ethernet
= NULL
;
290 policy
.GetDictionaryWithoutPathExpansion(::onc::network_config::kEthernet
,
293 ethernet
->GetStringWithoutPathExpansion(::onc::ethernet::kAuthentication
,
295 if (auth
== ::onc::ethernet::kAuthenticationNone
)
300 new_shill_configurations_
.push_back(shill_dictionary
.DeepCopy());
302 handler_
->CreateConfigurationFromPolicy(shill_dictionary
);
305 void PolicyApplicator::ApplyRemainingPolicies() {
306 DCHECK(pending_get_entry_calls_
.empty());
308 // Write all queued configurations now.
309 for (ScopedVector
<base::DictionaryValue
>::const_iterator it
=
310 new_shill_configurations_
.begin();
311 it
!= new_shill_configurations_
.end();
313 handler_
->CreateConfigurationFromPolicy(**it
);
315 new_shill_configurations_
.clear();
317 VLOG_IF(2, !remaining_policies_
.empty())
318 << "Create new managed network configurations in profile"
319 << profile_
.ToDebugString() << ".";
321 // All profile entries were compared to policies. |remaining_policies_|
322 // contains all modified policies that didn't match any entry. For these
323 // remaining policies, new configurations have to be created.
324 for (std::set
<std::string
>::iterator it
= remaining_policies_
.begin();
325 it
!= remaining_policies_
.end(); ++it
) {
326 const base::DictionaryValue
* network_policy
= GetByGUID(all_policies_
, *it
);
327 DCHECK(network_policy
);
329 VLOG(1) << "Creating new configuration managed by policy " << *it
330 << " in profile " << profile_
.ToDebugString() << ".";
332 scoped_ptr
<base::DictionaryValue
> shill_dictionary
=
333 policy_util::CreateShillConfiguration(profile_
,
335 &global_network_config_
,
337 NULL
/* no user settings */);
338 WriteNewShillConfiguration(
339 *shill_dictionary
, *network_policy
, false /* write now */);
341 remaining_policies_
.clear();
344 void PolicyApplicator::NotifyConfigurationHandlerAndFinish() {
345 weak_ptr_factory_
.InvalidateWeakPtrs();
346 handler_
->OnPoliciesApplied(profile_
);
349 } // namespace chromeos