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 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 55 CloudPolicyInvalidator::~CloudPolicyInvalidator() { 56 DCHECK(state_ == SHUT_DOWN); 57 } 58 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 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 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 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 116 std::string CloudPolicyInvalidator::GetOwnerName() const { return "Cloud"; } 117 118 void CloudPolicyInvalidator::OnCoreConnected(CloudPolicyCore* core) {} 119 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 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 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 160 void CloudPolicyInvalidator::OnStoreError(CloudPolicyStore* store) {} 161 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 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 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 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 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 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 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 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 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 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 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 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 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 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