// Copyright (c) 2011 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/message_loop.h"
#include "chrome/browser/policy/cloud_policy_cache_base.h"
#include "chrome/browser/policy/device_management_service.h"
#include "chrome/browser/policy/proto/device_management_constants.h"
#include "chrome/browser/policy/proto/device_management_local.pb.h"
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;
} // namespace
namespace policy {
namespace em = enterprise_management;
DeviceTokenFetcher::DeviceTokenFetcher(
DeviceManagementService* service,
CloudPolicyCacheBase* cache,
PolicyNotifier* notifier)
: ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
Initialize(service,
cache,
notifier,
kTokenFetchErrorDelayMilliseconds,
kTokenFetchErrorMaxDelayMilliseconds,
kUnmanagedDeviceRefreshRateMilliseconds);
}
DeviceTokenFetcher::DeviceTokenFetcher(
DeviceManagementService* service,
CloudPolicyCacheBase* cache,
PolicyNotifier* notifier,
int64 token_fetch_error_delay_ms,
int64 token_fetch_error_max_delay_ms,
int64 unmanaged_device_refresh_rate_ms)
: ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
Initialize(service,
cache,
notifier,
token_fetch_error_delay_ms,
token_fetch_error_max_delay_ms,
unmanaged_device_refresh_rate_ms);
}
DeviceTokenFetcher::~DeviceTokenFetcher() {
CancelRetryTask();
}
void DeviceTokenFetcher::FetchToken(
const std::string& auth_token,
const std::string& device_id,
em::DeviceRegisterRequest_Type policy_type,
const std::string& machine_id,
const std::string& machine_model) {
SetState(STATE_INACTIVE);
auth_token_ = auth_token;
device_id_ = device_id;
policy_type_ = policy_type;
machine_id_ = machine_id;
machine_model_ = machine_model;
FetchTokenInternal();
}
void DeviceTokenFetcher::FetchTokenInternal() {
DCHECK(state_ != STATE_TOKEN_AVAILABLE);
if (auth_token_.empty() || device_id_.empty()) {
// Maybe this device is unmanaged, just exit. The CloudPolicyController
// will call FetchToken() again if something changes.
return;
}
// Construct a new backend, which will discard any previous requests.
backend_.reset(service_->CreateBackend());
em::DeviceRegisterRequest request;
request.set_type(policy_type_);
if (!machine_id_.empty())
request.set_machine_id(machine_id_);
if (!machine_model_.empty())
request.set_machine_model(machine_model_);
backend_->ProcessRegisterRequest(auth_token_, device_id_, request, this);
}
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);
}
const std::string& DeviceTokenFetcher::GetDeviceToken() {
return device_token_;
}
void DeviceTokenFetcher::StopAutoRetry() {
CancelRetryTask();
backend_.reset();
device_token_.clear();
auth_token_.clear();
device_id_.clear();
}
void DeviceTokenFetcher::AddObserver(DeviceTokenFetcher::Observer* observer) {
observer_list_.AddObserver(observer);
}
void DeviceTokenFetcher::RemoveObserver(
DeviceTokenFetcher::Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void DeviceTokenFetcher::HandleRegisterResponse(
const em::DeviceRegisterResponse& response) {
if (response.has_device_management_token()) {
device_token_ = response.device_management_token();
SetState(STATE_TOKEN_AVAILABLE);
} else {
NOTREACHED();
SetState(STATE_ERROR);
}
}
void DeviceTokenFetcher::OnError(DeviceManagementBackend::ErrorCode code) {
switch (code) {
case DeviceManagementBackend::kErrorServiceManagementNotSupported:
cache_->SetUnmanaged();
SetState(STATE_UNMANAGED);
break;
case DeviceManagementBackend::kErrorRequestFailed:
case DeviceManagementBackend::kErrorTemporaryUnavailable:
case DeviceManagementBackend::kErrorServiceDeviceNotFound:
SetState(STATE_TEMPORARY_ERROR);
break;
case DeviceManagementBackend::kErrorServiceManagementTokenInvalid:
// Most probably the GAIA auth cookie has expired. We can not do anything
// until the user logs-in again.
SetState(STATE_BAD_AUTH);
break;
default:
SetState(STATE_ERROR);
}
}
void DeviceTokenFetcher::Initialize(DeviceManagementService* service,
CloudPolicyCacheBase* cache,
PolicyNotifier* notifier,
int64 token_fetch_error_delay_ms,
int64 token_fetch_error_max_delay_ms,
int64 unmanaged_device_refresh_rate_ms) {
service_ = service;
cache_ = cache;
notifier_ = notifier;
token_fetch_error_delay_ms_ = token_fetch_error_delay_ms;
token_fetch_error_max_delay_ms_ = token_fetch_error_max_delay_ms;
effective_token_fetch_error_delay_ms_ = token_fetch_error_delay_ms;
unmanaged_device_refresh_rate_ms_ = unmanaged_device_refresh_rate_ms;
state_ = STATE_INACTIVE;
retry_task_ = NULL;
if (cache_->is_unmanaged())
SetState(STATE_UNMANAGED);
}
void DeviceTokenFetcher::SetState(FetcherState state) {
state_ = state;
if (state_ != STATE_ERROR)
effective_token_fetch_error_delay_ms_ = token_fetch_error_delay_ms_;
base::Time delayed_work_at;
switch (state_) {
case STATE_INACTIVE:
device_token_.clear();
auth_token_.clear();
device_id_.clear();
notifier_->Inform(CloudPolicySubsystem::UNENROLLED,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_TOKEN_AVAILABLE:
FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceTokenAvailable());
notifier_->Inform(CloudPolicySubsystem::SUCCESS,
CloudPolicySubsystem::NO_DETAILS,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_UNMANAGED:
delayed_work_at = cache_->last_policy_refresh_time() +
base::TimeDelta::FromMilliseconds(unmanaged_device_refresh_rate_ms_);
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,
token_fetch_error_max_delay_ms_);
notifier_->Inform(CloudPolicySubsystem::NETWORK_ERROR,
CloudPolicySubsystem::DMTOKEN_NETWORK_ERROR,
PolicyNotifier::TOKEN_FETCHER);
break;
case STATE_ERROR:
effective_token_fetch_error_delay_ms_ = token_fetch_error_max_delay_ms_;
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;
}
CancelRetryTask();
if (!delayed_work_at.is_null()) {
base::Time now(base::Time::Now());
int64 delay = std::max<int64>((delayed_work_at - now).InMilliseconds(), 0);
retry_task_ = method_factory_.NewRunnableMethod(
&DeviceTokenFetcher::ExecuteRetryTask);
MessageLoop::current()->PostDelayedTask(FROM_HERE, retry_task_,
delay);
}
}
void DeviceTokenFetcher::ExecuteRetryTask() {
DCHECK(retry_task_);
retry_task_ = NULL;
switch (state_) {
case STATE_INACTIVE:
case STATE_TOKEN_AVAILABLE:
break;
case STATE_UNMANAGED:
case STATE_ERROR:
case STATE_TEMPORARY_ERROR:
case STATE_BAD_AUTH:
FetchTokenInternal();
break;
}
}
void DeviceTokenFetcher::CancelRetryTask() {
if (retry_task_) {
retry_task_->Cancel();
retry_task_ = NULL;
}
}
} // namespace policy