OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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_policy_cache.h" | |
6 | |
7 #include <limits> | |
8 | |
9 #include "base/file_util.h" | |
10 #include "base/logging.h" | |
11 #include "base/task.h" | |
12 #include "base/values.h" | |
13 #include "chrome/browser/policy/configuration_policy_pref_store.h" | |
14 #include "chrome/browser/policy/proto/cloud_policy.pb.h" | |
15 #include "chrome/browser/policy/proto/device_management_constants.h" | |
16 #include "chrome/browser/policy/proto/device_management_local.pb.h" | |
17 #include "content/browser/browser_thread.h" | |
18 | |
19 using google::protobuf::RepeatedField; | |
20 using google::protobuf::RepeatedPtrField; | |
21 | |
22 // This CloudPolicyCache currently supports two protocols for the interaction | |
23 // with DMServer: the old "DevicePolicy" format, which is being used in the | |
24 // CrOS Pilot Program and will be deprecated afterwards, and the new | |
25 // "CloudPolicy" format, which will be used exclusively after the public launch | |
26 // of ChromeOS. | |
27 | |
28 namespace policy { | |
29 | |
30 // Decodes a CloudPolicySettings object into two maps with mandatory and | |
31 // recommended settings, respectively. The implementation is generated code | |
32 // in policy/cloud_policy_generated.cc. | |
33 void DecodePolicy(const em::CloudPolicySettings& policy, | |
34 PolicyMap* mandatory, PolicyMap* recommended); | |
35 | |
36 // A thin ConfigurationPolicyProvider implementation sitting on top of | |
37 // CloudPolicyCache for hooking up with ConfigurationPolicyPrefStore. | |
38 class CloudPolicyCache::CloudPolicyProvider | |
39 : public ConfigurationPolicyProvider { | |
40 public: | |
41 CloudPolicyProvider(const PolicyDefinitionList* policy_list, | |
42 CloudPolicyCache* cache, | |
43 CloudPolicyCache::PolicyLevel level) | |
44 : ConfigurationPolicyProvider(policy_list), | |
45 cache_(cache), | |
46 level_(level) {} | |
47 virtual ~CloudPolicyProvider() {} | |
48 | |
49 virtual bool Provide(ConfigurationPolicyStoreInterface* store) { | |
50 if (!cache_->has_device_policy()) { | |
51 if (level_ == POLICY_LEVEL_MANDATORY) | |
52 ApplyPolicyMap(&cache_->mandatory_policy_, store); | |
53 else if (level_ == POLICY_LEVEL_RECOMMENDED) | |
54 ApplyPolicyMap(&cache_->recommended_policy_, store); | |
55 } else { | |
56 ApplyPolicyValueTree(cache_->device_policy_.get(), store); | |
57 } | |
58 return true; | |
59 } | |
60 | |
61 virtual bool IsInitializationComplete() const { | |
62 return cache_->initialization_complete_; | |
63 } | |
64 | |
65 virtual void AddObserver(ConfigurationPolicyProvider::Observer* observer) { | |
66 cache_->observer_list_.AddObserver(observer); | |
67 } | |
68 virtual void RemoveObserver(ConfigurationPolicyProvider::Observer* observer) { | |
69 cache_->observer_list_.RemoveObserver(observer); | |
70 } | |
71 | |
72 private: | |
73 // The underlying policy cache. | |
74 CloudPolicyCache* cache_; | |
75 // Policy level this provider will handle. | |
76 CloudPolicyCache::PolicyLevel level_; | |
77 | |
78 DISALLOW_COPY_AND_ASSIGN(CloudPolicyProvider); | |
79 }; | |
80 | |
81 // Saves policy information to a file. | |
82 class PersistPolicyTask : public Task { | |
83 public: | |
84 PersistPolicyTask(const FilePath& path, | |
85 const em::PolicyFetchResponse* cloud_policy_response, | |
86 const em::DevicePolicyResponse* device_policy_response, | |
87 const bool is_unmanaged) | |
88 : path_(path), | |
89 cloud_policy_response_(cloud_policy_response), | |
90 device_policy_response_(device_policy_response), | |
91 is_unmanaged_(is_unmanaged) {} | |
92 | |
93 private: | |
94 // Task override. | |
95 virtual void Run(); | |
96 | |
97 const FilePath path_; | |
98 scoped_ptr<const em::PolicyFetchResponse> cloud_policy_response_; | |
99 scoped_ptr<const em::DevicePolicyResponse> device_policy_response_; | |
100 const bool is_unmanaged_; | |
101 }; | |
102 | |
103 void PersistPolicyTask::Run() { | |
104 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
105 std::string data; | |
106 em::CachedCloudPolicyResponse cached_policy; | |
107 if (cloud_policy_response_.get()) { | |
108 cached_policy.mutable_cloud_policy()->CopyFrom(*cloud_policy_response_); | |
109 } else if (device_policy_response_.get()) { | |
110 cached_policy.mutable_device_policy()->CopyFrom(*device_policy_response_); | |
111 cached_policy.set_timestamp(base::Time::NowFromSystemTime().ToTimeT()); | |
112 } | |
113 if (is_unmanaged_) { | |
114 cached_policy.set_unmanaged(true); | |
115 cached_policy.set_timestamp(base::Time::NowFromSystemTime().ToTimeT()); | |
116 } | |
117 if (!cached_policy.SerializeToString(&data)) { | |
118 LOG(WARNING) << "Failed to serialize policy data"; | |
119 return; | |
120 } | |
121 | |
122 int size = data.size(); | |
123 if (file_util::WriteFile(path_, data.c_str(), size) != size) { | |
124 LOG(WARNING) << "Failed to write " << path_.value(); | |
125 return; | |
126 } | |
127 } | |
128 | |
129 CloudPolicyCache::CloudPolicyCache( | |
130 const FilePath& backing_file_path) | |
131 : backing_file_path_(backing_file_path), | |
132 device_policy_(new DictionaryValue), | |
133 initialization_complete_(false), | |
134 is_unmanaged_(false), | |
135 has_device_policy_(false) { | |
136 managed_policy_provider_.reset( | |
137 new CloudPolicyProvider( | |
138 ConfigurationPolicyPrefStore::GetChromePolicyDefinitionList(), | |
139 this, | |
140 POLICY_LEVEL_MANDATORY)); | |
141 recommended_policy_provider_.reset( | |
142 new CloudPolicyProvider( | |
143 ConfigurationPolicyPrefStore::GetChromePolicyDefinitionList(), | |
144 this, | |
145 POLICY_LEVEL_RECOMMENDED)); | |
146 } | |
147 | |
148 CloudPolicyCache::~CloudPolicyCache() { | |
149 FOR_EACH_OBSERVER(ConfigurationPolicyProvider::Observer, | |
150 observer_list_, OnProviderGoingAway()); | |
151 } | |
152 | |
153 void CloudPolicyCache::LoadFromFile() { | |
154 // TODO(jkummerow): This method is doing file IO during browser startup. In | |
155 // the long run it would be better to delay this until the FILE thread exists. | |
156 if (!file_util::PathExists(backing_file_path_) || initialization_complete_) { | |
157 return; | |
158 } | |
159 | |
160 // Read the protobuf from the file. | |
161 std::string data; | |
162 if (!file_util::ReadFileToString(backing_file_path_, &data)) { | |
163 LOG(WARNING) << "Failed to read policy data from " | |
164 << backing_file_path_.value(); | |
165 return; | |
166 } | |
167 | |
168 em::CachedCloudPolicyResponse cached_response; | |
169 if (!cached_response.ParseFromArray(data.c_str(), data.size())) { | |
170 LOG(WARNING) << "Failed to parse policy data read from " | |
171 << backing_file_path_.value(); | |
172 return; | |
173 } | |
174 base::Time timestamp; | |
175 PolicyMap mandatory_policy; | |
176 PolicyMap recommended_policy; | |
177 is_unmanaged_ = cached_response.unmanaged(); | |
178 if (is_unmanaged_ || cached_response.has_device_policy()) | |
179 timestamp = base::Time::FromTimeT(cached_response.timestamp()); | |
180 if (cached_response.has_cloud_policy()) { | |
181 DCHECK(!is_unmanaged_); | |
182 bool ok = DecodePolicyResponse(cached_response.cloud_policy(), | |
183 &mandatory_policy, | |
184 &recommended_policy, | |
185 ×tamp); | |
186 if (!ok) { | |
187 LOG(WARNING) << "Decoding policy data failed."; | |
188 return; | |
189 } | |
190 } | |
191 if (timestamp > base::Time::NowFromSystemTime()) { | |
192 LOG(WARNING) << "Rejected policy data from " << backing_file_path_.value() | |
193 << ", file is from the future."; | |
194 return; | |
195 } | |
196 // Swap in the new policy information. | |
197 if (cached_response.has_cloud_policy()) { | |
198 mandatory_policy_.Swap(&mandatory_policy); | |
199 recommended_policy_.Swap(&recommended_policy); | |
200 has_device_policy_ = false; | |
201 } else if (cached_response.has_device_policy()) { | |
202 scoped_ptr<DictionaryValue> value( | |
203 DecodeDevicePolicy(cached_response.device_policy())); | |
204 device_policy_.reset(value.release()); | |
205 has_device_policy_ = true; | |
206 } | |
207 last_policy_refresh_time_ = timestamp; | |
208 initialization_complete_ = true; | |
209 | |
210 FOR_EACH_OBSERVER(ConfigurationPolicyProvider::Observer, | |
211 observer_list_, OnUpdatePolicy()); | |
212 } | |
213 | |
214 void CloudPolicyCache::SetPolicy(const em::PolicyFetchResponse& policy) { | |
215 DCHECK(CalledOnValidThread()); | |
216 bool initialization_was_not_complete = !initialization_complete_; | |
217 is_unmanaged_ = false; | |
218 last_policy_refresh_time_ = base::Time::NowFromSystemTime(); | |
219 base::Time timestamp; | |
220 PolicyMap mandatory_policy; | |
221 PolicyMap recommended_policy; | |
222 bool ok = DecodePolicyResponse(policy, &mandatory_policy, &recommended_policy, | |
223 ×tamp); | |
224 if (!ok) | |
225 return; | |
226 | |
227 const bool new_policy_differs = | |
228 !mandatory_policy_.Equals(mandatory_policy) || | |
229 !recommended_policy_.Equals(recommended_policy); | |
230 mandatory_policy_.Swap(&mandatory_policy); | |
231 recommended_policy_.Swap(&recommended_policy); | |
232 initialization_complete_ = true; | |
233 has_device_policy_ = false; | |
234 | |
235 if (new_policy_differs || initialization_was_not_complete) { | |
236 FOR_EACH_OBSERVER(ConfigurationPolicyProvider::Observer, | |
237 observer_list_, OnUpdatePolicy()); | |
238 } | |
239 | |
240 if (timestamp > base::Time::NowFromSystemTime() + | |
241 base::TimeDelta::FromMinutes(1)) { | |
242 LOG(WARNING) << "Server returned policy with timestamp from the future, " | |
243 "not persisting to disk."; | |
244 } else { | |
245 em::PolicyFetchResponse* policy_copy = new em::PolicyFetchResponse; | |
246 policy_copy->CopyFrom(policy); | |
247 BrowserThread::PostTask( | |
248 BrowserThread::FILE, | |
249 FROM_HERE, | |
250 new PersistPolicyTask(backing_file_path_, policy_copy, NULL, false)); | |
251 } | |
252 } | |
253 | |
254 void CloudPolicyCache::SetDevicePolicy(const em::DevicePolicyResponse& policy) { | |
255 DCHECK(CalledOnValidThread()); | |
256 bool initialization_was_not_complete = !initialization_complete_; | |
257 is_unmanaged_ = false; | |
258 DictionaryValue* value = DecodeDevicePolicy(policy); | |
259 const bool new_policy_differs = !(value->Equals(device_policy_.get())); | |
260 base::Time now(base::Time::NowFromSystemTime()); | |
261 device_policy_.reset(value); | |
262 initialization_complete_ = true; | |
263 last_policy_refresh_time_ = now; | |
264 has_device_policy_ = true; | |
265 | |
266 if (new_policy_differs || initialization_was_not_complete) { | |
267 FOR_EACH_OBSERVER(ConfigurationPolicyProvider::Observer, | |
268 observer_list_, OnUpdatePolicy()); | |
269 } | |
270 | |
271 em::DevicePolicyResponse* policy_copy = new em::DevicePolicyResponse; | |
272 policy_copy->CopyFrom(policy); | |
273 BrowserThread::PostTask( | |
274 BrowserThread::FILE, | |
275 FROM_HERE, | |
276 new PersistPolicyTask(backing_file_path_, NULL, policy_copy, false)); | |
277 } | |
278 | |
279 ConfigurationPolicyProvider* CloudPolicyCache::GetManagedPolicyProvider() { | |
280 DCHECK(CalledOnValidThread()); | |
281 return managed_policy_provider_.get(); | |
282 } | |
283 | |
284 ConfigurationPolicyProvider* CloudPolicyCache::GetRecommendedPolicyProvider() { | |
285 DCHECK(CalledOnValidThread()); | |
286 return recommended_policy_provider_.get(); | |
287 } | |
288 | |
289 void CloudPolicyCache::SetUnmanaged() { | |
290 DCHECK(CalledOnValidThread()); | |
291 is_unmanaged_ = true; | |
292 mandatory_policy_.Clear(); | |
293 recommended_policy_.Clear(); | |
294 device_policy_.reset(new DictionaryValue); | |
295 last_policy_refresh_time_ = base::Time::NowFromSystemTime(); | |
296 | |
297 FOR_EACH_OBSERVER(ConfigurationPolicyProvider::Observer, | |
298 observer_list_, OnUpdatePolicy()); | |
299 | |
300 BrowserThread::PostTask( | |
301 BrowserThread::FILE, | |
302 FROM_HERE, | |
303 new PersistPolicyTask(backing_file_path_, NULL, NULL, true)); | |
304 } | |
305 | |
306 // static | |
307 bool CloudPolicyCache::DecodePolicyResponse( | |
308 const em::PolicyFetchResponse& policy_response, | |
309 PolicyMap* mandatory, | |
310 PolicyMap* recommended, | |
311 base::Time* timestamp) { | |
312 std::string data = policy_response.policy_data(); | |
313 | |
314 em::PolicyData policy_data; | |
315 if (!policy_data.ParseFromString(data)) { | |
316 LOG(WARNING) << "Failed to parse PolicyData protobuf."; | |
317 return false; | |
318 } | |
319 | |
320 // TODO(jkummerow): Verify policy_data.device_token(). Needs final | |
321 // specification which token we're actually sending / expecting to get back. | |
322 | |
323 // TODO(jkummerow): Store policy_data.device_name(), if we decide to transfer | |
324 // it from the server to the client. | |
325 | |
326 *timestamp = base::Time::UnixEpoch() + | |
327 base::TimeDelta::FromMilliseconds(policy_data.timestamp()); | |
328 em::CloudPolicySettings policy; | |
329 if (!policy.ParseFromString(policy_data.policy_value())) { | |
330 LOG(WARNING) << "Failed to parse CloudPolicySettings protobuf."; | |
331 return false; | |
332 } | |
333 DecodePolicy(policy, mandatory, recommended); | |
334 return true; | |
335 } | |
336 | |
337 // static | |
338 bool CloudPolicyCache::VerifySignature( | |
339 const std::string& signature, | |
340 const std::string& data, | |
341 const RepeatedPtrField<std::string>& certificate_chain) { | |
342 // TODO(jkummerow): Implement this. Non-trivial because we want to do it | |
343 // for all platforms -> it's enough work to deserve its own CL. | |
344 // Don't forget to also verify the hostname of the server against the cert. | |
345 return true; | |
346 } | |
347 | |
348 // static | |
349 Value* CloudPolicyCache::DecodeIntegerValue(google::protobuf::int64 value) { | |
350 if (value < std::numeric_limits<int>::min() || | |
351 value > std::numeric_limits<int>::max()) { | |
352 LOG(WARNING) << "Integer value " << value | |
353 << " out of numeric limits, ignoring."; | |
354 return NULL; | |
355 } | |
356 | |
357 return Value::CreateIntegerValue(static_cast<int>(value)); | |
358 } | |
359 | |
360 // static | |
361 Value* CloudPolicyCache::DecodeValue(const em::GenericValue& value) { | |
362 if (!value.has_value_type()) | |
363 return NULL; | |
364 | |
365 switch (value.value_type()) { | |
366 case em::GenericValue::VALUE_TYPE_BOOL: | |
367 if (value.has_bool_value()) | |
368 return Value::CreateBooleanValue(value.bool_value()); | |
369 return NULL; | |
370 case em::GenericValue::VALUE_TYPE_INT64: | |
371 if (value.has_int64_value()) | |
372 return DecodeIntegerValue(value.int64_value()); | |
373 return NULL; | |
374 case em::GenericValue::VALUE_TYPE_STRING: | |
375 if (value.has_string_value()) | |
376 return Value::CreateStringValue(value.string_value()); | |
377 return NULL; | |
378 case em::GenericValue::VALUE_TYPE_DOUBLE: | |
379 if (value.has_double_value()) | |
380 return Value::CreateDoubleValue(value.double_value()); | |
381 return NULL; | |
382 case em::GenericValue::VALUE_TYPE_BYTES: | |
383 if (value.has_bytes_value()) { | |
384 std::string bytes = value.bytes_value(); | |
385 return BinaryValue::CreateWithCopiedBuffer(bytes.c_str(), bytes.size()); | |
386 } | |
387 return NULL; | |
388 case em::GenericValue::VALUE_TYPE_BOOL_ARRAY: { | |
389 ListValue* list = new ListValue; | |
390 RepeatedField<bool>::const_iterator i; | |
391 for (i = value.bool_array().begin(); i != value.bool_array().end(); ++i) | |
392 list->Append(Value::CreateBooleanValue(*i)); | |
393 return list; | |
394 } | |
395 case em::GenericValue::VALUE_TYPE_INT64_ARRAY: { | |
396 ListValue* list = new ListValue; | |
397 RepeatedField<google::protobuf::int64>::const_iterator i; | |
398 for (i = value.int64_array().begin(); | |
399 i != value.int64_array().end(); ++i) { | |
400 Value* int_value = DecodeIntegerValue(*i); | |
401 if (int_value) | |
402 list->Append(int_value); | |
403 } | |
404 return list; | |
405 } | |
406 case em::GenericValue::VALUE_TYPE_STRING_ARRAY: { | |
407 ListValue* list = new ListValue; | |
408 RepeatedPtrField<std::string>::const_iterator i; | |
409 for (i = value.string_array().begin(); | |
410 i != value.string_array().end(); ++i) | |
411 list->Append(Value::CreateStringValue(*i)); | |
412 return list; | |
413 } | |
414 case em::GenericValue::VALUE_TYPE_DOUBLE_ARRAY: { | |
415 ListValue* list = new ListValue; | |
416 RepeatedField<double>::const_iterator i; | |
417 for (i = value.double_array().begin(); | |
418 i != value.double_array().end(); ++i) | |
419 list->Append(Value::CreateDoubleValue(*i)); | |
420 return list; | |
421 } | |
422 default: | |
423 NOTREACHED() << "Unhandled value type"; | |
424 } | |
425 | |
426 return NULL; | |
427 } | |
428 | |
429 // static | |
430 DictionaryValue* CloudPolicyCache::DecodeDevicePolicy( | |
431 const em::DevicePolicyResponse& policy) { | |
432 DictionaryValue* result = new DictionaryValue; | |
433 RepeatedPtrField<em::DevicePolicySetting>::const_iterator setting; | |
434 for (setting = policy.setting().begin(); | |
435 setting != policy.setting().end(); | |
436 ++setting) { | |
437 // Wrong policy key? Skip. | |
438 if (setting->policy_key().compare(kChromeDevicePolicySettingKey) != 0) | |
439 continue; | |
440 | |
441 // No policy value? Skip. | |
442 if (!setting->has_policy_value()) | |
443 continue; | |
444 | |
445 // Iterate through all the name-value pairs wrapped in |setting|. | |
446 const em::GenericSetting& policy_value(setting->policy_value()); | |
447 RepeatedPtrField<em::GenericNamedValue>::const_iterator named_value; | |
448 for (named_value = policy_value.named_value().begin(); | |
449 named_value != policy_value.named_value().end(); | |
450 ++named_value) { | |
451 if (named_value->has_value()) { | |
452 Value* decoded_value = | |
453 CloudPolicyCache::DecodeValue(named_value->value()); | |
454 if (decoded_value) | |
455 result->Set(named_value->name(), decoded_value); | |
456 } | |
457 } | |
458 } | |
459 return result; | |
460 } | |
461 | |
462 } // namespace policy | |
OLD | NEW |