| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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_policy_controller.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <string> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/guid.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/metrics/histogram.h" | |
| 14 #include "base/rand_util.h" | |
| 15 #include "chrome/browser/policy/browser_policy_connector.h" | |
| 16 #include "chrome/browser/policy/cloud_policy_cache_base.h" | |
| 17 #include "chrome/browser/policy/cloud_policy_constants.h" | |
| 18 #include "chrome/browser/policy/cloud_policy_subsystem.h" | |
| 19 #include "chrome/browser/policy/delayed_work_scheduler.h" | |
| 20 #include "chrome/browser/policy/device_management_service.h" | |
| 21 #include "chrome/browser/policy/device_token_fetcher.h" | |
| 22 #include "chrome/browser/policy/enterprise_metrics.h" | |
| 23 #include "chrome/browser/policy/policy_notifier.h" | |
| 24 | |
| 25 namespace policy { | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 // The maximum ratio in percent of the policy refresh rate we use for adjusting | |
| 30 // the policy refresh time instant. The rationale is to avoid load spikes from | |
| 31 // many devices that were set up in sync for some reason. | |
| 32 const int kPolicyRefreshDeviationFactorPercent = 10; | |
| 33 // Maximum deviation we are willing to accept. | |
| 34 const int64 kPolicyRefreshDeviationMaxInMilliseconds = 30 * 60 * 1000; | |
| 35 | |
| 36 // These are the base values for delays before retrying after an error. They | |
| 37 // will be doubled each time they are used. | |
| 38 const int64 kPolicyRefreshErrorDelayInMilliseconds = | |
| 39 5 * 60 * 1000; // 5 minutes. | |
| 40 | |
| 41 // Default value for the policy refresh rate. | |
| 42 const int kPolicyRefreshRateInMilliseconds = 3 * 60 * 60 * 1000; // 3 hours. | |
| 43 | |
| 44 // Records the UMA metric corresponding to |status|, if it represents an error. | |
| 45 // Also records that a fetch response was received. | |
| 46 void SampleErrorStatus(DeviceManagementStatus status) { | |
| 47 UMA_HISTOGRAM_ENUMERATION(kMetricPolicy, | |
| 48 kMetricPolicyFetchResponseReceived, | |
| 49 kMetricPolicySize); | |
| 50 int sample = -1; | |
| 51 switch (status) { | |
| 52 case DM_STATUS_SUCCESS: | |
| 53 return; | |
| 54 case DM_STATUS_SERVICE_POLICY_NOT_FOUND: | |
| 55 sample = kMetricPolicyFetchNotFound; | |
| 56 break; | |
| 57 case DM_STATUS_SERVICE_DEVICE_NOT_FOUND: | |
| 58 sample = kMetricPolicyFetchInvalidToken; | |
| 59 break; | |
| 60 case DM_STATUS_RESPONSE_DECODING_ERROR: | |
| 61 sample = kMetricPolicyFetchBadResponse; | |
| 62 break; | |
| 63 case DM_STATUS_REQUEST_FAILED: | |
| 64 case DM_STATUS_REQUEST_INVALID: | |
| 65 case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID: | |
| 66 sample = kMetricPolicyFetchRequestFailed; | |
| 67 break; | |
| 68 case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED: | |
| 69 case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT: | |
| 70 case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER: | |
| 71 case DM_STATUS_TEMPORARY_UNAVAILABLE: | |
| 72 case DM_STATUS_SERVICE_ACTIVATION_PENDING: | |
| 73 case DM_STATUS_HTTP_STATUS_ERROR: | |
| 74 case DM_STATUS_SERVICE_MISSING_LICENSES: | |
| 75 sample = kMetricPolicyFetchServerFailed; | |
| 76 break; | |
| 77 } | |
| 78 if (sample != -1) | |
| 79 UMA_HISTOGRAM_ENUMERATION(kMetricPolicy, sample, kMetricPolicySize); | |
| 80 else | |
| 81 NOTREACHED(); | |
| 82 } | |
| 83 | |
| 84 } // namespace | |
| 85 | |
| 86 namespace em = enterprise_management; | |
| 87 | |
| 88 CloudPolicyController::CloudPolicyController( | |
| 89 DeviceManagementService* service, | |
| 90 CloudPolicyCacheBase* cache, | |
| 91 DeviceTokenFetcher* token_fetcher, | |
| 92 CloudPolicyDataStore* data_store, | |
| 93 PolicyNotifier* notifier) { | |
| 94 Initialize(service, | |
| 95 cache, | |
| 96 token_fetcher, | |
| 97 data_store, | |
| 98 notifier, | |
| 99 new DelayedWorkScheduler); | |
| 100 } | |
| 101 | |
| 102 CloudPolicyController::~CloudPolicyController() { | |
| 103 data_store_->RemoveObserver(this); | |
| 104 scheduler_->CancelDelayedWork(); | |
| 105 } | |
| 106 | |
| 107 void CloudPolicyController::SetRefreshRate(int64 refresh_rate_milliseconds) { | |
| 108 policy_refresh_rate_ms_ = refresh_rate_milliseconds; | |
| 109 | |
| 110 // Reschedule the refresh task if necessary. | |
| 111 if (state_ == STATE_POLICY_VALID) { | |
| 112 scheduler_->CancelDelayedWork(); | |
| 113 base::Time now(base::Time::NowFromSystemTime()); | |
| 114 ScheduleDelayedWorkTask( | |
| 115 (GetLastRefreshTime(now) + GetRefreshDelay()) - now); | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 void CloudPolicyController::Retry() { | |
| 120 scheduler_->CancelDelayedWork(); | |
| 121 DoWork(); | |
| 122 } | |
| 123 | |
| 124 void CloudPolicyController::Reset() { | |
| 125 SetState(STATE_TOKEN_UNAVAILABLE); | |
| 126 } | |
| 127 | |
| 128 void CloudPolicyController::RefreshPolicies(bool wait_for_auth_token) { | |
| 129 // This call must eventually trigger a notification to the cache. | |
| 130 if (data_store_->device_token().empty()) { | |
| 131 // The DMToken has to be fetched. | |
| 132 if (ReadyToFetchToken()) { | |
| 133 SetState(STATE_TOKEN_UNAVAILABLE); | |
| 134 } else if (!wait_for_auth_token) { | |
| 135 // The controller doesn't have enough material to start a token fetch, | |
| 136 // but observers of the cache are waiting for the refresh. | |
| 137 SetState(STATE_TOKEN_UNMANAGED); | |
| 138 } | |
| 139 } else { | |
| 140 // The token is valid, so the next step is to fetch policy. | |
| 141 SetState(STATE_TOKEN_VALID); | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 void CloudPolicyController::OnPolicyFetchCompleted( | |
| 146 DeviceManagementStatus status, | |
| 147 const em::DeviceManagementResponse& response) { | |
| 148 if (status == DM_STATUS_SUCCESS && !response.has_policy_response()) { | |
| 149 // Handled below. | |
| 150 status = DM_STATUS_RESPONSE_DECODING_ERROR; | |
| 151 } | |
| 152 | |
| 153 SampleErrorStatus(status); | |
| 154 | |
| 155 switch (status) { | |
| 156 case DM_STATUS_SUCCESS: { | |
| 157 const em::DevicePolicyResponse& policy_response( | |
| 158 response.policy_response()); | |
| 159 if (policy_response.response_size() > 0) { | |
| 160 if (policy_response.response_size() > 1) { | |
| 161 LOG(WARNING) << "More than one policy in the response of the device " | |
| 162 << "management server, discarding."; | |
| 163 } | |
| 164 const em::PolicyFetchResponse& fetch_response( | |
| 165 policy_response.response(0)); | |
| 166 if (!fetch_response.has_error_code() || | |
| 167 fetch_response.error_code() == dm_protocol::POLICY_FETCH_SUCCESS) { | |
| 168 if (cache_->SetPolicy(fetch_response)) | |
| 169 SetState(STATE_POLICY_VALID); | |
| 170 else | |
| 171 SetState(STATE_POLICY_ERROR); | |
| 172 } else { | |
| 173 UMA_HISTOGRAM_ENUMERATION(kMetricPolicy, | |
| 174 kMetricPolicyFetchBadResponse, | |
| 175 kMetricPolicySize); | |
| 176 SetState(STATE_POLICY_UNAVAILABLE); | |
| 177 } | |
| 178 } else { | |
| 179 UMA_HISTOGRAM_ENUMERATION(kMetricPolicy, kMetricPolicyFetchBadResponse, | |
| 180 kMetricPolicySize); | |
| 181 SetState(STATE_POLICY_UNAVAILABLE); | |
| 182 } | |
| 183 return; | |
| 184 } | |
| 185 case DM_STATUS_SERVICE_DEVICE_NOT_FOUND: | |
| 186 case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT: | |
| 187 case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID: | |
| 188 LOG(WARNING) << "The device token was either invalid or unknown to the " | |
| 189 << "device manager, re-registering device."; | |
| 190 // Will retry fetching a token but gracefully backing off. | |
| 191 SetState(STATE_TOKEN_ERROR); | |
| 192 return; | |
| 193 case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER: | |
| 194 VLOG(1) << "The device is no longer enlisted for the domain."; | |
| 195 token_fetcher_->SetSerialNumberInvalidState(); | |
| 196 SetState(STATE_TOKEN_ERROR); | |
| 197 return; | |
| 198 case DM_STATUS_SERVICE_MISSING_LICENSES: | |
| 199 VLOG(1) << "There are no valid licenses for this domain left."; | |
| 200 token_fetcher_->SetMissingLicensesState(); | |
| 201 SetState(STATE_TOKEN_UNMANAGED); | |
| 202 return; | |
| 203 case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED: | |
| 204 VLOG(1) << "The device is no longer managed."; | |
| 205 token_fetcher_->SetUnmanagedState(); | |
| 206 SetState(STATE_TOKEN_UNMANAGED); | |
| 207 return; | |
| 208 case DM_STATUS_SERVICE_POLICY_NOT_FOUND: | |
| 209 case DM_STATUS_REQUEST_INVALID: | |
| 210 case DM_STATUS_SERVICE_ACTIVATION_PENDING: | |
| 211 case DM_STATUS_RESPONSE_DECODING_ERROR: | |
| 212 case DM_STATUS_HTTP_STATUS_ERROR: | |
| 213 VLOG(1) << "An error in the communication with the policy server occurred" | |
| 214 << ", will retry in a few hours."; | |
| 215 SetState(STATE_POLICY_UNAVAILABLE); | |
| 216 return; | |
| 217 case DM_STATUS_REQUEST_FAILED: | |
| 218 case DM_STATUS_TEMPORARY_UNAVAILABLE: | |
| 219 VLOG(1) << "A temporary error in the communication with the policy server" | |
| 220 << " occurred."; | |
| 221 // Will retry last operation but gracefully backing off. | |
| 222 SetState(STATE_POLICY_ERROR); | |
| 223 return; | |
| 224 } | |
| 225 | |
| 226 NOTREACHED(); | |
| 227 SetState(STATE_POLICY_ERROR); | |
| 228 } | |
| 229 | |
| 230 void CloudPolicyController::OnDeviceTokenChanged() { | |
| 231 if (data_store_->device_token().empty()) { | |
| 232 // Additionally clear the generated device id to ensure we don't reuse old | |
| 233 // ids which could be potentially used for user tracking. | |
| 234 data_store_->set_device_id(std::string()); | |
| 235 SetState(STATE_TOKEN_UNAVAILABLE); | |
| 236 } else { | |
| 237 SetState(STATE_TOKEN_VALID); | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 void CloudPolicyController::OnCredentialsChanged() { | |
| 242 // This notification is only interesting if we don't have a device token. | |
| 243 // If we already have a device token, that must be matching the current | |
| 244 // user, because (1) we always recreate the policy subsystem after user | |
| 245 // login (2) tokens are cached per user. | |
| 246 if (data_store_->device_token().empty()) { | |
| 247 notifier_->Inform(CloudPolicySubsystem::UNENROLLED, | |
| 248 CloudPolicySubsystem::NO_DETAILS, | |
| 249 PolicyNotifier::POLICY_CONTROLLER); | |
| 250 effective_policy_refresh_error_delay_ms_ = | |
| 251 kPolicyRefreshErrorDelayInMilliseconds; | |
| 252 SetState(STATE_TOKEN_UNAVAILABLE); | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 CloudPolicyController::CloudPolicyController( | |
| 257 DeviceManagementService* service, | |
| 258 CloudPolicyCacheBase* cache, | |
| 259 DeviceTokenFetcher* token_fetcher, | |
| 260 CloudPolicyDataStore* data_store, | |
| 261 PolicyNotifier* notifier, | |
| 262 DelayedWorkScheduler* scheduler) { | |
| 263 Initialize(service, | |
| 264 cache, | |
| 265 token_fetcher, | |
| 266 data_store, | |
| 267 notifier, | |
| 268 scheduler); | |
| 269 } | |
| 270 | |
| 271 void CloudPolicyController::Initialize( | |
| 272 DeviceManagementService* service, | |
| 273 CloudPolicyCacheBase* cache, | |
| 274 DeviceTokenFetcher* token_fetcher, | |
| 275 CloudPolicyDataStore* data_store, | |
| 276 PolicyNotifier* notifier, | |
| 277 DelayedWorkScheduler* scheduler) { | |
| 278 DCHECK(cache); | |
| 279 | |
| 280 service_ = service; | |
| 281 cache_ = cache; | |
| 282 token_fetcher_ = token_fetcher; | |
| 283 data_store_ = data_store; | |
| 284 notifier_ = notifier; | |
| 285 state_ = STATE_TOKEN_UNAVAILABLE; | |
| 286 policy_refresh_rate_ms_ = kPolicyRefreshRateInMilliseconds; | |
| 287 effective_policy_refresh_error_delay_ms_ = | |
| 288 kPolicyRefreshErrorDelayInMilliseconds; | |
| 289 scheduler_.reset(scheduler); | |
| 290 data_store_->AddObserver(this); | |
| 291 if (!data_store_->device_token().empty()) | |
| 292 SetState(STATE_TOKEN_VALID); | |
| 293 else | |
| 294 SetState(STATE_TOKEN_UNAVAILABLE); | |
| 295 } | |
| 296 | |
| 297 bool CloudPolicyController::ReadyToFetchToken() { | |
| 298 return data_store_->token_cache_loaded() && | |
| 299 !data_store_->user_name().empty() && | |
| 300 data_store_->has_auth_token(); | |
| 301 } | |
| 302 | |
| 303 void CloudPolicyController::FetchToken() { | |
| 304 if (ReadyToFetchToken()) { | |
| 305 if (BrowserPolicyConnector::IsNonEnterpriseUser(data_store_->user_name())) { | |
| 306 SetState(STATE_TOKEN_UNMANAGED); | |
| 307 } else { | |
| 308 // Either use an already prepopulated id or generate a new random device | |
| 309 // id. (It'll only be kept if registration succeeds.) | |
| 310 if (data_store_->device_id().empty()) | |
| 311 data_store_->set_device_id(base::GenerateGUID()); | |
| 312 token_fetcher_->FetchToken(); | |
| 313 } | |
| 314 } else { | |
| 315 VLOG(1) << "Not ready to fetch DMToken yet, will try again later."; | |
| 316 } | |
| 317 } | |
| 318 | |
| 319 void CloudPolicyController::SendPolicyRequest() { | |
| 320 DCHECK(!data_store_->device_token().empty()); | |
| 321 | |
| 322 if (!data_store_->policy_fetching_enabled()) | |
| 323 return; | |
| 324 | |
| 325 request_job_.reset( | |
| 326 service_->CreateJob(DeviceManagementRequestJob::TYPE_POLICY_FETCH)); | |
| 327 request_job_->SetDMToken(data_store_->device_token()); | |
| 328 request_job_->SetClientID(data_store_->device_id()); | |
| 329 request_job_->SetUserAffiliation(data_store_->user_affiliation()); | |
| 330 | |
| 331 em::DeviceManagementRequest* request = request_job_->GetRequest(); | |
| 332 em::PolicyFetchRequest* fetch_request = | |
| 333 request->mutable_policy_request()->add_request(); | |
| 334 fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA); | |
| 335 fetch_request->set_policy_type(data_store_->policy_type()); | |
| 336 if (cache_->machine_id_missing() && !data_store_->machine_id().empty()) | |
| 337 fetch_request->set_machine_id(data_store_->machine_id()); | |
| 338 if (!cache_->is_unmanaged() && | |
| 339 !cache_->last_policy_refresh_time().is_null()) { | |
| 340 base::TimeDelta timestamp = | |
| 341 cache_->last_policy_refresh_time() - base::Time::UnixEpoch(); | |
| 342 fetch_request->set_timestamp(timestamp.InMilliseconds()); | |
| 343 } | |
| 344 int key_version = 0; | |
| 345 if (cache_->GetPublicKeyVersion(&key_version)) | |
| 346 fetch_request->set_public_key_version(key_version); | |
| 347 | |
| 348 #if defined(OS_CHROMEOS) | |
| 349 if (data_store_->device_status_collector()) { | |
| 350 data_store_->device_status_collector()->GetStatus( | |
| 351 request->mutable_device_status_report_request()); | |
| 352 } | |
| 353 #endif | |
| 354 | |
| 355 request_job_->Start(base::Bind(&CloudPolicyController::OnPolicyFetchCompleted, | |
| 356 base::Unretained(this))); | |
| 357 UMA_HISTOGRAM_ENUMERATION(kMetricPolicy, kMetricPolicyFetchRequested, | |
| 358 kMetricPolicySize); | |
| 359 } | |
| 360 | |
| 361 void CloudPolicyController::DoWork() { | |
| 362 switch (state_) { | |
| 363 case STATE_TOKEN_UNAVAILABLE: | |
| 364 case STATE_TOKEN_ERROR: | |
| 365 FetchToken(); | |
| 366 return; | |
| 367 case STATE_TOKEN_VALID: | |
| 368 case STATE_POLICY_VALID: | |
| 369 case STATE_POLICY_ERROR: | |
| 370 case STATE_POLICY_UNAVAILABLE: | |
| 371 SendPolicyRequest(); | |
| 372 return; | |
| 373 case STATE_TOKEN_UNMANAGED: | |
| 374 return; | |
| 375 } | |
| 376 | |
| 377 NOTREACHED() << "Unhandled state" << state_; | |
| 378 } | |
| 379 | |
| 380 void CloudPolicyController::SetState( | |
| 381 CloudPolicyController::ControllerState new_state) { | |
| 382 state_ = new_state; | |
| 383 | |
| 384 request_job_.reset(); // Stop any pending requests. | |
| 385 | |
| 386 base::Time now(base::Time::NowFromSystemTime()); | |
| 387 base::Time last_refresh(GetLastRefreshTime(now)); | |
| 388 base::Time refresh_at; | |
| 389 | |
| 390 // Determine when to take the next step. | |
| 391 bool inform_notifier_done = false; | |
| 392 switch (state_) { | |
| 393 case STATE_TOKEN_UNMANAGED: | |
| 394 notifier_->Inform(CloudPolicySubsystem::UNMANAGED, | |
| 395 CloudPolicySubsystem::NO_DETAILS, | |
| 396 PolicyNotifier::POLICY_CONTROLLER); | |
| 397 break; | |
| 398 case STATE_TOKEN_UNAVAILABLE: | |
| 399 // The controller is not yet initialized and needs to immediately fetch | |
| 400 // token and policy if present. | |
| 401 case STATE_TOKEN_VALID: | |
| 402 // Immediately try to fetch the token on initialization or policy after a | |
| 403 // token update. Subsequent retries will respect the back-off strategy. | |
| 404 refresh_at = now; | |
| 405 // |notifier_| isn't informed about anything at this point, we wait for | |
| 406 // the result of the next action first. | |
| 407 break; | |
| 408 case STATE_POLICY_VALID: | |
| 409 // Delay is only reset if the policy fetch operation was successful. This | |
| 410 // will ensure the server won't get overloaded with retries in case of | |
| 411 // a bug on either side. | |
| 412 effective_policy_refresh_error_delay_ms_ = | |
| 413 kPolicyRefreshErrorDelayInMilliseconds; | |
| 414 refresh_at = last_refresh + GetRefreshDelay(); | |
| 415 notifier_->Inform(CloudPolicySubsystem::SUCCESS, | |
| 416 CloudPolicySubsystem::NO_DETAILS, | |
| 417 PolicyNotifier::POLICY_CONTROLLER); | |
| 418 break; | |
| 419 case STATE_TOKEN_ERROR: | |
| 420 notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR, | |
| 421 CloudPolicySubsystem::BAD_DMTOKEN, | |
| 422 PolicyNotifier::POLICY_CONTROLLER); | |
| 423 inform_notifier_done = true; | |
| 424 case STATE_POLICY_ERROR: | |
| 425 if (!inform_notifier_done) { | |
| 426 notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR, | |
| 427 CloudPolicySubsystem::POLICY_NETWORK_ERROR, | |
| 428 PolicyNotifier::POLICY_CONTROLLER); | |
| 429 } | |
| 430 refresh_at = now + base::TimeDelta::FromMilliseconds( | |
| 431 effective_policy_refresh_error_delay_ms_); | |
| 432 effective_policy_refresh_error_delay_ms_ = | |
| 433 std::min(effective_policy_refresh_error_delay_ms_ * 2, | |
| 434 policy_refresh_rate_ms_); | |
| 435 break; | |
| 436 case STATE_POLICY_UNAVAILABLE: | |
| 437 effective_policy_refresh_error_delay_ms_ = policy_refresh_rate_ms_; | |
| 438 refresh_at = now + base::TimeDelta::FromMilliseconds( | |
| 439 effective_policy_refresh_error_delay_ms_); | |
| 440 notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR, | |
| 441 CloudPolicySubsystem::POLICY_NETWORK_ERROR, | |
| 442 PolicyNotifier::POLICY_CONTROLLER); | |
| 443 break; | |
| 444 } | |
| 445 | |
| 446 // Update the delayed work task. | |
| 447 scheduler_->CancelDelayedWork(); | |
| 448 if (!refresh_at.is_null()) | |
| 449 ScheduleDelayedWorkTask(refresh_at - now); | |
| 450 | |
| 451 // Inform the cache if a fetch attempt has completed. This happens if policy | |
| 452 // has been succesfully fetched, or if token or policy fetching failed. | |
| 453 if (state_ != STATE_TOKEN_UNAVAILABLE && state_ != STATE_TOKEN_VALID) | |
| 454 cache_->SetFetchingDone(); | |
| 455 } | |
| 456 | |
| 457 base::TimeDelta CloudPolicyController::GetRefreshDelay() { | |
| 458 int64 deviation = (kPolicyRefreshDeviationFactorPercent * | |
| 459 policy_refresh_rate_ms_) / 100; | |
| 460 deviation = std::min(deviation, kPolicyRefreshDeviationMaxInMilliseconds); | |
| 461 return base::TimeDelta::FromMilliseconds( | |
| 462 policy_refresh_rate_ms_ - base::RandGenerator(deviation + 1)); | |
| 463 } | |
| 464 | |
| 465 void CloudPolicyController::ScheduleDelayedWorkTask( | |
| 466 const base::TimeDelta& delay) { | |
| 467 int64 effective_delay = std::max<int64>(delay.InMilliseconds(), 0); | |
| 468 scheduler_->PostDelayedWork( | |
| 469 base::Bind(&CloudPolicyController::DoWork, base::Unretained(this)), | |
| 470 effective_delay); | |
| 471 } | |
| 472 | |
| 473 base::Time CloudPolicyController::GetLastRefreshTime(const base::Time& now) { | |
| 474 base::Time last_refresh(cache_->last_policy_refresh_time()); | |
| 475 if (last_refresh.is_null()) | |
| 476 last_refresh = now; | |
| 477 | |
| 478 return last_refresh; | |
| 479 } | |
| 480 | |
| 481 } // namespace policy | |
| OLD | NEW |