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.
4
5 #include "chrome/browser/policy/cloud/cloud_policy_invalidator.h"
6
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/invalidation_service.h"
17 #include "components/policy/core/common/cloud/cloud_policy_client.h"
18 #include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
19 #include "components/policy/core/common/cloud/enterprise_metrics.h"
20 #include "policy/policy_constants.h"
21 #include "sync/notifier/object_id_invalidation_map.h"
22
23 namespace policy {
24
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;
32
CloudPolicyInvalidator(CloudPolicyCore * core,const scoped_refptr<base::SequencedTaskRunner> & task_runner,scoped_ptr<base::Clock> clock)33 CloudPolicyInvalidator::CloudPolicyInvalidator(
34 CloudPolicyCore* core,
35 const scoped_refptr<base::SequencedTaskRunner>& task_runner,
36 scoped_ptr<base::Clock> clock)
37 : state_(UNINITIALIZED),
38 core_(core),
39 task_runner_(task_runner),
40 clock_(clock.Pass()),
41 invalidation_service_(NULL),
42 invalidations_enabled_(false),
43 invalidation_service_enabled_(false),
44 is_registered_(false),
45 invalid_(false),
46 invalidation_version_(0),
47 unknown_version_invalidation_count_(0),
48 weak_factory_(this),
49 max_fetch_delay_(kMaxFetchDelayDefault),
50 policy_hash_value_(0) {
51 DCHECK(core);
52 DCHECK(task_runner.get());
53 }
54
~CloudPolicyInvalidator()55 CloudPolicyInvalidator::~CloudPolicyInvalidator() {
56 DCHECK(state_ == SHUT_DOWN);
57 }
58
Initialize(invalidation::InvalidationService * invalidation_service)59 void CloudPolicyInvalidator::Initialize(
60 invalidation::InvalidationService* invalidation_service) {
61 DCHECK(state_ == UNINITIALIZED);
62 DCHECK(thread_checker_.CalledOnValidThread());
63 DCHECK(invalidation_service);
64 invalidation_service_ = invalidation_service;
65 state_ = STOPPED;
66 core_->AddObserver(this);
67 if (core_->refresh_scheduler())
68 OnRefreshSchedulerStarted(core_);
69 }
70
Shutdown()71 void CloudPolicyInvalidator::Shutdown() {
72 DCHECK(state_ != SHUT_DOWN);
73 DCHECK(thread_checker_.CalledOnValidThread());
74 if (state_ == STARTED) {
75 if (is_registered_)
76 invalidation_service_->UnregisterInvalidationHandler(this);
77 core_->store()->RemoveObserver(this);
78 weak_factory_.InvalidateWeakPtrs();
79 }
80 if (state_ != UNINITIALIZED)
81 core_->RemoveObserver(this);
82 state_ = SHUT_DOWN;
83 }
84
OnInvalidatorStateChange(syncer::InvalidatorState state)85 void CloudPolicyInvalidator::OnInvalidatorStateChange(
86 syncer::InvalidatorState state) {
87 DCHECK(state_ == STARTED);
88 DCHECK(thread_checker_.CalledOnValidThread());
89 invalidation_service_enabled_ = state == syncer::INVALIDATIONS_ENABLED;
90 UpdateInvalidationsEnabled();
91 }
92
OnIncomingInvalidation(const syncer::ObjectIdInvalidationMap & invalidation_map)93 void CloudPolicyInvalidator::OnIncomingInvalidation(
94 const syncer::ObjectIdInvalidationMap& invalidation_map) {
95 DCHECK(state_ == STARTED);
96 DCHECK(thread_checker_.CalledOnValidThread());
97 const syncer::SingleObjectInvalidationSet& list =
98 invalidation_map.ForObject(object_id_);
99 if (list.IsEmpty()) {
100 NOTREACHED();
101 return;
102 }
103
104 // Acknowledge all except the invalidation with the highest version.
105 syncer::SingleObjectInvalidationSet::const_reverse_iterator it =
106 list.rbegin();
107 ++it;
108 for ( ; it != list.rend(); ++it) {
109 it->Acknowledge();
110 }
111
112 // Handle the highest version invalidation.
113 HandleInvalidation(list.back());
114 }
115
GetOwnerName() const116 std::string CloudPolicyInvalidator::GetOwnerName() const { return "Cloud"; }
117
OnCoreConnected(CloudPolicyCore * core)118 void CloudPolicyInvalidator::OnCoreConnected(CloudPolicyCore* core) {}
119
OnRefreshSchedulerStarted(CloudPolicyCore * core)120 void CloudPolicyInvalidator::OnRefreshSchedulerStarted(CloudPolicyCore* core) {
121 DCHECK(state_ == STOPPED);
122 DCHECK(thread_checker_.CalledOnValidThread());
123 state_ = STARTED;
124 OnStoreLoaded(core_->store());
125 core_->store()->AddObserver(this);
126 }
127
OnCoreDisconnecting(CloudPolicyCore * core)128 void CloudPolicyInvalidator::OnCoreDisconnecting(CloudPolicyCore* core) {
129 DCHECK(state_ == STARTED || state_ == STOPPED);
130 DCHECK(thread_checker_.CalledOnValidThread());
131 if (state_ == STARTED) {
132 Unregister();
133 core_->store()->RemoveObserver(this);
134 state_ = STOPPED;
135 }
136 }
137
OnStoreLoaded(CloudPolicyStore * store)138 void CloudPolicyInvalidator::OnStoreLoaded(CloudPolicyStore* store) {
139 DCHECK(state_ == STARTED);
140 DCHECK(thread_checker_.CalledOnValidThread());
141 bool policy_changed = IsPolicyChanged(store->policy());
142
143 if (is_registered_) {
144 // Update the kMetricPolicyRefresh histogram.
145 UMA_HISTOGRAM_ENUMERATION(
146 kMetricPolicyRefresh,
147 GetPolicyRefreshMetric(policy_changed),
148 METRIC_POLICY_REFRESH_SIZE);
149
150 // If the policy was invalid and the version stored matches the latest
151 // invalidation version, acknowledge the latest invalidation.
152 if (invalid_ && store->invalidation_version() == invalidation_version_)
153 AcknowledgeInvalidation();
154 }
155
156 UpdateRegistration(store->policy());
157 UpdateMaxFetchDelay(store->policy_map());
158 }
159
OnStoreError(CloudPolicyStore * store)160 void CloudPolicyInvalidator::OnStoreError(CloudPolicyStore* store) {}
161
HandleInvalidation(const syncer::Invalidation & invalidation)162 void CloudPolicyInvalidator::HandleInvalidation(
163 const syncer::Invalidation& invalidation) {
164 // Ignore old invalidations.
165 if (invalid_ &&
166 !invalidation.is_unknown_version() &&
167 invalidation.version() <= invalidation_version_) {
168 return;
169 }
170
171 // If there is still a pending invalidation, acknowledge it, since we only
172 // care about the latest invalidation.
173 if (invalid_)
174 AcknowledgeInvalidation();
175
176 // Get the version and payload from the invalidation.
177 // When an invalidation with unknown version is received, use negative
178 // numbers based on the number of such invalidations received. This
179 // ensures that the version numbers do not collide with "real" versions
180 // (which are positive) or previous invalidations with unknown version.
181 int64 version;
182 std::string payload;
183 if (invalidation.is_unknown_version()) {
184 version = -(++unknown_version_invalidation_count_);
185 } else {
186 version = invalidation.version();
187 payload = invalidation.payload();
188 }
189
190 // Ignore the invalidation if it is expired.
191 bool is_expired = IsInvalidationExpired(version);
192 UMA_HISTOGRAM_ENUMERATION(
193 kMetricPolicyInvalidations,
194 GetInvalidationMetric(payload.empty(), is_expired),
195 POLICY_INVALIDATION_TYPE_SIZE);
196 if (is_expired) {
197 invalidation.Acknowledge();
198 return;
199 }
200
201 // Update invalidation state.
202 invalid_ = true;
203 invalidation_.reset(new syncer::Invalidation(invalidation));
204 invalidation_version_ = version;
205
206 // In order to prevent the cloud policy server from becoming overwhelmed when
207 // a policy with many users is modified, delay for a random period of time
208 // before fetching the policy. Delay for at least 20ms so that if multiple
209 // invalidations are received in quick succession, only one fetch will be
210 // performed.
211 base::TimeDelta delay = base::TimeDelta::FromMilliseconds(
212 base::RandInt(20, max_fetch_delay_));
213
214 // If there is a payload, the policy can be refreshed at any time, so set
215 // the version and payload on the client immediately. Otherwise, the refresh
216 // must only run after at least kMissingPayloadDelay minutes.
217 if (!payload.empty())
218 core_->client()->SetInvalidationInfo(version, payload);
219 else
220 delay += base::TimeDelta::FromMinutes(kMissingPayloadDelay);
221
222 // Schedule the policy to be refreshed.
223 task_runner_->PostDelayedTask(
224 FROM_HERE,
225 base::Bind(
226 &CloudPolicyInvalidator::RefreshPolicy,
227 weak_factory_.GetWeakPtr(),
228 payload.empty() /* is_missing_payload */),
229 delay);
230 }
231
UpdateRegistration(const enterprise_management::PolicyData * policy)232 void CloudPolicyInvalidator::UpdateRegistration(
233 const enterprise_management::PolicyData* policy) {
234 // Create the ObjectId based on the policy data.
235 // If the policy does not specify an the ObjectId, then unregister.
236 if (!policy ||
237 !policy->has_invalidation_source() ||
238 !policy->has_invalidation_name()) {
239 Unregister();
240 return;
241 }
242 invalidation::ObjectId object_id(
243 policy->invalidation_source(),
244 policy->invalidation_name());
245
246 // If the policy object id in the policy data is different from the currently
247 // registered object id, update the object registration.
248 if (!is_registered_ || !(object_id == object_id_))
249 Register(object_id);
250 }
251
Register(const invalidation::ObjectId & object_id)252 void CloudPolicyInvalidator::Register(const invalidation::ObjectId& object_id) {
253 // Register this handler with the invalidation service if needed.
254 if (!is_registered_) {
255 OnInvalidatorStateChange(invalidation_service_->GetInvalidatorState());
256 invalidation_service_->RegisterInvalidationHandler(this);
257 }
258
259 // Update internal state.
260 if (invalid_)
261 AcknowledgeInvalidation();
262 is_registered_ = true;
263 object_id_ = object_id;
264 UpdateInvalidationsEnabled();
265
266 // Update registration with the invalidation service.
267 syncer::ObjectIdSet ids;
268 ids.insert(object_id);
269 invalidation_service_->UpdateRegisteredInvalidationIds(this, ids);
270 }
271
Unregister()272 void CloudPolicyInvalidator::Unregister() {
273 if (is_registered_) {
274 if (invalid_)
275 AcknowledgeInvalidation();
276 invalidation_service_->UpdateRegisteredInvalidationIds(
277 this,
278 syncer::ObjectIdSet());
279 invalidation_service_->UnregisterInvalidationHandler(this);
280 is_registered_ = false;
281 UpdateInvalidationsEnabled();
282 }
283 }
284
UpdateMaxFetchDelay(const PolicyMap & policy_map)285 void CloudPolicyInvalidator::UpdateMaxFetchDelay(const PolicyMap& policy_map) {
286 int delay;
287
288 // Try reading the delay from the policy.
289 const base::Value* delay_policy_value =
290 policy_map.GetValue(key::kMaxInvalidationFetchDelay);
291 if (delay_policy_value && delay_policy_value->GetAsInteger(&delay)) {
292 set_max_fetch_delay(delay);
293 return;
294 }
295
296 set_max_fetch_delay(kMaxFetchDelayDefault);
297 }
298
set_max_fetch_delay(int delay)299 void CloudPolicyInvalidator::set_max_fetch_delay(int delay) {
300 if (delay < kMaxFetchDelayMin)
301 max_fetch_delay_ = kMaxFetchDelayMin;
302 else if (delay > kMaxFetchDelayMax)
303 max_fetch_delay_ = kMaxFetchDelayMax;
304 else
305 max_fetch_delay_ = delay;
306 }
307
UpdateInvalidationsEnabled()308 void CloudPolicyInvalidator::UpdateInvalidationsEnabled() {
309 bool invalidations_enabled = invalidation_service_enabled_ && is_registered_;
310 if (invalidations_enabled_ != invalidations_enabled) {
311 invalidations_enabled_ = invalidations_enabled;
312 if (invalidations_enabled)
313 invalidations_enabled_time_ = clock_->Now();
314 core_->refresh_scheduler()->SetInvalidationServiceAvailability(
315 invalidations_enabled);
316 }
317 }
318
RefreshPolicy(bool is_missing_payload)319 void CloudPolicyInvalidator::RefreshPolicy(bool is_missing_payload) {
320 DCHECK(thread_checker_.CalledOnValidThread());
321 // In the missing payload case, the invalidation version has not been set on
322 // the client yet, so set it now that the required time has elapsed.
323 if (is_missing_payload)
324 core_->client()->SetInvalidationInfo(invalidation_version_, std::string());
325 core_->refresh_scheduler()->RefreshSoon();
326 }
327
AcknowledgeInvalidation()328 void CloudPolicyInvalidator::AcknowledgeInvalidation() {
329 DCHECK(invalid_);
330 invalid_ = false;
331 core_->client()->SetInvalidationInfo(0, std::string());
332 invalidation_->Acknowledge();
333 invalidation_.reset();
334 // Cancel any scheduled policy refreshes.
335 weak_factory_.InvalidateWeakPtrs();
336 }
337
IsPolicyChanged(const enterprise_management::PolicyData * policy)338 bool CloudPolicyInvalidator::IsPolicyChanged(
339 const enterprise_management::PolicyData* policy) {
340 // Determine if the policy changed by comparing its hash value to the
341 // previous policy's hash value.
342 uint32 new_hash_value = 0;
343 if (policy && policy->has_policy_value())
344 new_hash_value = base::Hash(policy->policy_value());
345 bool changed = new_hash_value != policy_hash_value_;
346 policy_hash_value_ = new_hash_value;
347 return changed;
348 }
349
IsInvalidationExpired(int64 version)350 bool CloudPolicyInvalidator::IsInvalidationExpired(int64 version) {
351 base::Time last_fetch_time = base::Time::UnixEpoch() +
352 base::TimeDelta::FromMilliseconds(core_->store()->policy()->timestamp());
353
354 // If the version is unknown, consider the invalidation invalid if the
355 // policy was fetched very recently.
356 if (version < 0) {
357 base::TimeDelta elapsed = clock_->Now() - last_fetch_time;
358 return elapsed.InSeconds() < kUnknownVersionIgnorePeriod;
359 }
360
361 // The invalidation version is the timestamp in microseconds. If the
362 // invalidation occurred before the last policy fetch, then the invalidation
363 // is expired. Time is added to the invalidation to err on the side of not
364 // expired.
365 base::Time invalidation_time = base::Time::UnixEpoch() +
366 base::TimeDelta::FromMicroseconds(version) +
367 base::TimeDelta::FromSeconds(kMaxInvalidationTimeDelta);
368 return invalidation_time < last_fetch_time;
369 }
370
GetPolicyRefreshMetric(bool policy_changed)371 int CloudPolicyInvalidator::GetPolicyRefreshMetric(bool policy_changed) {
372 if (policy_changed) {
373 if (invalid_)
374 return METRIC_POLICY_REFRESH_INVALIDATED_CHANGED;
375 if (GetInvalidationsEnabled())
376 return METRIC_POLICY_REFRESH_CHANGED;
377 return METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS;
378 }
379 if (invalid_)
380 return METRIC_POLICY_REFRESH_INVALIDATED_UNCHANGED;
381 return METRIC_POLICY_REFRESH_UNCHANGED;
382 }
383
GetInvalidationMetric(bool is_missing_payload,bool is_expired)384 int CloudPolicyInvalidator::GetInvalidationMetric(bool is_missing_payload,
385 bool is_expired) {
386 if (is_expired) {
387 if (is_missing_payload)
388 return POLICY_INVALIDATION_TYPE_NO_PAYLOAD_EXPIRED;
389 return POLICY_INVALIDATION_TYPE_EXPIRED;
390 }
391 if (is_missing_payload)
392 return POLICY_INVALIDATION_TYPE_NO_PAYLOAD;
393 return POLICY_INVALIDATION_TYPE_NORMAL;
394 }
395
GetInvalidationsEnabled()396 bool CloudPolicyInvalidator::GetInvalidationsEnabled() {
397 if (!invalidations_enabled_)
398 return false;
399 // If invalidations have been enabled for less than the grace period, then
400 // consider invalidations to be disabled for metrics reporting.
401 base::TimeDelta elapsed = clock_->Now() - invalidations_enabled_time_;
402 return elapsed.InSeconds() >= kInvalidationGracePeriod;
403 }
404
405 } // namespace policy
406