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 |