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