| // 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/cloud_policy_controller.h" |
| |
| #include <algorithm> |
| |
| #include "base/logging.h" |
| #include "base/message_loop.h" |
| #include "base/rand_util.h" |
| #include "base/string_util.h" |
| #include "chrome/browser/policy/cloud_policy_cache_base.h" |
| #include "chrome/browser/policy/cloud_policy_subsystem.h" |
| #include "chrome/browser/policy/device_management_backend.h" |
| #include "chrome/browser/policy/device_management_service.h" |
| #include "chrome/browser/policy/proto/device_management_constants.h" |
| |
| // Domain names that are known not to be managed. |
| // We don't register the device when such a user logs in. |
| static const char* kNonManagedDomains[] = { |
| "@googlemail.com", |
| "@gmail.com" |
| }; |
| |
| // Checks the domain part of the given username against the list of known |
| // non-managed domain names. Returns false if |username| is empty or |
| // in a domain known not to be managed. |
| static bool CanBeInManagedDomain(const std::string& username) { |
| if (username.empty()) { |
| // This means incognito user in case of ChromiumOS and |
| // no logged-in user in case of Chromium (SigninService). |
| return false; |
| } |
| for (size_t i = 0; i < arraysize(kNonManagedDomains); i++) { |
| if (EndsWith(username, kNonManagedDomains[i], true)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| namespace policy { |
| |
| namespace em = enterprise_management; |
| |
| // 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. |
| static const int kPolicyRefreshDeviationFactorPercent = 10; |
| // Maximum deviation we are willing to accept. |
| static 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. |
| static const int64 kPolicyRefreshErrorDelayInMilliseconds = |
| 5 * 60 * 1000; // 5 minutes |
| |
| // Default value for the policy refresh rate. |
| static const int kPolicyRefreshRateInMilliseconds = |
| 3 * 60 * 60 * 1000; // 3 hours. |
| |
| CloudPolicyController::CloudPolicyController( |
| DeviceManagementService* service, |
| CloudPolicyCacheBase* cache, |
| DeviceTokenFetcher* token_fetcher, |
| CloudPolicyIdentityStrategy* identity_strategy, |
| PolicyNotifier* notifier) |
| : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { |
| Initialize(service, |
| cache, |
| token_fetcher, |
| identity_strategy, |
| notifier, |
| kPolicyRefreshRateInMilliseconds, |
| kPolicyRefreshDeviationFactorPercent, |
| kPolicyRefreshDeviationMaxInMilliseconds, |
| kPolicyRefreshErrorDelayInMilliseconds); |
| } |
| |
| CloudPolicyController::~CloudPolicyController() { |
| token_fetcher_->RemoveObserver(this); |
| identity_strategy_->RemoveObserver(this); |
| 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) |
| SetState(STATE_POLICY_VALID); |
| } |
| |
| void CloudPolicyController::Retry() { |
| CancelDelayedWork(); |
| DoWork(); |
| } |
| |
| void CloudPolicyController::StopAutoRetry() { |
| CancelDelayedWork(); |
| backend_.reset(); |
| } |
| |
| void CloudPolicyController::HandlePolicyResponse( |
| const em::DevicePolicyResponse& response) { |
| if (response.response_size() > 0) { |
| if (response.response_size() > 1) { |
| LOG(WARNING) << "More than one policy in the response of the device " |
| << "management server, discarding."; |
| } |
| if (response.response(0).error_code() != |
| DeviceManagementBackend::kErrorServicePolicyNotFound) { |
| cache_->SetPolicy(response.response(0)); |
| SetState(STATE_POLICY_VALID); |
| } else { |
| SetState(STATE_POLICY_UNAVAILABLE); |
| } |
| } |
| } |
| |
| void CloudPolicyController::OnError(DeviceManagementBackend::ErrorCode code) { |
| switch (code) { |
| case DeviceManagementBackend::kErrorServiceDeviceNotFound: |
| case DeviceManagementBackend::kErrorServiceManagementTokenInvalid: { |
| 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); |
| break; |
| } |
| case DeviceManagementBackend::kErrorServiceManagementNotSupported: { |
| VLOG(1) << "The device is no longer managed."; |
| token_fetcher_->SetUnmanagedState(); |
| SetState(STATE_TOKEN_UNMANAGED); |
| break; |
| } |
| case DeviceManagementBackend::kErrorServicePolicyNotFound: |
| case DeviceManagementBackend::kErrorRequestInvalid: |
| case DeviceManagementBackend::kErrorServiceActivationPending: |
| case DeviceManagementBackend::kErrorResponseDecoding: |
| case DeviceManagementBackend::kErrorHttpStatus: { |
| VLOG(1) << "An error in the communication with the policy server occurred" |
| << ", will retry in a few hours."; |
| SetState(STATE_POLICY_UNAVAILABLE); |
| break; |
| } |
| case DeviceManagementBackend::kErrorRequestFailed: |
| case DeviceManagementBackend::kErrorTemporaryUnavailable: { |
| 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); |
| } |
| } |
| } |
| |
| void CloudPolicyController::OnDeviceTokenAvailable() { |
| identity_strategy_->OnDeviceTokenAvailable(token_fetcher_->GetDeviceToken()); |
| } |
| |
| void CloudPolicyController::OnDeviceTokenChanged() { |
| if (identity_strategy_->GetDeviceToken().empty()) |
| SetState(STATE_TOKEN_UNAVAILABLE); |
| else |
| SetState(STATE_TOKEN_VALID); |
| } |
| |
| void CloudPolicyController::OnCredentialsChanged() { |
| effective_policy_refresh_error_delay_ms_ = policy_refresh_error_delay_ms_; |
| SetState(STATE_TOKEN_UNAVAILABLE); |
| } |
| |
| CloudPolicyController::CloudPolicyController( |
| DeviceManagementService* service, |
| CloudPolicyCacheBase* cache, |
| DeviceTokenFetcher* token_fetcher, |
| CloudPolicyIdentityStrategy* identity_strategy, |
| PolicyNotifier* notifier, |
| int64 policy_refresh_rate_ms, |
| int policy_refresh_deviation_factor_percent, |
| int64 policy_refresh_deviation_max_ms, |
| int64 policy_refresh_error_delay_ms) |
| : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { |
| Initialize(service, |
| cache, |
| token_fetcher, |
| identity_strategy, |
| notifier, |
| policy_refresh_rate_ms, |
| policy_refresh_deviation_factor_percent, |
| policy_refresh_deviation_max_ms, |
| policy_refresh_error_delay_ms); |
| } |
| |
| void CloudPolicyController::Initialize( |
| DeviceManagementService* service, |
| CloudPolicyCacheBase* cache, |
| DeviceTokenFetcher* token_fetcher, |
| CloudPolicyIdentityStrategy* identity_strategy, |
| PolicyNotifier* notifier, |
| int64 policy_refresh_rate_ms, |
| int policy_refresh_deviation_factor_percent, |
| int64 policy_refresh_deviation_max_ms, |
| int64 policy_refresh_error_delay_ms) { |
| DCHECK(cache); |
| |
| service_ = service; |
| cache_ = cache; |
| token_fetcher_ = token_fetcher; |
| identity_strategy_ = identity_strategy; |
| notifier_ = notifier; |
| state_ = STATE_TOKEN_UNAVAILABLE; |
| delayed_work_task_ = NULL; |
| policy_refresh_rate_ms_ = policy_refresh_rate_ms; |
| policy_refresh_deviation_factor_percent_ = |
| policy_refresh_deviation_factor_percent; |
| policy_refresh_deviation_max_ms_ = policy_refresh_deviation_max_ms; |
| policy_refresh_error_delay_ms_ = policy_refresh_error_delay_ms; |
| effective_policy_refresh_error_delay_ms_ = policy_refresh_error_delay_ms; |
| |
| token_fetcher_->AddObserver(this); |
| identity_strategy_->AddObserver(this); |
| if (!identity_strategy_->GetDeviceToken().empty()) |
| SetState(STATE_TOKEN_VALID); |
| else |
| SetState(STATE_TOKEN_UNAVAILABLE); |
| } |
| |
| void CloudPolicyController::FetchToken() { |
| std::string username; |
| std::string auth_token; |
| std::string device_id = identity_strategy_->GetDeviceID(); |
| std::string machine_id = identity_strategy_->GetMachineID(); |
| std::string machine_model = identity_strategy_->GetMachineModel(); |
| em::DeviceRegisterRequest_Type policy_type = |
| identity_strategy_->GetPolicyRegisterType(); |
| if (identity_strategy_->GetCredentials(&username, &auth_token) && |
| CanBeInManagedDomain(username)) { |
| token_fetcher_->FetchToken(auth_token, device_id, policy_type, |
| machine_id, machine_model); |
| } |
| } |
| |
| void CloudPolicyController::SendPolicyRequest() { |
| backend_.reset(service_->CreateBackend()); |
| DCHECK(!identity_strategy_->GetDeviceToken().empty()); |
| em::DevicePolicyRequest policy_request; |
| em::PolicyFetchRequest* fetch_request = policy_request.add_request(); |
| fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA); |
| fetch_request->set_policy_type(identity_strategy_->GetPolicyType()); |
| 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); |
| |
| backend_->ProcessPolicyRequest(identity_strategy_->GetDeviceToken(), |
| identity_strategy_->GetDeviceID(), |
| policy_request, this); |
| } |
| |
| void CloudPolicyController::DoDelayedWork() { |
| DCHECK(delayed_work_task_); |
| delayed_work_task_ = NULL; |
| DoWork(); |
| } |
| |
| 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::CancelDelayedWork() { |
| if (delayed_work_task_) { |
| delayed_work_task_->Cancel(); |
| delayed_work_task_ = NULL; |
| } |
| } |
| |
| void CloudPolicyController::SetState( |
| CloudPolicyController::ControllerState new_state) { |
| state_ = new_state; |
| backend_.reset(); // Discard any pending requests. |
| |
| base::Time now(base::Time::NowFromSystemTime()); |
| base::Time refresh_at; |
| base::Time last_refresh(cache_->last_policy_refresh_time()); |
| if (last_refresh.is_null()) |
| last_refresh = now; |
| |
| // 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_ = policy_refresh_error_delay_ms_; |
| refresh_at = |
| last_refresh + base::TimeDelta::FromMilliseconds(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. |
| CancelDelayedWork(); |
| if (!refresh_at.is_null()) { |
| int64 delay = std::max<int64>((refresh_at - now).InMilliseconds(), 0); |
| delayed_work_task_ = method_factory_.NewRunnableMethod( |
| &CloudPolicyController::DoDelayedWork); |
| MessageLoop::current()->PostDelayedTask(FROM_HERE, delayed_work_task_, |
| delay); |
| } |
| } |
| |
| int64 CloudPolicyController::GetRefreshDelay() { |
| int64 deviation = (policy_refresh_deviation_factor_percent_ * |
| policy_refresh_rate_ms_) / 100; |
| deviation = std::min(deviation, policy_refresh_deviation_max_ms_); |
| return policy_refresh_rate_ms_ - base::RandGenerator(deviation + 1); |
| } |
| |
| } // namespace policy |