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"
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"
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
),
42 task_runner_(task_runner
),
44 invalidation_service_(NULL
),
45 invalidations_enabled_(false),
46 invalidation_service_enabled_(false),
47 is_registered_(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),
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
;
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
) {
91 invalidation_service_
->UnregisterInvalidationHandler(this);
92 core_
->store()->RemoveObserver(this);
93 weak_factory_
.InvalidateWeakPtrs();
95 if (state_
!= UNINITIALIZED
)
96 core_
->RemoveObserver(this);
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()) {
119 // Acknowledge all except the invalidation with the highest version.
120 syncer::SingleObjectInvalidationSet::const_reverse_iterator it
=
123 for ( ; it
!= list
.rend(); ++it
) {
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());
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
) {
148 core_
->store()->RemoveObserver(this);
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
);
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.
192 !invalidation
.is_unknown_version() &&
193 invalidation
.version() <= invalidation_version_
) {
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();
205 // If there is still a pending invalidation, acknowledge it, since we only
206 // care about the latest invalidation.
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.
217 if (invalidation
.is_unknown_version()) {
218 version
= -(++unknown_version_invalidation_count_
);
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
);
233 UMA_HISTOGRAM_ENUMERATION(
234 kMetricUserPolicyInvalidations
,
235 GetInvalidationMetric(payload
.empty(), is_expired
),
236 POLICY_INVALIDATION_TYPE_SIZE
);
239 invalidation
.Acknowledge();
243 // Update invalidation state.
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
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
);
262 delay
+= base::TimeDelta::FromMinutes(kMissingPayloadDelay
);
264 // Schedule the policy to be refreshed.
265 task_runner_
->PostDelayedTask(
268 &CloudPolicyInvalidator::RefreshPolicy
,
269 weak_factory_
.GetWeakPtr(),
270 payload
.empty() /* is_missing_payload */),
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.
279 !policy
->has_invalidation_source() ||
280 !policy
->has_invalidation_name()) {
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_
))
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.
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_
) {
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
) {
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
);
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
;
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() {
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
;
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.
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
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
) {
415 return METRIC_POLICY_REFRESH_INVALIDATED_CHANGED
;
416 if (GetInvalidationsEnabled())
417 return METRIC_POLICY_REFRESH_CHANGED
;
418 return METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS
;
421 return METRIC_POLICY_REFRESH_INVALIDATED_UNCHANGED
;
422 return METRIC_POLICY_REFRESH_UNCHANGED
;
425 int CloudPolicyInvalidator::GetInvalidationMetric(bool is_missing_payload
,
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_
)
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