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_updater.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/location.h" | |
11 #include "base/logging.h" | |
12 #include "base/sequenced_task_runner.h" | |
13 #include "base/stl_util.h" | |
14 #include "chrome/browser/policy/component_cloud_policy_store.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 "googleurl/src/gurl.h" | |
18 #include "net/base/backoff_entry.h" | |
19 #include "net/base/load_flags.h" | |
20 #include "net/base/net_errors.h" | |
21 #include "net/url_request/url_fetcher.h" | |
22 #include "net/url_request/url_fetcher_delegate.h" | |
23 #include "net/url_request/url_request_context_getter.h" | |
24 #include "net/url_request/url_request_status.h" | |
25 | |
26 namespace em = enterprise_management; | |
27 | |
28 namespace policy { | |
29 | |
30 namespace { | |
31 | |
32 // The maximum size of the serialized policy protobuf. | |
33 const size_t kPolicyProtoMaxSize = 16 * 1024; | |
34 | |
35 // The maximum size of the downloaded policy data. | |
36 const int64 kPolicyDataMaxSize = 5 * 1024 * 1024; | |
37 | |
38 // Policies for exponential backoff of failed requests. There are 3 policies, | |
39 // corresponding to the 3 RetrySchedule enum values below. | |
40 | |
41 // For temporary errors (HTTP 500, RST, etc). | |
42 const net::BackoffEntry::Policy kRetrySoonPolicy = { | |
43 // Number of initial errors to ignore before starting to back off. | |
44 0, | |
45 | |
46 // Initial delay in ms: 60 seconds. | |
47 1000 * 60, | |
48 | |
49 // Factor by which the waiting time is multiplied. | |
50 2, | |
51 | |
52 // Fuzzing percentage; this spreads delays randomly between 80% and 100% | |
53 // of the calculated time. | |
54 0.20, | |
55 | |
56 // Maximum delay in ms: 12 hours. | |
57 1000 * 60 * 60 * 12, | |
58 | |
59 // When to discard an entry: never. | |
60 -1, | |
61 | |
62 // |always_use_initial_delay|; false means that the initial delay is | |
63 // applied after the first error, and starts backing off from there. | |
64 false, | |
65 }; | |
66 | |
67 // For other errors (request failed, server errors). | |
68 const net::BackoffEntry::Policy kRetryLaterPolicy = { | |
69 // Number of initial errors to ignore before starting to back off. | |
70 0, | |
71 | |
72 // Initial delay in ms: 1 hour. | |
73 1000 * 60 * 60, | |
74 | |
75 // Factor by which the waiting time is multiplied. | |
76 2, | |
77 | |
78 // Fuzzing percentage; this spreads delays randomly between 80% and 100% | |
79 // of the calculated time. | |
80 0.20, | |
81 | |
82 // Maximum delay in ms: 12 hours. | |
83 1000 * 60 * 60 * 12, | |
84 | |
85 // When to discard an entry: never. | |
86 -1, | |
87 | |
88 // |always_use_initial_delay|; false means that the initial delay is | |
89 // applied after the first error, and starts backing off from there. | |
90 false, | |
91 }; | |
92 | |
93 // When the data fails validation (maybe because the policy URL and the data | |
94 // served at that URL are out of sync). This essentially retries every 12 hours, | |
95 // with some random jitter. | |
96 const net::BackoffEntry::Policy kRetryMuchLaterPolicy = { | |
97 // Number of initial errors to ignore before starting to back off. | |
98 0, | |
99 | |
100 // Initial delay in ms: 12 hours. | |
101 1000 * 60 * 60 * 12, | |
102 | |
103 // Factor by which the waiting time is multiplied. | |
104 2, | |
105 | |
106 // Fuzzing percentage; this spreads delays randomly between 80% and 100% | |
107 // of the calculated time. | |
108 0.20, | |
109 | |
110 // Maximum delay in ms: 12 hours. | |
111 1000 * 60 * 60 * 12, | |
112 | |
113 // When to discard an entry: never. | |
114 -1, | |
115 | |
116 // |always_use_initial_delay|; false means that the initial delay is | |
117 // applied after the first error, and starts backing off from there. | |
118 false, | |
119 }; | |
120 | |
121 // Maximum number of retries for requests that aren't likely to get a | |
122 // different response (e.g. HTTP 4xx replies). | |
123 const int kMaxLimitedRetries = 3; | |
124 | |
125 } // namespace | |
126 | |
127 // Each FetchJob contains the data about a particular component, and handles | |
128 // the downloading of its corresponding data. These objects are owned by the | |
129 // updater, and the updater always outlives FetchJobs. | |
130 // A FetchJob can be scheduled for a retry later, but its data never changes. | |
131 // If the ExternalPolicyData for a particular component changes then a new | |
132 // FetchJob is created, and the previous one is discarded. | |
133 class ComponentCloudPolicyUpdater::FetchJob | |
134 : public base::SupportsWeakPtr<FetchJob>, | |
135 public net::URLFetcherDelegate { | |
136 public: | |
137 FetchJob(ComponentCloudPolicyUpdater* updater, | |
138 const PolicyNamespace& ns, | |
139 const std::string& serialized_response, | |
140 const em::ExternalPolicyData& data); | |
141 virtual ~FetchJob(); | |
142 | |
143 const PolicyNamespace& policy_namespace() const { return ns_; } | |
144 | |
145 // Returns true if |other| equals |data_|. | |
146 bool ParamsEquals(const em::ExternalPolicyData& other); | |
147 | |
148 void StartJob(); | |
149 | |
150 // URLFetcherDelegate implementation: | |
151 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; | |
152 virtual void OnURLFetchDownloadProgress(const net::URLFetcher* source, | |
153 int64 current, | |
154 int64 total) OVERRIDE; | |
155 | |
156 private: | |
157 void OnSucceeded(); | |
158 void OnFailed(net::BackoffEntry* backoff_entry); | |
159 void Schedule(); | |
160 | |
161 // Always valid as long as |this| is alive. | |
162 ComponentCloudPolicyUpdater* updater_; | |
163 | |
164 const PolicyNamespace ns_; | |
165 const std::string serialized_response_; | |
166 const em::ExternalPolicyData data_; | |
167 | |
168 // If |fetcher_| exists then |this| is the current job, and must call either | |
169 // OnSucceeded or OnFailed. | |
170 scoped_ptr<net::URLFetcher> fetcher_; | |
171 | |
172 // Some errors should trigger a limited number of retries, even with backoff. | |
173 // This counts the number of such retries, to stop retrying once the limit | |
174 // is reached. | |
175 int limited_retries_count_; | |
176 | |
177 // Various delays to retry a failed download, depending on the failure reason. | |
178 net::BackoffEntry retry_soon_entry_; | |
179 net::BackoffEntry retry_later_entry_; | |
180 net::BackoffEntry retry_much_later_entry_; | |
181 | |
182 DISALLOW_COPY_AND_ASSIGN(FetchJob); | |
183 }; | |
184 | |
185 ComponentCloudPolicyUpdater::FetchJob::FetchJob( | |
186 ComponentCloudPolicyUpdater* updater, | |
187 const PolicyNamespace& ns, | |
188 const std::string& serialized_response, | |
189 const em::ExternalPolicyData& data) | |
190 : updater_(updater), | |
191 ns_(ns), | |
192 serialized_response_(serialized_response), | |
193 data_(data), | |
194 limited_retries_count_(0), | |
195 retry_soon_entry_(&kRetrySoonPolicy), | |
196 retry_later_entry_(&kRetryLaterPolicy), | |
197 retry_much_later_entry_(&kRetryMuchLaterPolicy) {} | |
198 | |
199 ComponentCloudPolicyUpdater::FetchJob::~FetchJob() { | |
200 if (fetcher_) { | |
201 fetcher_.reset(); | |
202 // This is the current job; inform the updater that it was cancelled. | |
203 updater_->OnJobFailed(this); | |
204 } | |
205 } | |
206 | |
207 bool ComponentCloudPolicyUpdater::FetchJob::ParamsEquals( | |
208 const em::ExternalPolicyData& other) { | |
209 return data_.download_url() == other.download_url() && | |
210 data_.secure_hash() == other.secure_hash() && | |
211 data_.download_auth_method() == other.download_auth_method(); | |
212 } | |
213 | |
214 void ComponentCloudPolicyUpdater::FetchJob::StartJob() { | |
215 fetcher_.reset(net::URLFetcher::Create( | |
216 0, GURL(data_.download_url()), net::URLFetcher::GET, this)); | |
217 fetcher_->SetRequestContext(updater_->request_context_); | |
218 fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | | |
219 net::LOAD_DISABLE_CACHE | | |
220 net::LOAD_DO_NOT_SAVE_COOKIES | | |
221 net::LOAD_IS_DOWNLOAD | | |
222 net::LOAD_DO_NOT_SEND_COOKIES | | |
223 net::LOAD_DO_NOT_SEND_AUTH_DATA); | |
224 fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); | |
225 fetcher_->Start(); | |
226 } | |
227 | |
228 void ComponentCloudPolicyUpdater::FetchJob::OnURLFetchComplete( | |
229 const net::URLFetcher* source) { | |
230 DCHECK(source == fetcher_.get()); | |
231 | |
232 const net::URLRequestStatus status = source->GetStatus(); | |
233 if (status.status() != net::URLRequestStatus::SUCCESS) { | |
234 if (status.error() == net::ERR_CONNECTION_RESET || | |
235 status.error() == net::ERR_TEMPORARILY_THROTTLED) { | |
236 // The connection was interrupted; try again soon. | |
237 OnFailed(&retry_soon_entry_); | |
238 return; | |
239 } else { | |
240 // Other network error; try again later. | |
241 OnFailed(&retry_later_entry_); | |
242 return; | |
243 } | |
244 } else { | |
245 // Status is success; inspect the HTTP response code. | |
246 if (source->GetResponseCode() >= 500) { | |
247 // Problem at the server; try again soon. | |
248 OnFailed(&retry_soon_entry_); | |
249 return; | |
250 } else if (source->GetResponseCode() >= 400) { | |
251 // Client error; this is unlikely to go away. Retry later, and give up | |
252 // retrying after 3 attempts. | |
253 OnFailed(limited_retries_count_ < kMaxLimitedRetries ? &retry_later_entry_ | |
254 : NULL); | |
255 limited_retries_count_++; | |
256 return; | |
257 } else if (source->GetResponseCode() != 200) { | |
258 // Other HTTP failure; try again later. | |
259 OnFailed(&retry_later_entry_); | |
260 return; | |
261 } | |
262 } | |
263 | |
264 std::string data; | |
265 if (!source->GetResponseAsString(&data) || | |
266 static_cast<int64>(data.size()) > kPolicyDataMaxSize || | |
267 !updater_->store_->Store( | |
268 ns_, serialized_response_, data_.secure_hash(), data)) { | |
269 // Failed to retrieve |data|, or it exceeds the size limit, or it failed | |
270 // validation. This may be a temporary error at the download URL. | |
271 OnFailed(&retry_much_later_entry_); | |
272 return; | |
273 } | |
274 | |
275 OnSucceeded(); | |
276 } | |
277 | |
278 void ComponentCloudPolicyUpdater::FetchJob::OnURLFetchDownloadProgress( | |
279 const net::URLFetcher* source, | |
280 int64 current, | |
281 int64 total) { | |
282 DCHECK(source == fetcher_.get()); | |
283 // Reject the data if it exceeds the size limit. The content length is in | |
284 // |total|, and it may be -1 when not known. | |
285 if (current > kPolicyDataMaxSize || total > kPolicyDataMaxSize) | |
286 OnFailed(&retry_much_later_entry_); | |
287 } | |
288 | |
289 void ComponentCloudPolicyUpdater::FetchJob::OnSucceeded() { | |
290 fetcher_.reset(); | |
291 updater_->OnJobSucceeded(this); | |
292 } | |
293 | |
294 void ComponentCloudPolicyUpdater::FetchJob::OnFailed(net::BackoffEntry* entry) { | |
295 fetcher_.reset(); | |
296 | |
297 if (entry) { | |
298 entry->InformOfRequest(false); | |
299 | |
300 // If new ExternalPolicyData for this component is fetched then this job | |
301 // will be deleted, and the retry task is invalidated. A new job using the | |
302 // new data will be scheduled immediately in that case. | |
303 updater_->task_runner_->PostDelayedTask( | |
304 FROM_HERE, | |
305 base::Bind(&FetchJob::Schedule, AsWeakPtr()), | |
306 entry->GetTimeUntilRelease()); | |
307 } | |
308 | |
309 updater_->OnJobFailed(this); | |
310 } | |
311 | |
312 void ComponentCloudPolicyUpdater::FetchJob::Schedule() { | |
313 updater_->ScheduleJob(this); | |
314 } | |
315 | |
316 ComponentCloudPolicyUpdater::ComponentCloudPolicyUpdater( | |
317 scoped_refptr<base::SequencedTaskRunner> task_runner, | |
318 scoped_refptr<net::URLRequestContextGetter> request_context, | |
319 ComponentCloudPolicyStore* store) | |
320 : task_runner_(task_runner), | |
321 request_context_(request_context), | |
322 store_(store), | |
323 shutting_down_(false) { | |
324 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
325 } | |
326 | |
327 ComponentCloudPolicyUpdater::~ComponentCloudPolicyUpdater() { | |
328 DCHECK(CalledOnValidThread()); | |
329 shutting_down_ = true; | |
330 STLDeleteValues(&fetch_jobs_); | |
331 } | |
332 | |
333 void ComponentCloudPolicyUpdater::UpdateExternalPolicy( | |
334 scoped_ptr<em::PolicyFetchResponse> response) { | |
335 DCHECK(CalledOnValidThread()); | |
336 | |
337 // Keep a serialized copy of |response|, to cache it later. | |
338 // The policy is also rejected if it exceeds the maximum size. | |
339 std::string serialized_response; | |
340 if (!response->SerializeToString(&serialized_response) || | |
341 serialized_response.size() > kPolicyProtoMaxSize) { | |
342 return; | |
343 } | |
344 | |
345 // Validate the policy before doing anything else. | |
346 PolicyNamespace ns; | |
347 em::ExternalPolicyData data; | |
348 if (!store_->ValidatePolicy(response.Pass(), &ns, &data)) { | |
349 LOG(ERROR) << "Failed to validate component policy fetched from DMServer"; | |
350 return; | |
351 } | |
352 | |
353 // Maybe the data for this hash has already been downloaded and cached. | |
354 if (data.has_secure_hash() && | |
355 data.secure_hash() == store_->GetCachedHash(ns)) { | |
356 return; | |
357 } | |
358 | |
359 // TODO(joaodasilva): implement the other two auth methods. | |
360 if (data.download_auth_method() != em::ExternalPolicyData::NONE) | |
361 return; | |
362 | |
363 // Check for an existing job for this component. | |
364 FetchJob* job = fetch_jobs_[ns]; | |
365 if (job) { | |
366 // Check if this data has already been seen. | |
367 if (job->ParamsEquals(data)) | |
368 return; | |
369 | |
370 // The existing job is obsolete, cancel it. If |job| is in the job queue | |
371 // then its WeakPtr will be invalided and skipped in the next StartNextJob. | |
372 // If |job| is the current job then it will immediately call OnJobFailed. | |
373 delete job; | |
374 fetch_jobs_.erase(ns); | |
375 } | |
376 | |
377 if (data.download_url().empty()) { | |
378 // There is no policy for this component, or the policy has been removed. | |
379 store_->Delete(ns); | |
380 } else { | |
381 // Start a new job with the new or updated data. | |
382 job = new FetchJob(this, ns, serialized_response, data); | |
383 fetch_jobs_[ns] = job; | |
384 ScheduleJob(job); | |
385 } | |
386 } | |
387 | |
388 void ComponentCloudPolicyUpdater::ScheduleJob(FetchJob* job) { | |
389 job_queue_.push(job->AsWeakPtr()); | |
390 // The job at the front of the queue is always the current job. If |job| is | |
391 // at the front then start it immediately. An invalid job is never at the | |
392 // front; as soon as it becomes invalidated it will call OnJobFailed() and | |
393 // flush the queue. | |
394 if (job == job_queue_.front().get()) | |
395 StartNextJob(); | |
396 } | |
397 | |
398 void ComponentCloudPolicyUpdater::StartNextJob() { | |
399 // Some of the jobs may have been invalidated, and have to be skipped. | |
400 while (!job_queue_.empty() && !job_queue_.front()) | |
401 job_queue_.pop(); | |
402 | |
403 // A started job will always call OnJobSucceeded or OnJobFailed. | |
404 if (!job_queue_.empty() && !shutting_down_) | |
405 job_queue_.front()->StartJob(); | |
406 } | |
407 | |
408 void ComponentCloudPolicyUpdater::OnJobSucceeded(FetchJob* job) { | |
409 DCHECK(fetch_jobs_[job->policy_namespace()] == job); | |
410 DCHECK(!job_queue_.empty() && job_queue_.front() == job); | |
411 fetch_jobs_.erase(job->policy_namespace()); | |
412 delete job; | |
413 job_queue_.pop(); | |
414 StartNextJob(); | |
415 } | |
416 | |
417 void ComponentCloudPolicyUpdater::OnJobFailed(FetchJob* job) { | |
418 DCHECK(fetch_jobs_[job->policy_namespace()] == job); | |
419 DCHECK(!job_queue_.empty() && job_queue_.front() == job); | |
420 // The job isn't deleted when it fails because a retry attempt may have been | |
421 // scheduled. It's also kept so that UpdateExternalPolicy() can see the | |
422 // current data, and avoid a new fetch if the data hasn't changed. | |
423 job_queue_.pop(); | |
424 StartNextJob(); | |
425 } | |
426 | |
427 } // namespace policy | |
OLD | NEW |