// 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_policy_cache.h"
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/task.h"
#include "base/values.h"
#include "chrome/browser/chromeos/cros_settings_names.h"
#include "chrome/browser/chromeos/login/ownership_service.h"
#include "chrome/browser/chromeos/login/signed_settings_helper.h"
#include "chrome/browser/chromeos/user_cros_settings_provider.h"
#include "chrome/browser/policy/configuration_policy_pref_store.h"
#include "chrome/browser/policy/device_policy_identity_strategy.h"
#include "chrome/browser/policy/enterprise_install_attributes.h"
#include "chrome/browser/policy/policy_map.h"
#include "chrome/browser/policy/proto/device_management_backend.pb.h"
#include "chrome/browser/policy/proto/device_management_constants.h"
#include "chrome/browser/policy/proto/device_management_local.pb.h"
#include "content/browser/browser_thread.h"
#include "policy/configuration_policy_type.h"
namespace {
// Stores policy, updates the owner key if required and reports the status
// through a callback.
class StorePolicyOperation : public chromeos::SignedSettingsHelper::Callback,
public chromeos::OwnerManager::KeyUpdateDelegate {
public:
typedef Callback1<chromeos::SignedSettings::ReturnCode>::Type Callback;
StorePolicyOperation(chromeos::SignedSettingsHelper* signed_settings_helper,
const em::PolicyFetchResponse& policy,
Callback* callback)
: signed_settings_helper_(signed_settings_helper),
policy_(policy),
callback_(callback) {
signed_settings_helper_->StartStorePolicyOp(policy, this);
}
virtual ~StorePolicyOperation() {
signed_settings_helper_->CancelCallback(this);
}
// SignedSettingsHelper implementation:
virtual void OnStorePolicyCompleted(
chromeos::SignedSettings::ReturnCode code) OVERRIDE {
if (code != chromeos::SignedSettings::SUCCESS) {
callback_->Run(code);
delete this;
return;
}
if (policy_.has_new_public_key()) {
// The session manager has successfully done a key rotation. Replace the
// owner key also in chrome.
const std::string& new_key = policy_.new_public_key();
const std::vector<uint8> new_key_data(new_key.c_str(),
new_key.c_str() + new_key.size());
chromeos::OwnershipService::GetSharedInstance()->StartUpdateOwnerKey(
new_key_data, this);
return;
} else {
UpdateUserCrosSettings();
callback_->Run(chromeos::SignedSettings::SUCCESS);
delete this;
return;
}
}
// OwnerManager::KeyUpdateDelegate implementation:
virtual void OnKeyUpdated() OVERRIDE {
UpdateUserCrosSettings();
callback_->Run(chromeos::SignedSettings::SUCCESS);
delete this;
}
private:
void UpdateUserCrosSettings() {
// TODO(mnissler): Find a better way. This is a hack that updates the
// UserCrosSettingsProvider's cache, since it is unable to notice we've
// updated policy information.
chromeos::UserCrosSettingsProvider().Reload();
}
chromeos::SignedSettingsHelper* signed_settings_helper_;
em::PolicyFetchResponse policy_;
scoped_ptr<Callback> callback_;
DISALLOW_COPY_AND_ASSIGN(StorePolicyOperation);
};
// Decodes a protobuf integer to an IntegerValue. The caller assumes ownership
// of the return Value*. Returns NULL in case the input value is out of bounds.
Value* DecodeIntegerValue(google::protobuf::int64 value) {
if (value < std::numeric_limits<int>::min() ||
value > std::numeric_limits<int>::max()) {
LOG(WARNING) << "Integer value " << value
<< " out of numeric limits, ignoring.";
return NULL;
}
return Value::CreateIntegerValue(static_cast<int>(value));
}
} // namespace
namespace policy {
DevicePolicyCache::DevicePolicyCache(
DevicePolicyIdentityStrategy* identity_strategy,
EnterpriseInstallAttributes* install_attributes)
: identity_strategy_(identity_strategy),
install_attributes_(install_attributes),
signed_settings_helper_(chromeos::SignedSettingsHelper::Get()),
starting_up_(true),
ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)) {
}
DevicePolicyCache::DevicePolicyCache(
DevicePolicyIdentityStrategy* identity_strategy,
EnterpriseInstallAttributes* install_attributes,
chromeos::SignedSettingsHelper* signed_settings_helper)
: identity_strategy_(identity_strategy),
install_attributes_(install_attributes),
signed_settings_helper_(signed_settings_helper),
starting_up_(true),
ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)) {
}
DevicePolicyCache::~DevicePolicyCache() {
signed_settings_helper_->CancelCallback(this);
}
void DevicePolicyCache::Load() {
signed_settings_helper_->StartRetrievePolicyOp(this);
}
void DevicePolicyCache::SetPolicy(const em::PolicyFetchResponse& policy) {
DCHECK(!starting_up_);
// Make sure we have an enterprise device.
std::string registration_user(install_attributes_->GetRegistrationUser());
if (registration_user.empty()) {
LOG(WARNING) << "Refusing to accept policy on non-enterprise device.";
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::POLICY_LOCAL_ERROR);
return;
}
// Check the user this policy is for against the device-locked name.
em::PolicyData policy_data;
if (!policy_data.ParseFromString(policy.policy_data())) {
LOG(WARNING) << "Invalid policy protobuf";
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::POLICY_LOCAL_ERROR);
return;
}
if (registration_user != policy_data.username()) {
LOG(WARNING) << "Refusing policy blob for " << policy_data.username()
<< " which doesn't match " << registration_user;
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::POLICY_LOCAL_ERROR);
return;
}
set_last_policy_refresh_time(base::Time::NowFromSystemTime());
// Start a store operation.
new StorePolicyOperation(signed_settings_helper_,
policy,
callback_factory_.NewCallback(
&DevicePolicyCache::PolicyStoreOpCompleted));
}
void DevicePolicyCache::SetUnmanaged() {
LOG(WARNING) << "Tried to set DevicePolicyCache to 'unmanaged'!";
// This is not supported for DevicePolicyCache.
}
void DevicePolicyCache::OnRetrievePolicyCompleted(
chromeos::SignedSettings::ReturnCode code,
const em::PolicyFetchResponse& policy) {
DCHECK(CalledOnValidThread());
if (starting_up_) {
starting_up_ = false;
if (code == chromeos::SignedSettings::NOT_FOUND ||
code == chromeos::SignedSettings::KEY_UNAVAILABLE ||
!policy.has_policy_data()) {
InformNotifier(CloudPolicySubsystem::UNENROLLED,
CloudPolicySubsystem::NO_DETAILS);
return;
}
em::PolicyData policy_data;
if (!policy_data.ParseFromString(policy.policy_data())) {
LOG(WARNING) << "Failed to parse PolicyData protobuf.";
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::POLICY_LOCAL_ERROR);
return;
}
if (!policy_data.has_request_token() ||
policy_data.request_token().empty()) {
SetUnmanagedInternal(base::Time::NowFromSystemTime());
InformNotifier(CloudPolicySubsystem::UNMANAGED,
CloudPolicySubsystem::NO_DETAILS);
// TODO(jkummerow): Reminder: When we want to feed device-wide settings
// made by a local owner into this cache, we need to call
// SetPolicyInternal() here.
return;
}
if (!policy_data.has_username() || !policy_data.has_device_id()) {
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::POLICY_LOCAL_ERROR);
return;
}
identity_strategy_->SetDeviceManagementCredentials(
policy_data.username(),
policy_data.device_id(),
policy_data.request_token());
SetPolicyInternal(policy, NULL, false);
} else { // In other words, starting_up_ == false.
if (code != chromeos::SignedSettings::SUCCESS) {
if (code == chromeos::SignedSettings::BAD_SIGNATURE) {
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::SIGNATURE_MISMATCH);
} else {
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::POLICY_LOCAL_ERROR);
}
return;
}
SetPolicyInternal(policy, NULL, false);
}
}
bool DevicePolicyCache::DecodePolicyData(const em::PolicyData& policy_data,
PolicyMap* mandatory,
PolicyMap* recommended) {
em::ChromeDeviceSettingsProto policy;
if (!policy.ParseFromString(policy_data.policy_value())) {
LOG(WARNING) << "Failed to parse ChromeDeviceSettingsProto.";
return false;
}
DecodeDevicePolicy(policy, mandatory, recommended);
return true;
}
void DevicePolicyCache::PolicyStoreOpCompleted(
chromeos::SignedSettings::ReturnCode code) {
DCHECK(CalledOnValidThread());
if (code != chromeos::SignedSettings::SUCCESS) {
if (code == chromeos::SignedSettings::BAD_SIGNATURE) {
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::SIGNATURE_MISMATCH);
} else {
InformNotifier(CloudPolicySubsystem::LOCAL_ERROR,
CloudPolicySubsystem::POLICY_LOCAL_ERROR);
}
return;
}
signed_settings_helper_->StartRetrievePolicyOp(this);
}
// static
void DevicePolicyCache::DecodeDevicePolicy(
const em::ChromeDeviceSettingsProto& policy,
PolicyMap* mandatory,
PolicyMap* recommended) {
if (policy.has_policy_refresh_rate()) {
const em::DevicePolicyRefreshRateProto container =
policy.policy_refresh_rate();
if (container.has_policy_refresh_rate()) {
mandatory->Set(kPolicyPolicyRefreshRate,
DecodeIntegerValue(container.policy_refresh_rate()));
}
}
if (policy.has_device_proxy_settings()) {
const em::DeviceProxySettingsProto container =
policy.device_proxy_settings();
if (container.has_proxy_mode()) {
recommended->Set(kPolicyProxyMode,
Value::CreateStringValue(container.proxy_mode()));
}
if (container.has_proxy_server()) {
recommended->Set(kPolicyProxyServer,
Value::CreateStringValue(container.proxy_server()));
}
if (container.has_proxy_pac_url()) {
recommended->Set(kPolicyProxyPacUrl,
Value::CreateStringValue(container.proxy_pac_url()));
}
if (container.has_proxy_bypass_list()) {
recommended->Set(kPolicyProxyBypassList,
Value::CreateStringValue(container.proxy_bypass_list()));
}
}
}
} // namespace policy