blob: 1cddd69d35b79ff49e21dc2496900e1e1faa7e59 [file] [log] [blame] [edit]
// 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/device_token_fetcher.h"
#include <algorithm>
#include "base/bind.h"
#include "base/metrics/histogram.h"
#include "base/time.h"
#include "chrome/browser/policy/cloud_policy_cache_base.h"
#include "chrome/browser/policy/cloud_policy_constants.h"
#include "chrome/browser/policy/cloud_policy_data_store.h"
#include "chrome/browser/policy/delayed_work_scheduler.h"
#include "chrome/browser/policy/device_management_service.h"
#include "chrome/browser/policy/enterprise_metrics.h"
#include "chrome/browser/policy/policy_notifier.h"
#include "chrome/browser/policy/proto/device_management_local.pb.h"
namespace em = enterprise_management;
namespace policy {
namespace {
// Retry after 5 minutes (with exponential backoff) after token fetch errors.
const int64 kTokenFetchErrorDelayMilliseconds = 5 * 60 * 1000;
// Retry after max 3 hours after token fetch errors.
const int64 kTokenFetchErrorMaxDelayMilliseconds = 3 * 60 * 60 * 1000;
// For unmanaged devices, check once per day whether they're still unmanaged.
const int64 kUnmanagedDeviceRefreshRateMilliseconds = 24 * 60 * 60 * 1000;
// 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(kMetricToken, kMetricTokenFetchResponseReceived,
kMetricTokenSize);
int sample = -1;
switch (status) {
case DM_STATUS_SUCCESS:
return;
case DM_STATUS_REQUEST_FAILED:
case DM_STATUS_REQUEST_INVALID:
case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID:
sample = kMetricTokenFetchRequestFailed;
break;
case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED:
sample = kMetricTokenFetchManagementNotSupported;
break;
case DM_STATUS_SERVICE_DEVICE_NOT_FOUND:
sample = kMetricTokenFetchDeviceNotFound;
break;
case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT:
sample = kMetricTokenFetchDeviceIdConflict;
break;
case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER:
sample = kMetricTokenFetchInvalidSerialNumber;
break;
case DM_STATUS_RESPONSE_DECODING_ERROR:
sample = kMetricTokenFetchBadResponse;
break;
case DM_STATUS_MISSING_LICENSES:
sample = kMetricMissingLicenses;
break;
case DM_STATUS_TEMPORARY_UNAVAILABLE:
case DM_STATUS_SERVICE_ACTIVATION_PENDING:
case DM_STATUS_SERVICE_POLICY_NOT_FOUND:
case DM_STATUS_HTTP_STATUS_ERROR:
sample = kMetricTokenFetchServerFailed;
break;
}
if (sample != -1)
UMA_HISTOGRAM_ENUMERATION(kMetricToken, sample, kMetricTokenSize);
else
NOTREACHED();
}
// Translates the DeviceRegisterResponse::DeviceMode |mode| to the enum used
// internally to represent different device modes.
DeviceMode TranslateProtobufDeviceMode(
em::DeviceRegisterResponse::DeviceMode mode) {
switch (mode) {
case em::DeviceRegisterResponse::ENTERPRISE:
return DEVICE_MODE_ENTERPRISE;
case em::DeviceRegisterResponse::RETAIL:
return DEVICE_MODE_KIOSK;
}
LOG(ERROR) << "Unknown enrollment mode in registration response: " << mode;
return DEVICE_MODE_PENDING;
}
} // namespace
DeviceTokenFetcher::DeviceTokenFetcher(
DeviceManagementService* service,
CloudPolicyCacheBase* cache,
CloudPolicyDataStore* data_store,
PolicyNotifier* notifier)
: effective_token_fetch_error_delay_ms_(
kTokenFetchErrorDelayMilliseconds) {
Initialize(service,
cache,
data_store,
notifier,
new DelayedWorkScheduler);
}
DeviceTokenFetcher::DeviceTokenFetcher(
DeviceManagementService* service,
CloudPolicyCacheBase* cache,
CloudPolicyDataStore* data_store,
PolicyNotifier* notifier,
DelayedWorkScheduler* scheduler)
: effective_token_fetch_error_delay_ms_(
kTokenFetchErrorDelayMilliseconds) {
Initialize(service, cache, data_store, notifier, scheduler);
}
DeviceTokenFetcher::~DeviceTokenFetcher() {
scheduler_->CancelDelayedWork();
}
void DeviceTokenFetcher::FetchToken() {
SetState(STATE_INACTIVE);
FetchTokenInternal();
}
void DeviceTokenFetcher::SetUnmanagedState() {
// The call to |cache_->SetUnmanaged()| has to happen first because it sets
// the timestamp that |SetState()| needs to determine the correct refresh
// time.
cache_->SetUnmanaged();
SetState(STATE_UNMANAGED);
}
void DeviceTokenFetcher::SetSerialNumberInvalidState() {
SetState(STATE_BAD_SERIAL);
}
void DeviceTokenFetcher::SetMissingLicensesState() {
SetState(STATE_MISSING_LICENSES);
}
void DeviceTokenFetcher::Reset() {
SetState(STATE_INACTIVE);
}
void DeviceTokenFetcher::Initialize(DeviceManagementService* service,
CloudPolicyCacheBase* cache,
CloudPolicyDataStore* data_store,
PolicyNotifier* notifier,
DelayedWorkScheduler* scheduler) {
service_ = service;
cache_ = cache;
notifier_ = notifier;
data_store_ = data_store;
effective_token_fetch_error_delay_ms_ = kTokenFetchErrorDelayMilliseconds;
state_ = STATE_INACTIVE;
scheduler_.reset(scheduler);
if (cache_->is_unmanaged())
SetState(STATE_UNMANAGED);
}
void DeviceTokenFetcher::FetchTokenInternal() {
DCHECK(state_ != STATE_TOKEN_AVAILABLE);
if (!data_store_->has_auth_token() || data_store_->device_id().empty()) {
// Maybe this device is unmanaged, just exit. The CloudPolicyController
// will call FetchToken() again if something changes.
return;
}
// Reinitialize |request_job_|, discarding any previous requests.
request_job_.reset(
service_->CreateJob(DeviceManagementRequestJob::TYPE_REGISTRATION));
request_job_->SetGaiaToken(data_store_->gaia_token());
request_job_->SetOAuthToken(data_store_->oauth_token());
request_job_->SetClientID(data_store_->device_id());
em::DeviceRegisterRequest* request =
request_job_->GetRequest()->mutable_register_request();
request->set_type(data_store_->policy_register_type());
if (!data_store_->machine_id().empty())
request->set_machine_id(data_store_->machine_id());
if (!data_store_->machine_model().empty())
request->set_machine_model(data_store_->machine_model());
if (data_store_->known_machine_id())
request->set_auto_enrolled(true);
if (data_store_->reregister())
request->set_reregister(true);
request_job_->Start(base::Bind(&DeviceTokenFetcher::OnTokenFetchCompleted,
base::Unretained(this)));
UMA_HISTOGRAM_ENUMERATION(kMetricToken, kMetricTokenFetchRequested,
kMetricTokenSize);
}
void DeviceTokenFetcher::OnTokenFetchCompleted(
DeviceManagementStatus status,
const em::DeviceManagementResponse& response) {
if (status == DM_STATUS_SUCCESS && !response.has_register_response()) {
// Handled below.
status = DM_STATUS_RESPONSE_DECODING_ERROR;
}
LOG_IF(ERROR, status != DM_STATUS_SUCCESS) << "DMServer returned error code: "
<< status;
SampleErrorStatus(status);
switch (status) {
case DM_STATUS_SUCCESS: {
const em::DeviceRegisterResponse& register_response =
response.register_response();
if (register_response.has_device_management_token()) {
UMA_HISTOGRAM_ENUMERATION(kMetricToken, kMetricTokenFetchOK,
kMetricTokenSize);
if (data_store_->policy_register_type() ==
em::DeviceRegisterRequest::DEVICE) {
DeviceMode mode = DEVICE_MODE_ENTERPRISE;
if (register_response.has_enrollment_type()) {
mode = TranslateProtobufDeviceMode(
register_response.enrollment_type());
}
if (mode == DEVICE_MODE_PENDING) {
LOG(ERROR) << "Enrollment mode missing or unknown!";
SetState(STATE_BAD_ENROLLMENT_MODE);
return;
}
data_store_->set_device_mode(mode);
}
data_store_->SetDeviceToken(register_response.device_management_token(),
false);
SetState(STATE_TOKEN_AVAILABLE);
} else {
NOTREACHED();
UMA_HISTOGRAM_ENUMERATION(kMetricToken, kMetricTokenFetchBadResponse,
kMetricTokenSize);
SetState(STATE_ERROR);
}
return;
}
case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED:
SetUnmanagedState();
return;
case DM_STATUS_REQUEST_FAILED:
case DM_STATUS_TEMPORARY_UNAVAILABLE:
case DM_STATUS_SERVICE_DEVICE_NOT_FOUND:
case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT:
SetState(STATE_TEMPORARY_ERROR);
return;
case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID:
// Most probably the GAIA auth cookie has expired. We can not do anything
// until the user logs-in again.
SetState(STATE_BAD_AUTH);
return;
case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER:
SetSerialNumberInvalidState();
return;
case DM_STATUS_MISSING_LICENSES:
SetMissingLicensesState();
return;
case DM_STATUS_REQUEST_INVALID:
case DM_STATUS_HTTP_STATUS_ERROR:
case DM_STATUS_RESPONSE_DECODING_ERROR:
case DM_STATUS_SERVICE_ACTIVATION_PENDING:
case DM_STATUS_SERVICE_POLICY_NOT_FOUND:
SetState(STATE_ERROR);
return;
}
NOTREACHED();
SetState(STATE_ERROR);
}
void DeviceTokenFetcher::SetState(FetcherState state) {
state_ = state;
if (state_ != STATE_TEMPORARY_ERROR)
effective_token_fetch_error_delay_ms_ = kTokenFetchErrorDelayMilliseconds;
request_job_.reset(); // Stop any pending requests.
base::Time delayed_work_at;
switch (state_) {
case STATE_INACTIVE:
notifier_->Inform(CloudPolicySubsystem::UNENROLLED,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_TOKEN_AVAILABLE:
notifier_->Inform(CloudPolicySubsystem::SUCCESS,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_BAD_SERIAL:
notifier_->Inform(CloudPolicySubsystem::UNENROLLED,
CloudPolicySubsystem::BAD_SERIAL_NUMBER,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_BAD_ENROLLMENT_MODE:
notifier_->Inform(CloudPolicySubsystem::UNENROLLED,
CloudPolicySubsystem::BAD_ENROLLMENT_MODE,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_MISSING_LICENSES:
notifier_->Inform(CloudPolicySubsystem::UNENROLLED,
CloudPolicySubsystem::MISSING_LICENSES,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_UNMANAGED:
delayed_work_at = cache_->last_policy_refresh_time() +
base::TimeDelta::FromMilliseconds(
kUnmanagedDeviceRefreshRateMilliseconds);
notifier_->Inform(CloudPolicySubsystem::UNMANAGED,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_TEMPORARY_ERROR:
delayed_work_at = base::Time::Now() +
base::TimeDelta::FromMilliseconds(
effective_token_fetch_error_delay_ms_);
effective_token_fetch_error_delay_ms_ =
std::min(effective_token_fetch_error_delay_ms_ * 2,
kTokenFetchErrorMaxDelayMilliseconds);
notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR,
CloudPolicySubsystem::DMTOKEN_NETWORK_ERROR,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_ERROR:
effective_token_fetch_error_delay_ms_ =
kTokenFetchErrorMaxDelayMilliseconds;
delayed_work_at = base::Time::Now() +
base::TimeDelta::FromMilliseconds(
effective_token_fetch_error_delay_ms_);
notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR,
CloudPolicySubsystem::DMTOKEN_NETWORK_ERROR,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_BAD_AUTH:
// Can't do anything, need to wait for new credentials.
notifier_->Inform(CloudPolicySubsystem::BAD_GAIA_TOKEN,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::TOKEN_FETCHER);
break;
}
scheduler_->CancelDelayedWork();
if (!delayed_work_at.is_null()) {
base::Time now(base::Time::Now());
int64 delay = std::max<int64>((delayed_work_at - now).InMilliseconds(), 0);
scheduler_->PostDelayedWork(
base::Bind(&DeviceTokenFetcher::DoWork, base::Unretained(this)), delay);
}
// Inform the cache if a token fetch attempt has failed.
if (state_ != STATE_INACTIVE && state_ != STATE_TOKEN_AVAILABLE)
cache_->SetFetchingDone();
}
void DeviceTokenFetcher::DoWork() {
switch (state_) {
case STATE_INACTIVE:
case STATE_TOKEN_AVAILABLE:
case STATE_BAD_SERIAL:
case STATE_BAD_ENROLLMENT_MODE:
case STATE_MISSING_LICENSES:
break;
case STATE_UNMANAGED:
case STATE_ERROR:
case STATE_TEMPORARY_ERROR:
case STATE_BAD_AUTH:
FetchTokenInternal();
break;
}
}
} // namespace policy