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/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/sha1.h" | |
11 #include "base/strings/string_util.h" | |
12 #include "base/values.h" | |
13 #include "chrome/browser/policy/cloud/cloud_policy_constants.h" | |
14 #include "chrome/browser/policy/cloud/cloud_policy_validator.h" | |
15 #include "chrome/browser/policy/proto/cloud/chrome_extension_policy.pb.h" | |
16 #include "chrome/browser/policy/proto/cloud/device_management_backend.pb.h" | |
17 #include "components/policy/core/common/external_data_fetcher.h" | |
18 #include "components/policy/core/common/policy_map.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_); | |
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 base::SHA1HashString(data) == secure_hash && ParsePolicy(data, policy); | |
321 } | |
322 | |
323 bool ComponentCloudPolicyStore::ParsePolicy(const std::string& data, | |
324 PolicyMap* policy) { | |
325 scoped_ptr<base::Value> json(base::JSONReader::Read( | |
326 data, base::JSON_PARSE_RFC | base::JSON_DETACHABLE_CHILDREN)); | |
327 base::DictionaryValue* dict = NULL; | |
328 if (!json || !json->GetAsDictionary(&dict)) | |
329 return false; | |
330 | |
331 // Each top-level key maps a policy name to its description. | |
332 // | |
333 // Each description is an object that contains the policy value under the | |
334 // "Value" key. The optional "Level" key is either "Mandatory" (default) or | |
335 // "Recommended". | |
336 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { | |
337 base::DictionaryValue* description = NULL; | |
338 if (!dict->GetDictionaryWithoutPathExpansion(it.key(), &description)) | |
339 return false; | |
340 | |
341 scoped_ptr<base::Value> value; | |
342 if (!description->RemoveWithoutPathExpansion(kValue, &value)) | |
343 return false; | |
344 | |
345 PolicyLevel level = POLICY_LEVEL_MANDATORY; | |
346 std::string level_string; | |
347 if (description->GetStringWithoutPathExpansion(kLevel, &level_string) && | |
348 level_string == kRecommended) { | |
349 level = POLICY_LEVEL_RECOMMENDED; | |
350 } | |
351 | |
352 // If policy for components is ever used for device-level settings then | |
353 // this must support a configurable scope; assuming POLICY_SCOPE_USER is | |
354 // fine for now. | |
355 policy->Set(it.key(), level, POLICY_SCOPE_USER, value.release(), NULL); | |
356 } | |
357 | |
358 return true; | |
359 } | |
360 | |
361 } // namespace policy | |
OLD | NEW |