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_util.h" 6 7 #include "base/logging.h" 8 #include "base/values.h" 9 #include "chromeos/network/network_profile.h" 10 #include "chromeos/network/network_ui_data.h" 11 #include "chromeos/network/onc/onc_merger.h" 12 #include "chromeos/network/onc/onc_normalizer.h" 13 #include "chromeos/network/onc/onc_signature.h" 14 #include "chromeos/network/onc/onc_translator.h" 15 #include "chromeos/network/onc/onc_utils.h" 16 #include "chromeos/network/shill_property_util.h" 17 #include "components/onc/onc_constants.h" 18 #include "third_party/cros_system_api/dbus/service_constants.h" 19 20 namespace chromeos { 21 22 namespace policy_util { 23 24 namespace { 25 26 // This fake credential contains a random postfix which is extremly unlikely to 27 // be used by any user. 28 const char kFakeCredential[] = "FAKE_CREDENTIAL_VPaJDV9x"; 29 30 31 // Removes all kFakeCredential values from sensitive fields (determined by 32 // onc::FieldIsCredential) of |onc_object|. 33 void RemoveFakeCredentials( 34 const onc::OncValueSignature& signature, 35 base::DictionaryValue* onc_object) { 36 base::DictionaryValue::Iterator it(*onc_object); 37 while (!it.IsAtEnd()) { 38 base::Value* value = NULL; 39 std::string field_name = it.key(); 40 // We need the non-const entry to remove nested values but DictionaryValue 41 // has no non-const iterator. 42 onc_object->GetWithoutPathExpansion(field_name, &value); 43 // Advance before delete. 44 it.Advance(); 45 46 // If |value| is a dictionary, recurse. 47 base::DictionaryValue* nested_object = NULL; 48 if (value->GetAsDictionary(&nested_object)) { 49 const onc::OncFieldSignature* field_signature = 50 onc::GetFieldSignature(signature, field_name); 51 if (field_signature) 52 RemoveFakeCredentials(*field_signature->value_signature, nested_object); 53 else 54 LOG(ERROR) << "ONC has unrecoginzed field: " << field_name; 55 continue; 56 } 57 58 // If |value| is a string, check if it is a fake credential. 59 std::string string_value; 60 if (value->GetAsString(&string_value) && 61 onc::FieldIsCredential(signature, field_name)) { 62 if (string_value == kFakeCredential) { 63 // The value wasn't modified by the UI, thus we remove the field to keep 64 // the existing value that is stored in Shill. 65 onc_object->RemoveWithoutPathExpansion(field_name, NULL); 66 } 67 // Otherwise, the value is set and modified by the UI, thus we keep that 68 // value to overwrite whatever is stored in Shill. 69 } 70 } 71 } 72 73 // Returns true if |policy| matches |actual_network|, which must be part of a 74 // ONC NetworkConfiguration. This should be the only such matching function 75 // within Chrome. Shill does such matching in several functions for network 76 // identification. For compatibility, we currently should stick to Shill's 77 // matching behavior. 78 bool IsPolicyMatching(const base::DictionaryValue& policy, 79 const base::DictionaryValue& actual_network) { 80 std::string policy_type; 81 policy.GetStringWithoutPathExpansion(::onc::network_config::kType, 82 &policy_type); 83 std::string actual_network_type; 84 actual_network.GetStringWithoutPathExpansion(::onc::network_config::kType, 85 &actual_network_type); 86 if (policy_type != actual_network_type) 87 return false; 88 89 if (actual_network_type == ::onc::network_type::kEthernet) { 90 const base::DictionaryValue* policy_ethernet = NULL; 91 policy.GetDictionaryWithoutPathExpansion(::onc::network_config::kEthernet, 92 &policy_ethernet); 93 const base::DictionaryValue* actual_ethernet = NULL; 94 actual_network.GetDictionaryWithoutPathExpansion( 95 ::onc::network_config::kEthernet, &actual_ethernet); 96 if (!policy_ethernet || !actual_ethernet) 97 return false; 98 99 std::string policy_auth; 100 policy_ethernet->GetStringWithoutPathExpansion( 101 ::onc::ethernet::kAuthentication, &policy_auth); 102 std::string actual_auth; 103 actual_ethernet->GetStringWithoutPathExpansion( 104 ::onc::ethernet::kAuthentication, &actual_auth); 105 return policy_auth == actual_auth; 106 } else if (actual_network_type == ::onc::network_type::kWiFi) { 107 const base::DictionaryValue* policy_wifi = NULL; 108 policy.GetDictionaryWithoutPathExpansion(::onc::network_config::kWiFi, 109 &policy_wifi); 110 const base::DictionaryValue* actual_wifi = NULL; 111 actual_network.GetDictionaryWithoutPathExpansion( 112 ::onc::network_config::kWiFi, 113 &actual_wifi); 114 if (!policy_wifi || !actual_wifi) 115 return false; 116 117 std::string policy_ssid; 118 policy_wifi->GetStringWithoutPathExpansion(::onc::wifi::kSSID, 119 &policy_ssid); 120 std::string actual_ssid; 121 actual_wifi->GetStringWithoutPathExpansion(::onc::wifi::kSSID, 122 &actual_ssid); 123 return (policy_ssid == actual_ssid); 124 } 125 return false; 126 } 127 128 base::DictionaryValue* GetOrCreateDictionary(const std::string& key, 129 base::DictionaryValue* dict) { 130 base::DictionaryValue* inner_dict = NULL; 131 if (!dict->GetDictionaryWithoutPathExpansion(key, &inner_dict)) { 132 inner_dict = new base::DictionaryValue; 133 dict->SetWithoutPathExpansion(key, inner_dict); 134 } 135 return inner_dict; 136 } 137 138 base::DictionaryValue* GetOrCreateNestedDictionary( 139 const std::string& key1, 140 const std::string& key2, 141 base::DictionaryValue* dict) { 142 base::DictionaryValue* inner_dict = GetOrCreateDictionary(key1, dict); 143 return GetOrCreateDictionary(key2, inner_dict); 144 } 145 146 void ApplyGlobalAutoconnectPolicy( 147 NetworkProfile::Type profile_type, 148 base::DictionaryValue* augmented_onc_network) { 149 base::DictionaryValue* type_dictionary = NULL; 150 augmented_onc_network->GetDictionaryWithoutPathExpansion( 151 ::onc::network_config::kType, &type_dictionary); 152 std::string type; 153 if (!type_dictionary || 154 !type_dictionary->GetStringWithoutPathExpansion( 155 ::onc::kAugmentationActiveSetting, &type) || 156 type.empty()) { 157 LOG(ERROR) << "ONC dictionary with no Type."; 158 return; 159 } 160 161 // Managed dictionaries don't contain empty dictionaries (see onc_merger.cc), 162 // so add the Autoconnect dictionary in case Shill didn't report a value. 163 base::DictionaryValue* auto_connect_dictionary = NULL; 164 if (type == ::onc::network_type::kWiFi) { 165 auto_connect_dictionary = 166 GetOrCreateNestedDictionary(::onc::network_config::kWiFi, 167 ::onc::wifi::kAutoConnect, 168 augmented_onc_network); 169 } else if (type == ::onc::network_type::kVPN) { 170 auto_connect_dictionary = 171 GetOrCreateNestedDictionary(::onc::network_config::kVPN, 172 ::onc::vpn::kAutoConnect, 173 augmented_onc_network); 174 } else { 175 return; // Network type without auto-connect property. 176 } 177 178 std::string policy_source; 179 if (profile_type == NetworkProfile::TYPE_USER) 180 policy_source = ::onc::kAugmentationUserPolicy; 181 else if(profile_type == NetworkProfile::TYPE_SHARED) 182 policy_source = ::onc::kAugmentationDevicePolicy; 183 else 184 NOTREACHED(); 185 186 auto_connect_dictionary->SetBooleanWithoutPathExpansion(policy_source, false); 187 auto_connect_dictionary->SetStringWithoutPathExpansion( 188 ::onc::kAugmentationEffectiveSetting, policy_source); 189 } 190 191 } // namespace 192 193 scoped_ptr<base::DictionaryValue> CreateManagedONC( 194 const base::DictionaryValue* global_policy, 195 const base::DictionaryValue* network_policy, 196 const base::DictionaryValue* user_settings, 197 const base::DictionaryValue* active_settings, 198 const NetworkProfile* profile) { 199 const base::DictionaryValue* user_policy = NULL; 200 const base::DictionaryValue* device_policy = NULL; 201 const base::DictionaryValue* nonshared_user_settings = NULL; 202 const base::DictionaryValue* shared_user_settings = NULL; 203 204 if (profile) { 205 if (profile->type() == NetworkProfile::TYPE_SHARED) { 206 device_policy = network_policy; 207 shared_user_settings = user_settings; 208 } else if (profile->type() == NetworkProfile::TYPE_USER) { 209 user_policy = network_policy; 210 nonshared_user_settings = user_settings; 211 } else { 212 NOTREACHED(); 213 } 214 } 215 216 // This call also removes credentials from policies. 217 scoped_ptr<base::DictionaryValue> augmented_onc_network = 218 onc::MergeSettingsAndPoliciesToAugmented( 219 onc::kNetworkConfigurationSignature, 220 user_policy, 221 device_policy, 222 nonshared_user_settings, 223 shared_user_settings, 224 active_settings); 225 226 // If present, apply the Autoconnect policy only to networks that are not 227 // managed by policy. 228 if (!network_policy && global_policy && profile) { 229 bool allow_only_policy_autoconnect = false; 230 global_policy->GetBooleanWithoutPathExpansion( 231 ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect, 232 &allow_only_policy_autoconnect); 233 if (allow_only_policy_autoconnect) { 234 ApplyGlobalAutoconnectPolicy(profile->type(), 235 augmented_onc_network.get()); 236 } 237 } 238 239 return augmented_onc_network.Pass(); 240 } 241 242 void SetShillPropertiesForGlobalPolicy( 243 const base::DictionaryValue& shill_dictionary, 244 const base::DictionaryValue& global_network_policy, 245 base::DictionaryValue* shill_properties_to_update) { 246 // kAllowOnlyPolicyNetworksToAutoconnect is currently the only global config. 247 248 std::string type; 249 shill_dictionary.GetStringWithoutPathExpansion(shill::kTypeProperty, &type); 250 if (NetworkTypePattern::Ethernet().MatchesType(type)) 251 return; // Autoconnect for Ethernet cannot be configured. 252 253 // By default all networks are allowed to autoconnect. 254 bool only_policy_autoconnect = false; 255 global_network_policy.GetBooleanWithoutPathExpansion( 256 ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect, 257 &only_policy_autoconnect); 258 if (!only_policy_autoconnect) 259 return; 260 261 bool old_autoconnect = false; 262 if (shill_dictionary.GetBooleanWithoutPathExpansion( 263 shill::kAutoConnectProperty, &old_autoconnect) && 264 !old_autoconnect) { 265 // Autoconnect is already explictly disabled. No need to set it again. 266 return; 267 } 268 269 // If autconnect is not explicitly set yet, it might automatically be enabled 270 // by Shill. To prevent that, disable it explicitly. 271 shill_properties_to_update->SetBooleanWithoutPathExpansion( 272 shill::kAutoConnectProperty, false); 273 } 274 275 scoped_ptr<base::DictionaryValue> CreateShillConfiguration( 276 const NetworkProfile& profile, 277 const std::string& guid, 278 const base::DictionaryValue* global_policy, 279 const base::DictionaryValue* network_policy, 280 const base::DictionaryValue* user_settings) { 281 scoped_ptr<base::DictionaryValue> effective; 282 ::onc::ONCSource onc_source = ::onc::ONC_SOURCE_NONE; 283 if (network_policy) { 284 if (profile.type() == NetworkProfile::TYPE_SHARED) { 285 effective = onc::MergeSettingsAndPoliciesToEffective( 286 NULL, // no user policy 287 network_policy, // device policy 288 NULL, // no user settings 289 user_settings); // shared settings 290 onc_source = ::onc::ONC_SOURCE_DEVICE_POLICY; 291 } else if (profile.type() == NetworkProfile::TYPE_USER) { 292 effective = onc::MergeSettingsAndPoliciesToEffective( 293 network_policy, // user policy 294 NULL, // no device policy 295 user_settings, // user settings 296 NULL); // no shared settings 297 onc_source = ::onc::ONC_SOURCE_USER_POLICY; 298 } else { 299 NOTREACHED(); 300 } 301 } else if (user_settings) { 302 effective.reset(user_settings->DeepCopy()); 303 // TODO(pneubeck): change to source ONC_SOURCE_USER 304 onc_source = ::onc::ONC_SOURCE_NONE; 305 } else { 306 NOTREACHED(); 307 onc_source = ::onc::ONC_SOURCE_NONE; 308 } 309 310 RemoveFakeCredentials(onc::kNetworkConfigurationSignature, 311 effective.get()); 312 313 effective->SetStringWithoutPathExpansion(::onc::network_config::kGUID, guid); 314 315 // Remove irrelevant fields. 316 onc::Normalizer normalizer(true /* remove recommended fields */); 317 effective = normalizer.NormalizeObject(&onc::kNetworkConfigurationSignature, 318 *effective); 319 320 scoped_ptr<base::DictionaryValue> shill_dictionary( 321 onc::TranslateONCObjectToShill(&onc::kNetworkConfigurationSignature, 322 *effective)); 323 324 shill_dictionary->SetStringWithoutPathExpansion(shill::kProfileProperty, 325 profile.path); 326 327 if (!network_policy && global_policy) { 328 // The network isn't managed. Global network policies have to be applied. 329 SetShillPropertiesForGlobalPolicy( 330 *shill_dictionary, *global_policy, shill_dictionary.get()); 331 } 332 333 scoped_ptr<NetworkUIData> ui_data(NetworkUIData::CreateFromONC(onc_source)); 334 335 if (user_settings) { 336 // Shill doesn't know that sensitive data is contained in the UIData 337 // property and might write it into logs or other insecure places. Thus, we 338 // have to remove or mask credentials. 339 // 340 // Shill's GetProperties doesn't return credentials. Masking credentials 341 // instead of just removing them, allows remembering if a credential is set 342 // or not. 343 scoped_ptr<base::DictionaryValue> sanitized_user_settings( 344 onc::MaskCredentialsInOncObject(onc::kNetworkConfigurationSignature, 345 *user_settings, 346 kFakeCredential)); 347 ui_data->set_user_settings(sanitized_user_settings.Pass()); 348 } 349 350 shill_property_util::SetUIData(*ui_data, shill_dictionary.get()); 351 352 VLOG(2) << "Created Shill properties: " << *shill_dictionary; 353 354 return shill_dictionary.Pass(); 355 } 356 357 const base::DictionaryValue* FindMatchingPolicy( 358 const GuidToPolicyMap& policies, 359 const base::DictionaryValue& actual_network) { 360 for (GuidToPolicyMap::const_iterator it = policies.begin(); 361 it != policies.end(); ++it) { 362 if (IsPolicyMatching(*it->second, actual_network)) 363 return it->second; 364 } 365 return NULL; 366 } 367 368 } // namespace policy_util 369 370 } // namespace chromeos 371