1 // Copyright (c) 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 "components/policy/core/common/cloud/component_cloud_policy_store.h" 6 7 #include "base/callback.h" 8 #include "base/json/json_reader.h" 9 #include "base/logging.h" 10 #include "base/strings/string_util.h" 11 #include "base/values.h" 12 #include "components/policy/core/common/cloud/cloud_policy_constants.h" 13 #include "components/policy/core/common/cloud/cloud_policy_validator.h" 14 #include "components/policy/core/common/external_data_fetcher.h" 15 #include "components/policy/core/common/policy_map.h" 16 #include "crypto/sha2.h" 17 #include "policy/proto/chrome_extension_policy.pb.h" 18 #include "policy/proto/device_management_backend.pb.h" 19 #include "url/gurl.h" 20 21 namespace em = enterprise_management; 22 23 namespace policy { 24 25 namespace { 26 27 const char kValue[] = "Value"; 28 const char kLevel[] = "Level"; 29 const char kRecommended[] = "Recommended"; 30 31 const struct DomainConstants { 32 PolicyDomain domain; 33 const char* proto_cache_key; 34 const char* data_cache_key; 35 const char* policy_type; 36 } kDomains[] = { 37 { 38 POLICY_DOMAIN_EXTENSIONS, 39 "extension-policy", 40 "extension-policy-data", 41 dm_protocol::kChromeExtensionPolicyType, 42 }, 43 }; 44 45 const DomainConstants* GetDomainConstants(PolicyDomain domain) { 46 for (size_t i = 0; i < arraysize(kDomains); ++i) { 47 if (kDomains[i].domain == domain) 48 return &kDomains[i]; 49 } 50 return NULL; 51 } 52 53 const DomainConstants* GetDomainConstantsForType(const std::string& type) { 54 for (size_t i = 0; i < arraysize(kDomains); ++i) { 55 if (kDomains[i].policy_type == type) 56 return &kDomains[i]; 57 } 58 return NULL; 59 } 60 61 } // namespace 62 63 ComponentCloudPolicyStore::Delegate::~Delegate() {} 64 65 ComponentCloudPolicyStore::ComponentCloudPolicyStore( 66 Delegate* delegate, 67 ResourceCache* cache) 68 : delegate_(delegate), 69 cache_(cache) { 70 // Allow the store to be created on a different thread than the thread that 71 // will end up using it. 72 DetachFromThread(); 73 } 74 75 ComponentCloudPolicyStore::~ComponentCloudPolicyStore() { 76 DCHECK(CalledOnValidThread()); 77 } 78 79 // static 80 bool ComponentCloudPolicyStore::SupportsDomain(PolicyDomain domain) { 81 return GetDomainConstants(domain) != NULL; 82 } 83 84 // static 85 bool ComponentCloudPolicyStore::GetPolicyType(PolicyDomain domain, 86 std::string* policy_type) { 87 const DomainConstants* constants = GetDomainConstants(domain); 88 if (constants) 89 *policy_type = constants->policy_type; 90 return constants != NULL; 91 } 92 93 // static 94 bool ComponentCloudPolicyStore::GetPolicyDomain(const std::string& policy_type, 95 PolicyDomain* domain) { 96 const DomainConstants* constants = GetDomainConstantsForType(policy_type); 97 if (constants) 98 *domain = constants->domain; 99 return constants != NULL; 100 } 101 102 const std::string& ComponentCloudPolicyStore::GetCachedHash( 103 const PolicyNamespace& ns) const { 104 DCHECK(CalledOnValidThread()); 105 std::map<PolicyNamespace, std::string>::const_iterator it = 106 cached_hashes_.find(ns); 107 return it == cached_hashes_.end() ? base::EmptyString() : it->second; 108 } 109 110 void ComponentCloudPolicyStore::SetCredentials(const std::string& username, 111 const std::string& dm_token) { 112 DCHECK(CalledOnValidThread()); 113 DCHECK(username_.empty() || username == username_); 114 DCHECK(dm_token_.empty() || dm_token == dm_token_); 115 username_ = username; 116 dm_token_ = dm_token; 117 } 118 119 void ComponentCloudPolicyStore::Load() { 120 DCHECK(CalledOnValidThread()); 121 typedef std::map<std::string, std::string> ContentMap; 122 123 // Load all cached policy protobufs for each domain. 124 for (size_t domain = 0; domain < arraysize(kDomains); ++domain) { 125 const DomainConstants& constants = kDomains[domain]; 126 ContentMap protos; 127 cache_->LoadAllSubkeys(constants.proto_cache_key, &protos); 128 for (ContentMap::iterator it = protos.begin(); it != protos.end(); ++it) { 129 const std::string& id(it->first); 130 PolicyNamespace ns(constants.domain, id); 131 132 // Validate each protobuf. 133 scoped_ptr<em::PolicyFetchResponse> proto(new em::PolicyFetchResponse); 134 em::ExternalPolicyData payload; 135 if (!proto->ParseFromString(it->second) || 136 !ValidateProto( 137 proto.Pass(), constants.policy_type, id, &payload, NULL)) { 138 Delete(ns); 139 continue; 140 } 141 142 // The protobuf looks good; load the policy data. 143 std::string data; 144 PolicyMap policy; 145 if (cache_->Load(constants.data_cache_key, id, &data) && 146 ValidateData(data, payload.secure_hash(), &policy)) { 147 // The data is also good; expose the policies. 148 policy_bundle_.Get(ns).Swap(&policy); 149 cached_hashes_[ns] = payload.secure_hash(); 150 } else { 151 // The data for this proto couldn't be loaded or is corrupted. 152 Delete(ns); 153 } 154 } 155 } 156 } 157 158 bool ComponentCloudPolicyStore::Store(const PolicyNamespace& ns, 159 const std::string& serialized_policy, 160 const std::string& secure_hash, 161 const std::string& data) { 162 DCHECK(CalledOnValidThread()); 163 const DomainConstants* constants = GetDomainConstants(ns.domain); 164 PolicyMap policy; 165 // |serialized_policy| has already been validated; validate the data now. 166 if (!constants || !ValidateData(data, secure_hash, &policy)) 167 return false; 168 169 // Flush the proto and the data to the cache. 170 cache_->Store(constants->proto_cache_key, ns.component_id, serialized_policy); 171 cache_->Store(constants->data_cache_key, ns.component_id, data); 172 // And expose the policy. 173 policy_bundle_.Get(ns).Swap(&policy); 174 cached_hashes_[ns] = secure_hash; 175 delegate_->OnComponentCloudPolicyStoreUpdated(); 176 return true; 177 } 178 179 void ComponentCloudPolicyStore::Delete(const PolicyNamespace& ns) { 180 DCHECK(CalledOnValidThread()); 181 const DomainConstants* constants = GetDomainConstants(ns.domain); 182 if (!constants) 183 return; 184 185 cache_->Delete(constants->proto_cache_key, ns.component_id); 186 cache_->Delete(constants->data_cache_key, ns.component_id); 187 188 if (!policy_bundle_.Get(ns).empty()) { 189 policy_bundle_.Get(ns).Clear(); 190 delegate_->OnComponentCloudPolicyStoreUpdated(); 191 } 192 } 193 194 void ComponentCloudPolicyStore::Purge( 195 PolicyDomain domain, 196 const ResourceCache::SubkeyFilter& filter) { 197 DCHECK(CalledOnValidThread()); 198 const DomainConstants* constants = GetDomainConstants(domain); 199 if (!constants) 200 return; 201 202 cache_->FilterSubkeys(constants->proto_cache_key, filter); 203 cache_->FilterSubkeys(constants->data_cache_key, filter); 204 205 // Stop serving policies for purged namespaces. 206 bool purged_current_policies = false; 207 for (PolicyBundle::const_iterator it = policy_bundle_.begin(); 208 it != policy_bundle_.end(); ++it) { 209 if (it->first.domain == domain && 210 filter.Run(it->first.component_id) && 211 !policy_bundle_.Get(it->first).empty()) { 212 policy_bundle_.Get(it->first).Clear(); 213 purged_current_policies = true; 214 } 215 } 216 217 // Purge cached hashes, so that those namespaces can be fetched again if the 218 // policy state changes. 219 std::map<PolicyNamespace, std::string>::iterator it = cached_hashes_.begin(); 220 while (it != cached_hashes_.end()) { 221 if (it->first.domain == domain && filter.Run(it->first.component_id)) { 222 std::map<PolicyNamespace, std::string>::iterator prev = it; 223 ++it; 224 cached_hashes_.erase(prev); 225 } else { 226 ++it; 227 } 228 } 229 230 if (purged_current_policies) 231 delegate_->OnComponentCloudPolicyStoreUpdated(); 232 } 233 234 void ComponentCloudPolicyStore::Clear() { 235 for (size_t i = 0; i < arraysize(kDomains); ++i) { 236 cache_->Clear(kDomains[i].proto_cache_key); 237 cache_->Clear(kDomains[i].data_cache_key); 238 } 239 cached_hashes_.clear(); 240 const PolicyBundle empty_bundle; 241 if (!policy_bundle_.Equals(empty_bundle)) { 242 policy_bundle_.Clear(); 243 delegate_->OnComponentCloudPolicyStoreUpdated(); 244 } 245 } 246 247 bool ComponentCloudPolicyStore::ValidatePolicy( 248 scoped_ptr<em::PolicyFetchResponse> proto, 249 PolicyNamespace* ns, 250 em::ExternalPolicyData* payload) { 251 em::PolicyData policy_data; 252 if (!ValidateProto( 253 proto.Pass(), std::string(), std::string(), payload, &policy_data)) { 254 return false; 255 } 256 257 if (!policy_data.has_policy_type()) 258 return false; 259 260 const DomainConstants* constants = 261 GetDomainConstantsForType(policy_data.policy_type()); 262 if (!constants || !policy_data.has_settings_entity_id()) 263 return false; 264 265 ns->domain = constants->domain; 266 ns->component_id = policy_data.settings_entity_id(); 267 return true; 268 } 269 270 bool ComponentCloudPolicyStore::ValidateProto( 271 scoped_ptr<em::PolicyFetchResponse> proto, 272 const std::string& policy_type, 273 const std::string& settings_entity_id, 274 em::ExternalPolicyData* payload, 275 em::PolicyData* policy_data) { 276 if (username_.empty() || dm_token_.empty()) 277 return false; 278 279 scoped_ptr<ComponentCloudPolicyValidator> validator( 280 ComponentCloudPolicyValidator::Create( 281 proto.Pass(), scoped_refptr<base::SequencedTaskRunner>())); 282 validator->ValidateUsername(username_, true); 283 validator->ValidateDMToken(dm_token_, 284 ComponentCloudPolicyValidator::DM_TOKEN_REQUIRED); 285 if (!policy_type.empty()) 286 validator->ValidatePolicyType(policy_type); 287 if (!settings_entity_id.empty()) 288 validator->ValidateSettingsEntityId(settings_entity_id); 289 validator->ValidatePayload(); 290 // TODO(joaodasilva): validate signature. 291 validator->RunValidation(); 292 if (!validator->success()) 293 return false; 294 295 em::ExternalPolicyData* data = validator->payload().get(); 296 // The download URL must be empty, or must be a valid URL. 297 // An empty download URL signals that this component doesn't have cloud 298 // policy, or that the policy has been removed. 299 if (data->has_download_url() && !data->download_url().empty()) { 300 if (!GURL(data->download_url()).is_valid() || 301 !data->has_secure_hash() || 302 data->secure_hash().empty()) { 303 return false; 304 } 305 } else if (data->has_secure_hash()) { 306 return false; 307 } 308 309 if (payload) 310 payload->Swap(validator->payload().get()); 311 if (policy_data) 312 policy_data->Swap(validator->policy_data().get()); 313 return true; 314 } 315 316 bool ComponentCloudPolicyStore::ValidateData( 317 const std::string& data, 318 const std::string& secure_hash, 319 PolicyMap* policy) { 320 return crypto::SHA256HashString(data) == secure_hash && 321 ParsePolicy(data, policy); 322 } 323 324 bool ComponentCloudPolicyStore::ParsePolicy(const std::string& data, 325 PolicyMap* policy) { 326 scoped_ptr<base::Value> json(base::JSONReader::Read( 327 data, base::JSON_PARSE_RFC | base::JSON_DETACHABLE_CHILDREN)); 328 base::DictionaryValue* dict = NULL; 329 if (!json || !json->GetAsDictionary(&dict)) 330 return false; 331 332 // Each top-level key maps a policy name to its description. 333 // 334 // Each description is an object that contains the policy value under the 335 // "Value" key. The optional "Level" key is either "Mandatory" (default) or 336 // "Recommended". 337 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { 338 base::DictionaryValue* description = NULL; 339 if (!dict->GetDictionaryWithoutPathExpansion(it.key(), &description)) 340 return false; 341 342 scoped_ptr<base::Value> value; 343 if (!description->RemoveWithoutPathExpansion(kValue, &value)) 344 return false; 345 346 PolicyLevel level = POLICY_LEVEL_MANDATORY; 347 std::string level_string; 348 if (description->GetStringWithoutPathExpansion(kLevel, &level_string) && 349 level_string == kRecommended) { 350 level = POLICY_LEVEL_RECOMMENDED; 351 } 352 353 // If policy for components is ever used for device-level settings then 354 // this must support a configurable scope; assuming POLICY_SCOPE_USER is 355 // fine for now. 356 policy->Set(it.key(), level, POLICY_SCOPE_USER, value.release(), NULL); 357 } 358 359 return true; 360 } 361 362 } // namespace policy 363