blob: bd4b9b56d8095c935152072364fd163b60f6e603 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/cloud_policy_controller.h"
#include <algorithm>
#include <string>
#include "base/bind.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/rand_util.h"
#include "chrome/browser/policy/browser_policy_connector.h"
#include "chrome/browser/policy/cloud_policy_cache_base.h"
#include "chrome/browser/policy/cloud_policy_constants.h"
#include "chrome/browser/policy/cloud_policy_subsystem.h"
#include "chrome/browser/policy/delayed_work_scheduler.h"
#include "chrome/browser/policy/device_management_service.h"
#include "chrome/browser/policy/device_token_fetcher.h"
#include "chrome/browser/policy/enterprise_metrics.h"
#include "chrome/browser/policy/policy_notifier.h"
namespace policy {
namespace {
// The maximum ratio in percent of the policy refresh rate we use for adjusting
// the policy refresh time instant. The rationale is to avoid load spikes from
// many devices that were set up in sync for some reason.
const int kPolicyRefreshDeviationFactorPercent = 10;
// Maximum deviation we are willing to accept.
const int64 kPolicyRefreshDeviationMaxInMilliseconds = 30 * 60 * 1000;
// These are the base values for delays before retrying after an error. They
// will be doubled each time they are used.
const int64 kPolicyRefreshErrorDelayInMilliseconds =
5 * 60 * 1000; // 5 minutes.
// Default value for the policy refresh rate.
const int kPolicyRefreshRateInMilliseconds = 3 * 60 * 60 * 1000; // 3 hours.
// Records the UMA metric corresponding to |status|, if it represents an error.
// Also records that a fetch response was received.
void SampleErrorStatus(DeviceManagementStatus status) {
UMA_HISTOGRAM_ENUMERATION(kMetricPolicy,
kMetricPolicyFetchResponseReceived,
kMetricPolicySize);
int sample = -1;
switch (status) {
case DM_STATUS_SUCCESS:
return;
case DM_STATUS_SERVICE_POLICY_NOT_FOUND:
sample = kMetricPolicyFetchNotFound;
break;
case DM_STATUS_SERVICE_DEVICE_NOT_FOUND:
sample = kMetricPolicyFetchInvalidToken;
break;
case DM_STATUS_RESPONSE_DECODING_ERROR:
sample = kMetricPolicyFetchBadResponse;
break;
case DM_STATUS_REQUEST_FAILED:
case DM_STATUS_REQUEST_INVALID:
case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID:
sample = kMetricPolicyFetchRequestFailed;
break;
case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED:
case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT:
case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER:
case DM_STATUS_TEMPORARY_UNAVAILABLE:
case DM_STATUS_SERVICE_ACTIVATION_PENDING:
case DM_STATUS_HTTP_STATUS_ERROR:
case DM_STATUS_SERVICE_MISSING_LICENSES:
sample = kMetricPolicyFetchServerFailed;
break;
}
if (sample != -1)
UMA_HISTOGRAM_ENUMERATION(kMetricPolicy, sample, kMetricPolicySize);
else
NOTREACHED();
}
} // namespace
namespace em = enterprise_management;
CloudPolicyController::CloudPolicyController(
DeviceManagementService* service,
CloudPolicyCacheBase* cache,
DeviceTokenFetcher* token_fetcher,
CloudPolicyDataStore* data_store,
PolicyNotifier* notifier) {
Initialize(service,
cache,
token_fetcher,
data_store,
notifier,
new DelayedWorkScheduler);
}
CloudPolicyController::~CloudPolicyController() {
data_store_->RemoveObserver(this);
scheduler_->CancelDelayedWork();
}
void CloudPolicyController::SetRefreshRate(int64 refresh_rate_milliseconds) {
policy_refresh_rate_ms_ = refresh_rate_milliseconds;
// Reschedule the refresh task if necessary.
if (state_ == STATE_POLICY_VALID) {
scheduler_->CancelDelayedWork();
base::Time now(base::Time::NowFromSystemTime());
ScheduleDelayedWorkTask(
(GetLastRefreshTime(now) + GetRefreshDelay()) - now);
}
}
void CloudPolicyController::Retry() {
scheduler_->CancelDelayedWork();
DoWork();
}
void CloudPolicyController::Reset() {
SetState(STATE_TOKEN_UNAVAILABLE);
}
void CloudPolicyController::RefreshPolicies(bool wait_for_auth_token) {
// This call must eventually trigger a notification to the cache.
if (data_store_->device_token().empty()) {
// The DMToken has to be fetched.
if (ReadyToFetchToken()) {
SetState(STATE_TOKEN_UNAVAILABLE);
} else if (!wait_for_auth_token) {
// The controller doesn't have enough material to start a token fetch,
// but observers of the cache are waiting for the refresh.
SetState(STATE_TOKEN_UNMANAGED);
}
} else {
// The token is valid, so the next step is to fetch policy.
SetState(STATE_TOKEN_VALID);
}
}
void CloudPolicyController::OnPolicyFetchCompleted(
DeviceManagementStatus status,
const em::DeviceManagementResponse& response) {
if (status == DM_STATUS_SUCCESS && !response.has_policy_response()) {
// Handled below.
status = DM_STATUS_RESPONSE_DECODING_ERROR;
}
SampleErrorStatus(status);
switch (status) {
case DM_STATUS_SUCCESS: {
const em::DevicePolicyResponse& policy_response(
response.policy_response());
if (policy_response.response_size() > 0) {
if (policy_response.response_size() > 1) {
LOG(WARNING) << "More than one policy in the response of the device "
<< "management server, discarding.";
}
const em::PolicyFetchResponse& fetch_response(
policy_response.response(0));
if (!fetch_response.has_error_code() ||
fetch_response.error_code() == dm_protocol::POLICY_FETCH_SUCCESS) {
if (cache_->SetPolicy(fetch_response))
SetState(STATE_POLICY_VALID);
else
SetState(STATE_POLICY_ERROR);
} else {
UMA_HISTOGRAM_ENUMERATION(kMetricPolicy,
kMetricPolicyFetchBadResponse,
kMetricPolicySize);
SetState(STATE_POLICY_UNAVAILABLE);
}
} else {
UMA_HISTOGRAM_ENUMERATION(kMetricPolicy, kMetricPolicyFetchBadResponse,
kMetricPolicySize);
SetState(STATE_POLICY_UNAVAILABLE);
}
return;
}
case DM_STATUS_SERVICE_DEVICE_NOT_FOUND:
case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT:
case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID:
LOG(WARNING) << "The device token was either invalid or unknown to the "
<< "device manager, re-registering device.";
// Will retry fetching a token but gracefully backing off.
SetState(STATE_TOKEN_ERROR);
return;
case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER:
VLOG(1) << "The device is no longer enlisted for the domain.";
token_fetcher_->SetSerialNumberInvalidState();
SetState(STATE_TOKEN_ERROR);
return;
case DM_STATUS_SERVICE_MISSING_LICENSES:
VLOG(1) << "There are no valid licenses for this domain left.";
token_fetcher_->SetMissingLicensesState();
SetState(STATE_TOKEN_UNMANAGED);
return;
case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED:
VLOG(1) << "The device is no longer managed.";
token_fetcher_->SetUnmanagedState();
SetState(STATE_TOKEN_UNMANAGED);
return;
case DM_STATUS_SERVICE_POLICY_NOT_FOUND:
case DM_STATUS_REQUEST_INVALID:
case DM_STATUS_SERVICE_ACTIVATION_PENDING:
case DM_STATUS_RESPONSE_DECODING_ERROR:
case DM_STATUS_HTTP_STATUS_ERROR:
VLOG(1) << "An error in the communication with the policy server occurred"
<< ", will retry in a few hours.";
SetState(STATE_POLICY_UNAVAILABLE);
return;
case DM_STATUS_REQUEST_FAILED:
case DM_STATUS_TEMPORARY_UNAVAILABLE:
VLOG(1) << "A temporary error in the communication with the policy server"
<< " occurred.";
// Will retry last operation but gracefully backing off.
SetState(STATE_POLICY_ERROR);
return;
}
NOTREACHED();
SetState(STATE_POLICY_ERROR);
}
void CloudPolicyController::OnDeviceTokenChanged() {
if (data_store_->device_token().empty()) {
// Additionally clear the generated device id to ensure we don't reuse old
// ids which could be potentially used for user tracking.
data_store_->set_device_id(std::string());
SetState(STATE_TOKEN_UNAVAILABLE);
} else {
SetState(STATE_TOKEN_VALID);
}
}
void CloudPolicyController::OnCredentialsChanged() {
// This notification is only interesting if we don't have a device token.
// If we already have a device token, that must be matching the current
// user, because (1) we always recreate the policy subsystem after user
// login (2) tokens are cached per user.
if (data_store_->device_token().empty()) {
notifier_->Inform(CloudPolicySubsystem::UNENROLLED,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::POLICY_CONTROLLER);
effective_policy_refresh_error_delay_ms_ =
kPolicyRefreshErrorDelayInMilliseconds;
SetState(STATE_TOKEN_UNAVAILABLE);
}
}
CloudPolicyController::CloudPolicyController(
DeviceManagementService* service,
CloudPolicyCacheBase* cache,
DeviceTokenFetcher* token_fetcher,
CloudPolicyDataStore* data_store,
PolicyNotifier* notifier,
DelayedWorkScheduler* scheduler) {
Initialize(service,
cache,
token_fetcher,
data_store,
notifier,
scheduler);
}
void CloudPolicyController::Initialize(
DeviceManagementService* service,
CloudPolicyCacheBase* cache,
DeviceTokenFetcher* token_fetcher,
CloudPolicyDataStore* data_store,
PolicyNotifier* notifier,
DelayedWorkScheduler* scheduler) {
DCHECK(cache);
service_ = service;
cache_ = cache;
token_fetcher_ = token_fetcher;
data_store_ = data_store;
notifier_ = notifier;
state_ = STATE_TOKEN_UNAVAILABLE;
policy_refresh_rate_ms_ = kPolicyRefreshRateInMilliseconds;
effective_policy_refresh_error_delay_ms_ =
kPolicyRefreshErrorDelayInMilliseconds;
scheduler_.reset(scheduler);
data_store_->AddObserver(this);
if (!data_store_->device_token().empty())
SetState(STATE_TOKEN_VALID);
else
SetState(STATE_TOKEN_UNAVAILABLE);
}
bool CloudPolicyController::ReadyToFetchToken() {
return data_store_->token_cache_loaded() &&
!data_store_->user_name().empty() &&
data_store_->has_auth_token();
}
void CloudPolicyController::FetchToken() {
if (ReadyToFetchToken()) {
if (BrowserPolicyConnector::IsNonEnterpriseUser(data_store_->user_name())) {
SetState(STATE_TOKEN_UNMANAGED);
} else {
// Either use an already prepopulated id or generate a new random device
// id. (It'll only be kept if registration succeeds.)
if (data_store_->device_id().empty())
data_store_->set_device_id(base::GenerateGUID());
token_fetcher_->FetchToken();
}
} else {
VLOG(1) << "Not ready to fetch DMToken yet, will try again later.";
}
}
void CloudPolicyController::SendPolicyRequest() {
DCHECK(!data_store_->device_token().empty());
if (!data_store_->policy_fetching_enabled())
return;
request_job_.reset(
service_->CreateJob(DeviceManagementRequestJob::TYPE_POLICY_FETCH));
request_job_->SetDMToken(data_store_->device_token());
request_job_->SetClientID(data_store_->device_id());
request_job_->SetUserAffiliation(data_store_->user_affiliation());
em::DeviceManagementRequest* request = request_job_->GetRequest();
em::PolicyFetchRequest* fetch_request =
request->mutable_policy_request()->add_request();
fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA);
fetch_request->set_policy_type(data_store_->policy_type());
if (cache_->machine_id_missing() && !data_store_->machine_id().empty())
fetch_request->set_machine_id(data_store_->machine_id());
if (!cache_->is_unmanaged() &&
!cache_->last_policy_refresh_time().is_null()) {
base::TimeDelta timestamp =
cache_->last_policy_refresh_time() - base::Time::UnixEpoch();
fetch_request->set_timestamp(timestamp.InMilliseconds());
}
int key_version = 0;
if (cache_->GetPublicKeyVersion(&key_version))
fetch_request->set_public_key_version(key_version);
#if defined(OS_CHROMEOS)
if (data_store_->device_status_collector()) {
data_store_->device_status_collector()->GetStatus(
request->mutable_device_status_report_request());
}
#endif
request_job_->Start(base::Bind(&CloudPolicyController::OnPolicyFetchCompleted,
base::Unretained(this)));
UMA_HISTOGRAM_ENUMERATION(kMetricPolicy, kMetricPolicyFetchRequested,
kMetricPolicySize);
}
void CloudPolicyController::DoWork() {
switch (state_) {
case STATE_TOKEN_UNAVAILABLE:
case STATE_TOKEN_ERROR:
FetchToken();
return;
case STATE_TOKEN_VALID:
case STATE_POLICY_VALID:
case STATE_POLICY_ERROR:
case STATE_POLICY_UNAVAILABLE:
SendPolicyRequest();
return;
case STATE_TOKEN_UNMANAGED:
return;
}
NOTREACHED() << "Unhandled state" << state_;
}
void CloudPolicyController::SetState(
CloudPolicyController::ControllerState new_state) {
state_ = new_state;
request_job_.reset(); // Stop any pending requests.
base::Time now(base::Time::NowFromSystemTime());
base::Time last_refresh(GetLastRefreshTime(now));
base::Time refresh_at;
// Determine when to take the next step.
bool inform_notifier_done = false;
switch (state_) {
case STATE_TOKEN_UNMANAGED:
notifier_->Inform(CloudPolicySubsystem::UNMANAGED,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::POLICY_CONTROLLER);
break;
case STATE_TOKEN_UNAVAILABLE:
// The controller is not yet initialized and needs to immediately fetch
// token and policy if present.
case STATE_TOKEN_VALID:
// Immediately try to fetch the token on initialization or policy after a
// token update. Subsequent retries will respect the back-off strategy.
refresh_at = now;
// |notifier_| isn't informed about anything at this point, we wait for
// the result of the next action first.
break;
case STATE_POLICY_VALID:
// Delay is only reset if the policy fetch operation was successful. This
// will ensure the server won't get overloaded with retries in case of
// a bug on either side.
effective_policy_refresh_error_delay_ms_ =
kPolicyRefreshErrorDelayInMilliseconds;
refresh_at = last_refresh + GetRefreshDelay();
notifier_->Inform(CloudPolicySubsystem::SUCCESS,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::POLICY_CONTROLLER);
break;
case STATE_TOKEN_ERROR:
notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR,
CloudPolicySubsystem::BAD_DMTOKEN,
PolicyNotifier::POLICY_CONTROLLER);
inform_notifier_done = true;
case STATE_POLICY_ERROR:
if (!inform_notifier_done) {
notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR,
CloudPolicySubsystem::POLICY_NETWORK_ERROR,
PolicyNotifier::POLICY_CONTROLLER);
}
refresh_at = now + base::TimeDelta::FromMilliseconds(
effective_policy_refresh_error_delay_ms_);
effective_policy_refresh_error_delay_ms_ =
std::min(effective_policy_refresh_error_delay_ms_ * 2,
policy_refresh_rate_ms_);
break;
case STATE_POLICY_UNAVAILABLE:
effective_policy_refresh_error_delay_ms_ = policy_refresh_rate_ms_;
refresh_at = now + base::TimeDelta::FromMilliseconds(
effective_policy_refresh_error_delay_ms_);
notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR,
CloudPolicySubsystem::POLICY_NETWORK_ERROR,
PolicyNotifier::POLICY_CONTROLLER);
break;
}
// Update the delayed work task.
scheduler_->CancelDelayedWork();
if (!refresh_at.is_null())
ScheduleDelayedWorkTask(refresh_at - now);
// Inform the cache if a fetch attempt has completed. This happens if policy
// has been succesfully fetched, or if token or policy fetching failed.
if (state_ != STATE_TOKEN_UNAVAILABLE && state_ != STATE_TOKEN_VALID)
cache_->SetFetchingDone();
}
base::TimeDelta CloudPolicyController::GetRefreshDelay() {
int64 deviation = (kPolicyRefreshDeviationFactorPercent *
policy_refresh_rate_ms_) / 100;
deviation = std::min(deviation, kPolicyRefreshDeviationMaxInMilliseconds);
return base::TimeDelta::FromMilliseconds(
policy_refresh_rate_ms_ - base::RandGenerator(deviation + 1));
}
void CloudPolicyController::ScheduleDelayedWorkTask(
const base::TimeDelta& delay) {
int64 effective_delay = std::max<int64>(delay.InMilliseconds(), 0);
scheduler_->PostDelayedWork(
base::Bind(&CloudPolicyController::DoWork, base::Unretained(this)),
effective_delay);
}
base::Time CloudPolicyController::GetLastRefreshTime(const base::Time& now) {
base::Time last_refresh(cache_->last_policy_refresh_time());
if (last_refresh.is_null())
last_refresh = now;
return last_refresh;
}
} // namespace policy