Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / policy / cloud / cloud_policy_invalidator.cc
blob0947a248b96190a158cc3f048936f26cc6f1f181
1 // Copyright 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.
5 #include "chrome/browser/policy/cloud/cloud_policy_invalidator.h"
7 #include "base/bind.h"
8 #include "base/hash.h"
9 #include "base/location.h"
10 #include "base/metrics/histogram.h"
11 #include "base/rand_util.h"
12 #include "base/sequenced_task_runner.h"
13 #include "base/time/clock.h"
14 #include "base/time/time.h"
15 #include "base/values.h"
16 #include "components/invalidation/public/invalidation_service.h"
17 #include "components/invalidation/public/object_id_invalidation_map.h"
18 #include "components/policy/core/common/cloud/cloud_policy_client.h"
19 #include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
20 #include "components/policy/core/common/cloud/enterprise_metrics.h"
21 #include "policy/policy_constants.h"
23 namespace policy {
25 const int CloudPolicyInvalidator::kMissingPayloadDelay = 5;
26 const int CloudPolicyInvalidator::kMaxFetchDelayDefault = 10000;
27 const int CloudPolicyInvalidator::kMaxFetchDelayMin = 1000;
28 const int CloudPolicyInvalidator::kMaxFetchDelayMax = 300000;
29 const int CloudPolicyInvalidator::kInvalidationGracePeriod = 10;
30 const int CloudPolicyInvalidator::kUnknownVersionIgnorePeriod = 30;
31 const int CloudPolicyInvalidator::kMaxInvalidationTimeDelta = 300;
33 CloudPolicyInvalidator::CloudPolicyInvalidator(
34 enterprise_management::DeviceRegisterRequest::Type type,
35 CloudPolicyCore* core,
36 const scoped_refptr<base::SequencedTaskRunner>& task_runner,
37 scoped_ptr<base::Clock> clock,
38 int64 highest_handled_invalidation_version)
39 : state_(UNINITIALIZED),
40 type_(type),
41 core_(core),
42 task_runner_(task_runner),
43 clock_(clock.Pass()),
44 invalidation_service_(NULL),
45 invalidations_enabled_(false),
46 invalidation_service_enabled_(false),
47 is_registered_(false),
48 invalid_(false),
49 invalidation_version_(0),
50 unknown_version_invalidation_count_(0),
51 highest_handled_invalidation_version_(
52 highest_handled_invalidation_version),
53 max_fetch_delay_(kMaxFetchDelayDefault),
54 policy_hash_value_(0),
55 weak_factory_(this) {
56 DCHECK(core);
57 DCHECK(task_runner.get());
58 // |highest_handled_invalidation_version_| indicates the highest actual
59 // invalidation version handled. Since actual invalidations can have only
60 // positive versions, this member may be zero (no versioned invalidation
61 // handled yet) or positive. Negative values are not allowed:
63 // Negative version numbers are used internally by CloudPolicyInvalidator to
64 // keep track of unversioned invalidations. When such an invalidation is
65 // handled, |highest_handled_invalidation_version_| remains unchanged and does
66 // not become negative.
67 DCHECK_LE(0, highest_handled_invalidation_version_);
70 CloudPolicyInvalidator::~CloudPolicyInvalidator() {
71 DCHECK(state_ == SHUT_DOWN);
74 void CloudPolicyInvalidator::Initialize(
75 invalidation::InvalidationService* invalidation_service) {
76 DCHECK(state_ == UNINITIALIZED);
77 DCHECK(thread_checker_.CalledOnValidThread());
78 DCHECK(invalidation_service);
79 invalidation_service_ = invalidation_service;
80 state_ = STOPPED;
81 core_->AddObserver(this);
82 if (core_->refresh_scheduler())
83 OnRefreshSchedulerStarted(core_);
86 void CloudPolicyInvalidator::Shutdown() {
87 DCHECK(state_ != SHUT_DOWN);
88 DCHECK(thread_checker_.CalledOnValidThread());
89 if (state_ == STARTED) {
90 if (is_registered_)
91 invalidation_service_->UnregisterInvalidationHandler(this);
92 core_->store()->RemoveObserver(this);
93 weak_factory_.InvalidateWeakPtrs();
95 if (state_ != UNINITIALIZED)
96 core_->RemoveObserver(this);
97 state_ = SHUT_DOWN;
100 void CloudPolicyInvalidator::OnInvalidatorStateChange(
101 syncer::InvalidatorState state) {
102 DCHECK(state_ == STARTED);
103 DCHECK(thread_checker_.CalledOnValidThread());
104 invalidation_service_enabled_ = state == syncer::INVALIDATIONS_ENABLED;
105 UpdateInvalidationsEnabled();
108 void CloudPolicyInvalidator::OnIncomingInvalidation(
109 const syncer::ObjectIdInvalidationMap& invalidation_map) {
110 DCHECK(state_ == STARTED);
111 DCHECK(thread_checker_.CalledOnValidThread());
112 const syncer::SingleObjectInvalidationSet& list =
113 invalidation_map.ForObject(object_id_);
114 if (list.IsEmpty()) {
115 NOTREACHED();
116 return;
119 // Acknowledge all except the invalidation with the highest version.
120 syncer::SingleObjectInvalidationSet::const_reverse_iterator it =
121 list.rbegin();
122 ++it;
123 for ( ; it != list.rend(); ++it) {
124 it->Acknowledge();
127 // Handle the highest version invalidation.
128 HandleInvalidation(list.back());
131 std::string CloudPolicyInvalidator::GetOwnerName() const { return "Cloud"; }
133 void CloudPolicyInvalidator::OnCoreConnected(CloudPolicyCore* core) {}
135 void CloudPolicyInvalidator::OnRefreshSchedulerStarted(CloudPolicyCore* core) {
136 DCHECK(state_ == STOPPED);
137 DCHECK(thread_checker_.CalledOnValidThread());
138 state_ = STARTED;
139 OnStoreLoaded(core_->store());
140 core_->store()->AddObserver(this);
143 void CloudPolicyInvalidator::OnCoreDisconnecting(CloudPolicyCore* core) {
144 DCHECK(state_ == STARTED || state_ == STOPPED);
145 DCHECK(thread_checker_.CalledOnValidThread());
146 if (state_ == STARTED) {
147 Unregister();
148 core_->store()->RemoveObserver(this);
149 state_ = STOPPED;
153 void CloudPolicyInvalidator::OnStoreLoaded(CloudPolicyStore* store) {
154 DCHECK(state_ == STARTED);
155 DCHECK(thread_checker_.CalledOnValidThread());
156 bool policy_changed = IsPolicyChanged(store->policy());
158 if (is_registered_) {
159 // Update the kMetricDevicePolicyRefresh/kMetricUserPolicyRefresh histogram.
160 if (type_ == enterprise_management::DeviceRegisterRequest::DEVICE) {
161 UMA_HISTOGRAM_ENUMERATION(kMetricDevicePolicyRefresh,
162 GetPolicyRefreshMetric(policy_changed),
163 METRIC_POLICY_REFRESH_SIZE);
164 } else {
165 UMA_HISTOGRAM_ENUMERATION(kMetricUserPolicyRefresh,
166 GetPolicyRefreshMetric(policy_changed),
167 METRIC_POLICY_REFRESH_SIZE);
170 const int64 store_invalidation_version = store->invalidation_version();
172 // If the policy was invalid and the version stored matches the latest
173 // invalidation version, acknowledge the latest invalidation.
174 if (invalid_ && store_invalidation_version == invalidation_version_)
175 AcknowledgeInvalidation();
177 // Update the highest invalidation version that was handled already.
178 if (store_invalidation_version > highest_handled_invalidation_version_)
179 highest_handled_invalidation_version_ = store_invalidation_version;
182 UpdateRegistration(store->policy());
183 UpdateMaxFetchDelay(store->policy_map());
186 void CloudPolicyInvalidator::OnStoreError(CloudPolicyStore* store) {}
188 void CloudPolicyInvalidator::HandleInvalidation(
189 const syncer::Invalidation& invalidation) {
190 // Ignore old invalidations.
191 if (invalid_ &&
192 !invalidation.is_unknown_version() &&
193 invalidation.version() <= invalidation_version_) {
194 return;
197 if (!invalidation.is_unknown_version() &&
198 invalidation.version() <= highest_handled_invalidation_version_) {
199 // If this invalidation version was handled already, acknowledge the
200 // invalidation but ignore it otherwise.
201 invalidation.Acknowledge();
202 return;
205 // If there is still a pending invalidation, acknowledge it, since we only
206 // care about the latest invalidation.
207 if (invalid_)
208 AcknowledgeInvalidation();
210 // Get the version and payload from the invalidation.
211 // When an invalidation with unknown version is received, use negative
212 // numbers based on the number of such invalidations received. This
213 // ensures that the version numbers do not collide with "real" versions
214 // (which are positive) or previous invalidations with unknown version.
215 int64 version;
216 std::string payload;
217 if (invalidation.is_unknown_version()) {
218 version = -(++unknown_version_invalidation_count_);
219 } else {
220 version = invalidation.version();
221 payload = invalidation.payload();
224 // Ignore the invalidation if it is expired.
225 bool is_expired = IsInvalidationExpired(version);
227 if (type_ == enterprise_management::DeviceRegisterRequest::DEVICE) {
228 UMA_HISTOGRAM_ENUMERATION(
229 kMetricDevicePolicyInvalidations,
230 GetInvalidationMetric(payload.empty(), is_expired),
231 POLICY_INVALIDATION_TYPE_SIZE);
232 } else {
233 UMA_HISTOGRAM_ENUMERATION(
234 kMetricUserPolicyInvalidations,
235 GetInvalidationMetric(payload.empty(), is_expired),
236 POLICY_INVALIDATION_TYPE_SIZE);
238 if (is_expired) {
239 invalidation.Acknowledge();
240 return;
243 // Update invalidation state.
244 invalid_ = true;
245 invalidation_.reset(new syncer::Invalidation(invalidation));
246 invalidation_version_ = version;
248 // In order to prevent the cloud policy server from becoming overwhelmed when
249 // a policy with many users is modified, delay for a random period of time
250 // before fetching the policy. Delay for at least 20ms so that if multiple
251 // invalidations are received in quick succession, only one fetch will be
252 // performed.
253 base::TimeDelta delay = base::TimeDelta::FromMilliseconds(
254 base::RandInt(20, max_fetch_delay_));
256 // If there is a payload, the policy can be refreshed at any time, so set
257 // the version and payload on the client immediately. Otherwise, the refresh
258 // must only run after at least kMissingPayloadDelay minutes.
259 if (!payload.empty())
260 core_->client()->SetInvalidationInfo(version, payload);
261 else
262 delay += base::TimeDelta::FromMinutes(kMissingPayloadDelay);
264 // Schedule the policy to be refreshed.
265 task_runner_->PostDelayedTask(
266 FROM_HERE,
267 base::Bind(
268 &CloudPolicyInvalidator::RefreshPolicy,
269 weak_factory_.GetWeakPtr(),
270 payload.empty() /* is_missing_payload */),
271 delay);
274 void CloudPolicyInvalidator::UpdateRegistration(
275 const enterprise_management::PolicyData* policy) {
276 // Create the ObjectId based on the policy data.
277 // If the policy does not specify an the ObjectId, then unregister.
278 if (!policy ||
279 !policy->has_invalidation_source() ||
280 !policy->has_invalidation_name()) {
281 Unregister();
282 return;
284 invalidation::ObjectId object_id(
285 policy->invalidation_source(),
286 policy->invalidation_name());
288 // If the policy object id in the policy data is different from the currently
289 // registered object id, update the object registration.
290 if (!is_registered_ || !(object_id == object_id_))
291 Register(object_id);
294 void CloudPolicyInvalidator::Register(const invalidation::ObjectId& object_id) {
295 // Register this handler with the invalidation service if needed.
296 if (!is_registered_) {
297 OnInvalidatorStateChange(invalidation_service_->GetInvalidatorState());
298 invalidation_service_->RegisterInvalidationHandler(this);
301 // Update internal state.
302 if (invalid_)
303 AcknowledgeInvalidation();
304 is_registered_ = true;
305 object_id_ = object_id;
306 UpdateInvalidationsEnabled();
308 // Update registration with the invalidation service.
309 syncer::ObjectIdSet ids;
310 ids.insert(object_id);
311 CHECK(invalidation_service_->UpdateRegisteredInvalidationIds(this, ids));
314 void CloudPolicyInvalidator::Unregister() {
315 if (is_registered_) {
316 if (invalid_)
317 AcknowledgeInvalidation();
318 CHECK(invalidation_service_->UpdateRegisteredInvalidationIds(
319 this, syncer::ObjectIdSet()));
320 invalidation_service_->UnregisterInvalidationHandler(this);
321 is_registered_ = false;
322 UpdateInvalidationsEnabled();
326 void CloudPolicyInvalidator::UpdateMaxFetchDelay(const PolicyMap& policy_map) {
327 int delay;
329 // Try reading the delay from the policy.
330 const base::Value* delay_policy_value =
331 policy_map.GetValue(key::kMaxInvalidationFetchDelay);
332 if (delay_policy_value && delay_policy_value->GetAsInteger(&delay)) {
333 set_max_fetch_delay(delay);
334 return;
337 set_max_fetch_delay(kMaxFetchDelayDefault);
340 void CloudPolicyInvalidator::set_max_fetch_delay(int delay) {
341 if (delay < kMaxFetchDelayMin)
342 max_fetch_delay_ = kMaxFetchDelayMin;
343 else if (delay > kMaxFetchDelayMax)
344 max_fetch_delay_ = kMaxFetchDelayMax;
345 else
346 max_fetch_delay_ = delay;
349 void CloudPolicyInvalidator::UpdateInvalidationsEnabled() {
350 bool invalidations_enabled = invalidation_service_enabled_ && is_registered_;
351 if (invalidations_enabled_ != invalidations_enabled) {
352 invalidations_enabled_ = invalidations_enabled;
353 if (invalidations_enabled)
354 invalidations_enabled_time_ = clock_->Now();
355 core_->refresh_scheduler()->SetInvalidationServiceAvailability(
356 invalidations_enabled);
360 void CloudPolicyInvalidator::RefreshPolicy(bool is_missing_payload) {
361 DCHECK(thread_checker_.CalledOnValidThread());
362 // In the missing payload case, the invalidation version has not been set on
363 // the client yet, so set it now that the required time has elapsed.
364 if (is_missing_payload)
365 core_->client()->SetInvalidationInfo(invalidation_version_, std::string());
366 core_->refresh_scheduler()->RefreshSoon();
369 void CloudPolicyInvalidator::AcknowledgeInvalidation() {
370 DCHECK(invalid_);
371 invalid_ = false;
372 core_->client()->SetInvalidationInfo(0, std::string());
373 invalidation_->Acknowledge();
374 invalidation_.reset();
375 // Cancel any scheduled policy refreshes.
376 weak_factory_.InvalidateWeakPtrs();
379 bool CloudPolicyInvalidator::IsPolicyChanged(
380 const enterprise_management::PolicyData* policy) {
381 // Determine if the policy changed by comparing its hash value to the
382 // previous policy's hash value.
383 uint32 new_hash_value = 0;
384 if (policy && policy->has_policy_value())
385 new_hash_value = base::Hash(policy->policy_value());
386 bool changed = new_hash_value != policy_hash_value_;
387 policy_hash_value_ = new_hash_value;
388 return changed;
391 bool CloudPolicyInvalidator::IsInvalidationExpired(int64 version) {
392 base::Time last_fetch_time = base::Time::UnixEpoch() +
393 base::TimeDelta::FromMilliseconds(core_->store()->policy()->timestamp());
395 // If the version is unknown, consider the invalidation invalid if the
396 // policy was fetched very recently.
397 if (version < 0) {
398 base::TimeDelta elapsed = clock_->Now() - last_fetch_time;
399 return elapsed.InSeconds() < kUnknownVersionIgnorePeriod;
402 // The invalidation version is the timestamp in microseconds. If the
403 // invalidation occurred before the last policy fetch, then the invalidation
404 // is expired. Time is added to the invalidation to err on the side of not
405 // expired.
406 base::Time invalidation_time = base::Time::UnixEpoch() +
407 base::TimeDelta::FromMicroseconds(version) +
408 base::TimeDelta::FromSeconds(kMaxInvalidationTimeDelta);
409 return invalidation_time < last_fetch_time;
412 int CloudPolicyInvalidator::GetPolicyRefreshMetric(bool policy_changed) {
413 if (policy_changed) {
414 if (invalid_)
415 return METRIC_POLICY_REFRESH_INVALIDATED_CHANGED;
416 if (GetInvalidationsEnabled())
417 return METRIC_POLICY_REFRESH_CHANGED;
418 return METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS;
420 if (invalid_)
421 return METRIC_POLICY_REFRESH_INVALIDATED_UNCHANGED;
422 return METRIC_POLICY_REFRESH_UNCHANGED;
425 int CloudPolicyInvalidator::GetInvalidationMetric(bool is_missing_payload,
426 bool is_expired) {
427 if (is_expired) {
428 if (is_missing_payload)
429 return POLICY_INVALIDATION_TYPE_NO_PAYLOAD_EXPIRED;
430 return POLICY_INVALIDATION_TYPE_EXPIRED;
432 if (is_missing_payload)
433 return POLICY_INVALIDATION_TYPE_NO_PAYLOAD;
434 return POLICY_INVALIDATION_TYPE_NORMAL;
437 bool CloudPolicyInvalidator::GetInvalidationsEnabled() {
438 if (!invalidations_enabled_)
439 return false;
440 // If invalidations have been enabled for less than the grace period, then
441 // consider invalidations to be disabled for metrics reporting.
442 base::TimeDelta elapsed = clock_->Now() - invalidations_enabled_time_;
443 return elapsed.InSeconds() >= kInvalidationGracePeriod;
446 } // namespace policy