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