1 // Copyright (c) 2012 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/onc/onc_utils.h"
7 #include "base/base64.h"
8 #include "base/json/json_reader.h"
9 #include "base/logging.h"
10 #include "base/metrics/histogram.h"
11 #include "base/strings/string_util.h"
12 #include "base/values.h"
13 #include "chromeos/network/network_event_log.h"
14 #include "chromeos/network/onc/onc_mapper.h"
15 #include "chromeos/network/onc/onc_signature.h"
16 #include "chromeos/network/onc/onc_utils.h"
17 #include "chromeos/network/onc/onc_validator.h"
18 #include "crypto/encryptor.h"
19 #include "crypto/hmac.h"
20 #include "crypto/symmetric_key.h"
21 #include "net/cert/pem_tokenizer.h"
22 #include "net/cert/x509_certificate.h"
24 #define ONC_LOG_WARNING(message) NET_LOG_WARNING("ONC", message)
25 #define ONC_LOG_ERROR(message) NET_LOG_ERROR("ONC", message)
27 using namespace ::onc
;
34 const char kUnableToDecrypt
[] = "Unable to decrypt encrypted ONC";
35 const char kUnableToDecode
[] = "Unable to decode encrypted ONC";
39 const char kEmptyUnencryptedConfiguration
[] =
40 "{\"Type\":\"UnencryptedConfiguration\",\"NetworkConfigurations\":[],"
41 "\"Certificates\":[]}";
43 scoped_ptr
<base::DictionaryValue
> ReadDictionaryFromJson(
44 const std::string
& json
) {
46 base::Value
* root
= base::JSONReader::ReadAndReturnError(
47 json
, base::JSON_ALLOW_TRAILING_COMMAS
, NULL
, &error
);
49 base::DictionaryValue
* dict_ptr
= NULL
;
50 if (!root
|| !root
->GetAsDictionary(&dict_ptr
)) {
51 ONC_LOG_ERROR("Invalid JSON Dictionary: " + error
);
55 return make_scoped_ptr(dict_ptr
);
58 scoped_ptr
<base::DictionaryValue
> Decrypt(const std::string
& passphrase
,
59 const base::DictionaryValue
& root
) {
60 const int kKeySizeInBits
= 256;
61 const int kMaxIterationCount
= 500000;
63 std::string initial_vector
;
66 std::string stretch_method
;
67 std::string hmac_method
;
70 std::string ciphertext
;
72 if (!root
.GetString(encrypted::kCiphertext
, &ciphertext
) ||
73 !root
.GetString(encrypted::kCipher
, &cipher
) ||
74 !root
.GetString(encrypted::kHMAC
, &hmac
) ||
75 !root
.GetString(encrypted::kHMACMethod
, &hmac_method
) ||
76 !root
.GetString(encrypted::kIV
, &initial_vector
) ||
77 !root
.GetInteger(encrypted::kIterations
, &iterations
) ||
78 !root
.GetString(encrypted::kSalt
, &salt
) ||
79 !root
.GetString(encrypted::kStretch
, &stretch_method
) ||
80 !root
.GetString(toplevel_config::kType
, &onc_type
) ||
81 onc_type
!= toplevel_config::kEncryptedConfiguration
) {
83 ONC_LOG_ERROR("Encrypted ONC malformed.");
84 return scoped_ptr
<base::DictionaryValue
>();
87 if (hmac_method
!= encrypted::kSHA1
||
88 cipher
!= encrypted::kAES256
||
89 stretch_method
!= encrypted::kPBKDF2
) {
90 ONC_LOG_ERROR("Encrypted ONC unsupported encryption scheme.");
91 return scoped_ptr
<base::DictionaryValue
>();
94 // Make sure iterations != 0, since that's not valid.
95 if (iterations
== 0) {
96 ONC_LOG_ERROR(kUnableToDecrypt
);
97 return scoped_ptr
<base::DictionaryValue
>();
100 // Simply a sanity check to make sure we can't lock up the machine
101 // for too long with a huge number (or a negative number).
102 if (iterations
< 0 || iterations
> kMaxIterationCount
) {
103 ONC_LOG_ERROR("Too many iterations in encrypted ONC");
104 return scoped_ptr
<base::DictionaryValue
>();
107 if (!base::Base64Decode(salt
, &salt
)) {
108 ONC_LOG_ERROR(kUnableToDecode
);
109 return scoped_ptr
<base::DictionaryValue
>();
112 scoped_ptr
<crypto::SymmetricKey
> key(
113 crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES
,
119 if (!base::Base64Decode(initial_vector
, &initial_vector
)) {
120 ONC_LOG_ERROR(kUnableToDecode
);
121 return scoped_ptr
<base::DictionaryValue
>();
123 if (!base::Base64Decode(ciphertext
, &ciphertext
)) {
124 ONC_LOG_ERROR(kUnableToDecode
);
125 return scoped_ptr
<base::DictionaryValue
>();
127 if (!base::Base64Decode(hmac
, &hmac
)) {
128 ONC_LOG_ERROR(kUnableToDecode
);
129 return scoped_ptr
<base::DictionaryValue
>();
132 crypto::HMAC
hmac_verifier(crypto::HMAC::SHA1
);
133 if (!hmac_verifier
.Init(key
.get()) ||
134 !hmac_verifier
.Verify(ciphertext
, hmac
)) {
135 ONC_LOG_ERROR(kUnableToDecrypt
);
136 return scoped_ptr
<base::DictionaryValue
>();
139 crypto::Encryptor decryptor
;
140 if (!decryptor
.Init(key
.get(), crypto::Encryptor::CBC
, initial_vector
)) {
141 ONC_LOG_ERROR(kUnableToDecrypt
);
142 return scoped_ptr
<base::DictionaryValue
>();
145 std::string plaintext
;
146 if (!decryptor
.Decrypt(ciphertext
, &plaintext
)) {
147 ONC_LOG_ERROR(kUnableToDecrypt
);
148 return scoped_ptr
<base::DictionaryValue
>();
151 scoped_ptr
<base::DictionaryValue
> new_root
=
152 ReadDictionaryFromJson(plaintext
);
153 if (new_root
.get() == NULL
) {
154 ONC_LOG_ERROR("Property dictionary malformed.");
155 return scoped_ptr
<base::DictionaryValue
>();
158 return new_root
.Pass();
161 std::string
GetSourceAsString(ONCSource source
) {
163 case ONC_SOURCE_UNKNOWN
:
165 case ONC_SOURCE_NONE
:
167 case ONC_SOURCE_DEVICE_POLICY
:
168 return "device policy";
169 case ONC_SOURCE_USER_POLICY
:
170 return "user policy";
171 case ONC_SOURCE_USER_IMPORT
:
172 return "user import";
174 NOTREACHED() << "unknown ONC source " << source
;
178 void ExpandField(const std::string
& fieldname
,
179 const StringSubstitution
& substitution
,
180 base::DictionaryValue
* onc_object
) {
181 std::string user_string
;
182 if (!onc_object
->GetStringWithoutPathExpansion(fieldname
, &user_string
))
185 std::string login_id
;
186 if (substitution
.GetSubstitute(substitutes::kLoginIDField
, &login_id
)) {
187 ReplaceSubstringsAfterOffset(&user_string
, 0,
188 substitutes::kLoginIDField
,
193 if (substitution
.GetSubstitute(substitutes::kEmailField
, &email
)) {
194 ReplaceSubstringsAfterOffset(&user_string
, 0,
195 substitutes::kEmailField
,
199 onc_object
->SetStringWithoutPathExpansion(fieldname
, user_string
);
202 void ExpandStringsInOncObject(
203 const OncValueSignature
& signature
,
204 const StringSubstitution
& substitution
,
205 base::DictionaryValue
* onc_object
) {
206 if (&signature
== &kEAPSignature
) {
207 ExpandField(eap::kAnonymousIdentity
, substitution
, onc_object
);
208 ExpandField(eap::kIdentity
, substitution
, onc_object
);
209 } else if (&signature
== &kL2TPSignature
||
210 &signature
== &kOpenVPNSignature
) {
211 ExpandField(vpn::kUsername
, substitution
, onc_object
);
214 // Recurse into nested objects.
215 for (base::DictionaryValue::Iterator
it(*onc_object
); !it
.IsAtEnd();
217 base::DictionaryValue
* inner_object
= NULL
;
218 if (!onc_object
->GetDictionaryWithoutPathExpansion(it
.key(), &inner_object
))
221 const OncFieldSignature
* field_signature
=
222 GetFieldSignature(signature
, it
.key());
223 if (!field_signature
)
226 ExpandStringsInOncObject(*field_signature
->value_signature
,
227 substitution
, inner_object
);
231 void ExpandStringsInNetworks(const StringSubstitution
& substitution
,
232 base::ListValue
* network_configs
) {
233 for (base::ListValue::iterator it
= network_configs
->begin();
234 it
!= network_configs
->end(); ++it
) {
235 base::DictionaryValue
* network
= NULL
;
236 (*it
)->GetAsDictionary(&network
);
238 ExpandStringsInOncObject(
239 kNetworkConfigurationSignature
, substitution
, network
);
245 class OncMaskValues
: public Mapper
{
247 static scoped_ptr
<base::DictionaryValue
> Mask(
248 const OncValueSignature
& signature
,
249 const base::DictionaryValue
& onc_object
,
250 const std::string
& mask
) {
251 OncMaskValues
masker(mask
);
253 return masker
.MapObject(signature
, onc_object
, &unused_error
);
257 explicit OncMaskValues(const std::string
& mask
)
261 virtual scoped_ptr
<base::Value
> MapField(
262 const std::string
& field_name
,
263 const OncValueSignature
& object_signature
,
264 const base::Value
& onc_value
,
265 bool* found_unknown_field
,
266 bool* error
) OVERRIDE
{
267 if (FieldIsCredential(object_signature
, field_name
)) {
268 return scoped_ptr
<base::Value
>(new base::StringValue(mask_
));
270 return Mapper::MapField(field_name
, object_signature
, onc_value
,
271 found_unknown_field
, error
);
275 // Mask to insert in place of the sensitive values.
281 scoped_ptr
<base::DictionaryValue
> MaskCredentialsInOncObject(
282 const OncValueSignature
& signature
,
283 const base::DictionaryValue
& onc_object
,
284 const std::string
& mask
) {
285 return OncMaskValues::Mask(signature
, onc_object
, mask
);
290 std::string
DecodePEM(const std::string
& pem_encoded
) {
291 // The PEM block header used for DER certificates
292 const char kCertificateHeader
[] = "CERTIFICATE";
294 // This is an older PEM marker for DER certificates.
295 const char kX509CertificateHeader
[] = "X509 CERTIFICATE";
297 std::vector
<std::string
> pem_headers
;
298 pem_headers
.push_back(kCertificateHeader
);
299 pem_headers
.push_back(kX509CertificateHeader
);
301 net::PEMTokenizer
pem_tokenizer(pem_encoded
, pem_headers
);
303 if (pem_tokenizer
.GetNext()) {
304 decoded
= pem_tokenizer
.data();
306 // If we failed to read the data as a PEM file, then try plain base64 decode
307 // in case the PEM marker strings are missing. For this to work, there has
308 // to be no white space, and it has to only contain the base64-encoded data.
309 if (!base::Base64Decode(pem_encoded
, &decoded
)) {
310 LOG(ERROR
) << "Unable to base64 decode X509 data: " << pem_encoded
;
311 return std::string();
317 CertPEMsByGUIDMap
GetServerAndCACertsByGUID(
318 const base::ListValue
& certificates
) {
319 CertPEMsByGUIDMap certs_by_guid
;
320 for (base::ListValue::const_iterator it
= certificates
.begin();
321 it
!= certificates
.end(); ++it
) {
322 base::DictionaryValue
* cert
= NULL
;
323 (*it
)->GetAsDictionary(&cert
);
326 cert
->GetStringWithoutPathExpansion(certificate::kGUID
, &guid
);
327 std::string cert_type
;
328 cert
->GetStringWithoutPathExpansion(certificate::kType
, &cert_type
);
329 if (cert_type
!= certificate::kServer
&&
330 cert_type
!= certificate::kAuthority
) {
333 std::string x509_data
;
334 cert
->GetStringWithoutPathExpansion(certificate::kX509
, &x509_data
);
336 std::string der
= DecodePEM(x509_data
);
338 if (der
.empty() || !net::X509Certificate::GetPEMEncodedFromDER(der
, &pem
)) {
339 LOG(ERROR
) << "Certificate with GUID " << guid
340 << " is not in PEM encoding.";
343 certs_by_guid
[guid
] = pem
;
346 return certs_by_guid
;
351 bool ParseAndValidateOncForImport(const std::string
& onc_blob
,
352 ONCSource onc_source
,
353 const std::string
& passphrase
,
354 base::ListValue
* network_configs
,
355 base::DictionaryValue
* global_network_config
,
356 base::ListValue
* certificates
) {
357 network_configs
->Clear();
358 global_network_config
->Clear();
359 certificates
->Clear();
360 if (onc_blob
.empty())
363 scoped_ptr
<base::DictionaryValue
> toplevel_onc
=
364 ReadDictionaryFromJson(onc_blob
);
365 if (toplevel_onc
.get() == NULL
) {
366 LOG(ERROR
) << "ONC loaded from " << GetSourceAsString(onc_source
)
367 << " is not a valid JSON dictionary.";
371 // Check and see if this is an encrypted ONC file. If so, decrypt it.
372 std::string onc_type
;
373 toplevel_onc
->GetStringWithoutPathExpansion(toplevel_config::kType
,
375 if (onc_type
== toplevel_config::kEncryptedConfiguration
) {
376 toplevel_onc
= Decrypt(passphrase
, *toplevel_onc
);
377 if (toplevel_onc
.get() == NULL
) {
378 LOG(ERROR
) << "Couldn't decrypt the ONC from "
379 << GetSourceAsString(onc_source
);
384 bool from_policy
= (onc_source
== ONC_SOURCE_USER_POLICY
||
385 onc_source
== ONC_SOURCE_DEVICE_POLICY
);
387 // Validate the ONC dictionary. We are liberal and ignore unknown field
388 // names and ignore invalid field names in kRecommended arrays.
389 Validator
validator(false, // Ignore unknown fields.
390 false, // Ignore invalid recommended field names.
391 true, // Fail on missing fields.
393 validator
.SetOncSource(onc_source
);
395 Validator::Result validation_result
;
396 toplevel_onc
= validator
.ValidateAndRepairObject(
397 &kToplevelConfigurationSignature
,
402 UMA_HISTOGRAM_BOOLEAN("Enterprise.ONC.PolicyValidation",
403 validation_result
== Validator::VALID
);
407 if (validation_result
== Validator::VALID_WITH_WARNINGS
) {
408 LOG(WARNING
) << "ONC from " << GetSourceAsString(onc_source
)
409 << " produced warnings.";
411 } else if (validation_result
== Validator::INVALID
|| toplevel_onc
== NULL
) {
412 LOG(ERROR
) << "ONC from " << GetSourceAsString(onc_source
)
413 << " is invalid and couldn't be repaired.";
417 base::ListValue
* validated_certs
= NULL
;
418 if (toplevel_onc
->GetListWithoutPathExpansion(toplevel_config::kCertificates
,
420 certificates
->Swap(validated_certs
);
423 base::ListValue
* validated_networks
= NULL
;
424 if (toplevel_onc
->GetListWithoutPathExpansion(
425 toplevel_config::kNetworkConfigurations
, &validated_networks
)) {
426 CertPEMsByGUIDMap server_and_ca_certs
=
427 GetServerAndCACertsByGUID(*certificates
);
429 if (!ResolveServerCertRefsInNetworks(server_and_ca_certs
,
430 validated_networks
)) {
431 LOG(ERROR
) << "Some certificate references in the ONC policy for source "
432 << GetSourceAsString(onc_source
) << " could not be resolved.";
436 network_configs
->Swap(validated_networks
);
439 base::DictionaryValue
* validated_global_config
= NULL
;
440 if (toplevel_onc
->GetDictionaryWithoutPathExpansion(
441 toplevel_config::kGlobalNetworkConfiguration
,
442 &validated_global_config
)) {
443 global_network_config
->Swap(validated_global_config
);
449 scoped_refptr
<net::X509Certificate
> DecodePEMCertificate(
450 const std::string
& pem_encoded
) {
451 std::string decoded
= DecodePEM(pem_encoded
);
452 scoped_refptr
<net::X509Certificate
> cert
=
453 net::X509Certificate::CreateFromBytes(decoded
.data(), decoded
.size());
454 LOG_IF(ERROR
, !cert
.get()) << "Couldn't create certificate from X509 data: "
461 bool GUIDRefToPEMEncoding(const CertPEMsByGUIDMap
& certs_by_guid
,
462 const std::string
& guid_ref
,
463 std::string
* pem_encoded
) {
464 CertPEMsByGUIDMap::const_iterator it
= certs_by_guid
.find(guid_ref
);
465 if (it
== certs_by_guid
.end()) {
466 LOG(ERROR
) << "Couldn't resolve certificate reference " << guid_ref
;
469 *pem_encoded
= it
->second
;
470 if (pem_encoded
->empty()) {
471 LOG(ERROR
) << "Couldn't PEM-encode certificate with GUID " << guid_ref
;
477 bool ResolveSingleCertRef(const CertPEMsByGUIDMap
& certs_by_guid
,
478 const std::string
& key_guid_ref
,
479 const std::string
& key_pem
,
480 base::DictionaryValue
* onc_object
) {
481 std::string guid_ref
;
482 if (!onc_object
->GetStringWithoutPathExpansion(key_guid_ref
, &guid_ref
))
485 std::string pem_encoded
;
486 if (!GUIDRefToPEMEncoding(certs_by_guid
, guid_ref
, &pem_encoded
))
489 onc_object
->RemoveWithoutPathExpansion(key_guid_ref
, NULL
);
490 onc_object
->SetStringWithoutPathExpansion(key_pem
, pem_encoded
);
494 bool ResolveCertRefList(const CertPEMsByGUIDMap
& certs_by_guid
,
495 const std::string
& key_guid_ref_list
,
496 const std::string
& key_pem_list
,
497 base::DictionaryValue
* onc_object
) {
498 const base::ListValue
* guid_ref_list
= NULL
;
499 if (!onc_object
->GetListWithoutPathExpansion(key_guid_ref_list
,
504 scoped_ptr
<base::ListValue
> pem_list(new base::ListValue
);
505 for (base::ListValue::const_iterator it
= guid_ref_list
->begin();
506 it
!= guid_ref_list
->end(); ++it
) {
507 std::string guid_ref
;
508 (*it
)->GetAsString(&guid_ref
);
510 std::string pem_encoded
;
511 if (!GUIDRefToPEMEncoding(certs_by_guid
, guid_ref
, &pem_encoded
))
514 pem_list
->AppendString(pem_encoded
);
517 onc_object
->RemoveWithoutPathExpansion(key_guid_ref_list
, NULL
);
518 onc_object
->SetWithoutPathExpansion(key_pem_list
, pem_list
.release());
522 bool ResolveSingleCertRefToList(const CertPEMsByGUIDMap
& certs_by_guid
,
523 const std::string
& key_guid_ref
,
524 const std::string
& key_pem_list
,
525 base::DictionaryValue
* onc_object
) {
526 std::string guid_ref
;
527 if (!onc_object
->GetStringWithoutPathExpansion(key_guid_ref
, &guid_ref
))
530 std::string pem_encoded
;
531 if (!GUIDRefToPEMEncoding(certs_by_guid
, guid_ref
, &pem_encoded
))
534 scoped_ptr
<base::ListValue
> pem_list(new base::ListValue
);
535 pem_list
->AppendString(pem_encoded
);
536 onc_object
->RemoveWithoutPathExpansion(key_guid_ref
, NULL
);
537 onc_object
->SetWithoutPathExpansion(key_pem_list
, pem_list
.release());
541 // Resolves the reference list at |key_guid_refs| if present and otherwise the
542 // single reference at |key_guid_ref|. Returns whether the respective resolving
544 bool ResolveCertRefsOrRefToList(const CertPEMsByGUIDMap
& certs_by_guid
,
545 const std::string
& key_guid_refs
,
546 const std::string
& key_guid_ref
,
547 const std::string
& key_pem_list
,
548 base::DictionaryValue
* onc_object
) {
549 if (onc_object
->HasKey(key_guid_refs
)) {
550 if (onc_object
->HasKey(key_guid_ref
)) {
551 LOG(ERROR
) << "Found both " << key_guid_refs
<< " and " << key_guid_ref
552 << ". Ignoring and removing the latter.";
553 onc_object
->RemoveWithoutPathExpansion(key_guid_ref
, NULL
);
555 return ResolveCertRefList(
556 certs_by_guid
, key_guid_refs
, key_pem_list
, onc_object
);
559 // Only resolve |key_guid_ref| if |key_guid_refs| isn't present.
560 return ResolveSingleCertRefToList(
561 certs_by_guid
, key_guid_ref
, key_pem_list
, onc_object
);
564 bool ResolveServerCertRefsInObject(const CertPEMsByGUIDMap
& certs_by_guid
,
565 const OncValueSignature
& signature
,
566 base::DictionaryValue
* onc_object
) {
567 if (&signature
== &kCertificatePatternSignature
) {
568 if (!ResolveCertRefList(certs_by_guid
,
569 client_cert::kIssuerCARef
,
570 client_cert::kIssuerCAPEMs
,
574 } else if (&signature
== &kEAPSignature
) {
575 if (!ResolveCertRefsOrRefToList(certs_by_guid
,
582 } else if (&signature
== &kIPsecSignature
) {
583 if (!ResolveCertRefsOrRefToList(certs_by_guid
,
584 ipsec::kServerCARefs
,
586 ipsec::kServerCAPEMs
,
590 } else if (&signature
== &kIPsecSignature
||
591 &signature
== &kOpenVPNSignature
) {
592 if (!ResolveSingleCertRef(certs_by_guid
,
593 openvpn::kServerCertRef
,
594 openvpn::kServerCertPEM
,
596 !ResolveCertRefsOrRefToList(certs_by_guid
,
597 openvpn::kServerCARefs
,
598 openvpn::kServerCARef
,
599 openvpn::kServerCAPEMs
,
605 // Recurse into nested objects.
606 for (base::DictionaryValue::Iterator
it(*onc_object
); !it
.IsAtEnd();
608 base::DictionaryValue
* inner_object
= NULL
;
609 if (!onc_object
->GetDictionaryWithoutPathExpansion(it
.key(), &inner_object
))
612 const OncFieldSignature
* field_signature
=
613 GetFieldSignature(signature
, it
.key());
614 if (!field_signature
)
617 if (!ResolveServerCertRefsInObject(certs_by_guid
,
618 *field_signature
->value_signature
,
628 bool ResolveServerCertRefsInNetworks(const CertPEMsByGUIDMap
& certs_by_guid
,
629 base::ListValue
* network_configs
) {
631 for (base::ListValue::iterator it
= network_configs
->begin();
632 it
!= network_configs
->end(); ) {
633 base::DictionaryValue
* network
= NULL
;
634 (*it
)->GetAsDictionary(&network
);
635 if (!ResolveServerCertRefsInNetwork(certs_by_guid
, network
)) {
637 network
->GetStringWithoutPathExpansion(network_config::kGUID
, &guid
);
638 // This might happen even with correct validation, if the referenced
639 // certificate couldn't be imported.
640 LOG(ERROR
) << "Couldn't resolve some certificate reference of network "
642 it
= network_configs
->Erase(it
, NULL
);
651 bool ResolveServerCertRefsInNetwork(const CertPEMsByGUIDMap
& certs_by_guid
,
652 base::DictionaryValue
* network_config
) {
653 return ResolveServerCertRefsInObject(certs_by_guid
,
654 kNetworkConfigurationSignature
,
658 NetworkTypePattern
NetworkTypePatternFromOncType(const std::string
& type
) {
659 if (type
== ::onc::network_type::kAllTypes
)
660 return NetworkTypePattern::Default();
661 if (type
== ::onc::network_type::kCellular
)
662 return NetworkTypePattern::Cellular();
663 if (type
== ::onc::network_type::kEthernet
)
664 return NetworkTypePattern::Ethernet();
665 if (type
== ::onc::network_type::kVPN
)
666 return NetworkTypePattern::VPN();
667 if (type
== ::onc::network_type::kWiFi
)
668 return NetworkTypePattern::WiFi();
669 if (type
== ::onc::network_type::kWimax
)
670 return NetworkTypePattern::Wimax();
671 if (type
== ::onc::network_type::kWireless
)
672 return NetworkTypePattern::Wireless();
674 return NetworkTypePattern::Default();
677 bool IsRecommendedValue(const base::DictionaryValue
* onc
,
678 const std::string
& property_key
) {
679 std::string property_basename
, recommended_property_key
;
680 size_t pos
= property_key
.find_last_of('.');
681 if (pos
!= std::string::npos
) {
682 // 'WiFi.AutoConnect' -> 'AutoConnect', 'WiFi.Recommended'
683 property_basename
= property_key
.substr(pos
+ 1);
684 recommended_property_key
=
685 property_key
.substr(0, pos
+ 1) + ::onc::kRecommended
;
687 // 'Name' -> 'Name', 'Recommended'
688 property_basename
= property_key
;
689 recommended_property_key
= ::onc::kRecommended
;
692 const base::ListValue
* recommended_keys
= NULL
;
693 return (onc
->GetList(recommended_property_key
, &recommended_keys
) &&
694 recommended_keys
->Find(base::StringValue(property_basename
)) !=
695 recommended_keys
->end());
699 } // namespace chromeos