blob: f92b7da61e552e266a8599569e36c71edd75361c [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/user_cloud_policy_store_chromeos.h"
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/policy/proto/cloud_policy.pb.h"
#include "chrome/browser/policy/proto/device_management_local.pb.h"
#include "chrome/browser/policy/user_policy_disk_cache.h"
#include "chrome/browser/policy/user_policy_token_cache.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/session_manager_client.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_auth_util.h"
namespace em = enterprise_management;
namespace policy {
namespace {
// Subdirectory in the user's profile for storing user policies.
const FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Device Management");
// File in the above directory for stroing user policy dmtokens.
const FilePath::CharType kTokenCacheFile[] = FILE_PATH_LITERAL("Token");
// File in the above directory for storing user policy data.
const FilePath::CharType kPolicyCacheFile[] = FILE_PATH_LITERAL("Policy");
} // namespace
// Helper class for loading legacy policy caches.
class LegacyPolicyCacheLoader : public UserPolicyTokenCache::Delegate,
public UserPolicyDiskCache::Delegate {
public:
typedef base::Callback<void(const std::string&,
const std::string&,
CloudPolicyStore::Status,
scoped_ptr<em::PolicyFetchResponse>)> Callback;
LegacyPolicyCacheLoader(const FilePath& token_cache_file,
const FilePath& policy_cache_file);
virtual ~LegacyPolicyCacheLoader();
// Starts loading, and reports the result to |callback| when done.
void Load(const Callback& callback);
// UserPolicyTokenLoader::Delegate:
virtual void OnTokenLoaded(const std::string& token,
const std::string& device_id) OVERRIDE;
// UserPolicyDiskCache::Delegate:
virtual void OnDiskCacheLoaded(
UserPolicyDiskCache::LoadResult result,
const em::CachedCloudPolicyResponse& policy) OVERRIDE;
private:
// Checks whether the load operations from the legacy caches completed. If so,
// fires the appropriate notification.
void CheckLoadFinished();
// Maps a disk cache LoadResult to a CloudPolicyStore::Status.
static CloudPolicyStore::Status TranslateLoadResult(
UserPolicyDiskCache::LoadResult result);
base::WeakPtrFactory<LegacyPolicyCacheLoader> weak_factory_;
scoped_refptr<UserPolicyTokenLoader> token_loader_;
scoped_refptr<UserPolicyDiskCache> policy_cache_;
std::string dm_token_;
std::string device_id_;
bool has_policy_;
scoped_ptr<em::PolicyFetchResponse> policy_;
CloudPolicyStore::Status status_;
Callback callback_;
DISALLOW_COPY_AND_ASSIGN(LegacyPolicyCacheLoader);
};
LegacyPolicyCacheLoader::LegacyPolicyCacheLoader(
const FilePath& token_cache_file,
const FilePath& policy_cache_file)
: ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
has_policy_(false),
status_(CloudPolicyStore::STATUS_OK) {
token_loader_ = new UserPolicyTokenLoader(weak_factory_.GetWeakPtr(),
token_cache_file);
policy_cache_ = new UserPolicyDiskCache(weak_factory_.GetWeakPtr(),
policy_cache_file);
}
LegacyPolicyCacheLoader::~LegacyPolicyCacheLoader() {}
void LegacyPolicyCacheLoader::Load(const Callback& callback) {
callback_ = callback;
token_loader_->Load();
policy_cache_->Load();
}
void LegacyPolicyCacheLoader::OnTokenLoaded(const std::string& token,
const std::string& device_id) {
dm_token_ = token;
device_id_ = device_id;
token_loader_ = NULL;
CheckLoadFinished();
}
void LegacyPolicyCacheLoader::OnDiskCacheLoaded(
UserPolicyDiskCache::LoadResult result,
const em::CachedCloudPolicyResponse& policy) {
status_ = TranslateLoadResult(result);
if (result == UserPolicyDiskCache::LOAD_RESULT_SUCCESS) {
if (policy.has_cloud_policy())
policy_.reset(new em::PolicyFetchResponse(policy.cloud_policy()));
} else {
LOG(WARNING) << "Failed to load legacy policy cache: " << result;
}
policy_cache_ = NULL;
CheckLoadFinished();
}
void LegacyPolicyCacheLoader::CheckLoadFinished() {
if (!token_loader_.get() && !policy_cache_.get())
callback_.Run(dm_token_, device_id_, status_, policy_.Pass());
}
// static
CloudPolicyStore::Status LegacyPolicyCacheLoader::TranslateLoadResult(
UserPolicyDiskCache::LoadResult result) {
switch (result) {
case UserPolicyDiskCache::LOAD_RESULT_SUCCESS:
case UserPolicyDiskCache::LOAD_RESULT_NOT_FOUND:
return CloudPolicyStore::STATUS_OK;
case UserPolicyDiskCache::LOAD_RESULT_PARSE_ERROR:
case UserPolicyDiskCache::LOAD_RESULT_READ_ERROR:
return CloudPolicyStore::STATUS_LOAD_ERROR;
}
NOTREACHED();
return CloudPolicyStore::STATUS_OK;
}
UserCloudPolicyStoreChromeOS::UserCloudPolicyStoreChromeOS(
chromeos::SessionManagerClient* session_manager_client,
const FilePath& legacy_token_cache_file,
const FilePath& legacy_policy_cache_file)
: session_manager_client_(session_manager_client),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
legacy_cache_dir_(legacy_token_cache_file.DirName()),
legacy_loader_(new LegacyPolicyCacheLoader(legacy_token_cache_file,
legacy_policy_cache_file)),
legacy_caches_loaded_(false) {}
UserCloudPolicyStoreChromeOS::~UserCloudPolicyStoreChromeOS() {}
void UserCloudPolicyStoreChromeOS::Store(
const em::PolicyFetchResponse& policy) {
// Cancel all pending requests.
weak_factory_.InvalidateWeakPtrs();
Validate(
scoped_ptr<em::PolicyFetchResponse>(new em::PolicyFetchResponse(policy)),
base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated,
weak_factory_.GetWeakPtr()));
}
void UserCloudPolicyStoreChromeOS::Load() {
// Cancel all pending requests.
weak_factory_.InvalidateWeakPtrs();
session_manager_client_->RetrieveUserPolicy(
base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyRetrieved,
weak_factory_.GetWeakPtr()));
}
void UserCloudPolicyStoreChromeOS::RemoveStoredPolicy() {
// This should never be called on ChromeOS since it is not possible to sign
// out of a Profile. The underlying policy store is only removed if the
// Profile itself is deleted.
NOTREACHED();
}
void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved(
const std::string& policy_blob) {
if (policy_blob.empty()) {
// Policy fetch failed. Try legacy caches if we haven't done that already.
if (!legacy_caches_loaded_ && legacy_loader_.get()) {
legacy_caches_loaded_ = true;
legacy_loader_->Load(
base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished,
weak_factory_.GetWeakPtr()));
} else {
// session_manager doesn't have policy. Adjust internal state and notify
// the world about the policy update.
policy_.reset();
NotifyStoreLoaded();
}
return;
}
// Policy is supplied by session_manager. Disregard legacy data from now on.
legacy_loader_.reset();
scoped_ptr<em::PolicyFetchResponse> policy(new em::PolicyFetchResponse());
if (!policy->ParseFromString(policy_blob)) {
status_ = STATUS_PARSE_ERROR;
NotifyStoreError();
return;
}
Validate(policy.Pass(),
base::Bind(&UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated,
weak_factory_.GetWeakPtr()));
}
void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated(
UserCloudPolicyValidator* validator) {
validation_status_ = validator->status();
if (!validator->success()) {
status_ = STATUS_VALIDATION_ERROR;
NotifyStoreError();
return;
}
InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass());
status_ = STATUS_OK;
// Policy has been loaded successfully. This indicates that new-style policy
// is working, so the legacy cache directory can be removed.
if (!legacy_cache_dir_.empty()) {
content::BrowserThread::PostBlockingPoolTask(
FROM_HERE,
base::Bind(&UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir,
legacy_cache_dir_));
legacy_cache_dir_.clear();
}
NotifyStoreLoaded();
}
void UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated(
UserCloudPolicyValidator* validator) {
validation_status_ = validator->status();
if (!validator->success()) {
status_ = STATUS_VALIDATION_ERROR;
NotifyStoreError();
return;
}
std::string policy_blob;
if (!validator->policy()->SerializeToString(&policy_blob)) {
status_ = STATUS_SERIALIZE_ERROR;
NotifyStoreError();
return;
}
session_manager_client_->StoreUserPolicy(
policy_blob,
base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyStored,
weak_factory_.GetWeakPtr()));
}
void UserCloudPolicyStoreChromeOS::OnPolicyStored(bool success) {
if (!success) {
status_ = STATUS_STORE_ERROR;
NotifyStoreError();
} else {
// TODO(mnissler): Once we do signature verifications, we'll have to reload
// the key at this point to account for key rotations.
Load();
}
}
void UserCloudPolicyStoreChromeOS::Validate(
scoped_ptr<em::PolicyFetchResponse> policy,
const UserCloudPolicyValidator::CompletionCallback& callback) {
// Configure the validator.
scoped_ptr<UserCloudPolicyValidator> validator =
CreateValidator(policy.Pass(), callback);
validator->ValidateUsername(
chromeos::UserManager::Get()->GetLoggedInUser().email());
// TODO(mnissler): Do a signature check here as well. The key is stored by
// session_manager in the root-owned cryptohome area, which is currently
// inaccessible to Chrome though.
// Start validation. The Validator will free itself once validation is
// complete.
validator.release()->StartValidation();
}
void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished(
const std::string& dm_token,
const std::string& device_id,
Status status,
scoped_ptr<em::PolicyFetchResponse> policy) {
status_ = status;
if (policy.get()) {
Validate(policy.Pass(),
base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated,
weak_factory_.GetWeakPtr(),
dm_token, device_id));
} else {
InstallLegacyTokens(dm_token, device_id);
}
}
void UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated(
const std::string& dm_token,
const std::string& device_id,
UserCloudPolicyValidator* validator) {
validation_status_ = validator->status();
if (validator->success()) {
status_ = STATUS_OK;
InstallPolicy(validator->policy_data().Pass(),
validator->payload().Pass());
// Clear the public key version. The public key version field would
// otherwise indicate that we have key installed in the store when in fact
// we haven't. This may result in policy updates failing signature
// verification.
policy_->clear_public_key_version();
} else {
status_ = STATUS_VALIDATION_ERROR;
}
InstallLegacyTokens(dm_token, device_id);
}
void UserCloudPolicyStoreChromeOS::InstallLegacyTokens(
const std::string& dm_token,
const std::string& device_id) {
// Write token and device ID to |policy_|, giving them precedence over the
// policy blob. This is to match the legacy behavior, which used token and
// device id exclusively from the token cache file.
if (!dm_token.empty() && !device_id.empty()) {
if (!policy_.get())
policy_.reset(new em::PolicyData());
policy_->set_request_token(dm_token);
policy_->set_device_id(device_id);
}
// Tell the rest of the world that the policy load completed.
NotifyStoreLoaded();
}
// static
void UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir(const FilePath& dir) {
if (file_util::PathExists(dir) && !file_util::Delete(dir, true))
LOG(ERROR) << "Failed to remove cache dir " << dir.value();
}
// static
scoped_ptr<CloudPolicyStore> CloudPolicyStore::CreateUserPolicyStore(
Profile* profile) {
FilePath profile_dir;
CHECK(PathService::Get(chrome::DIR_USER_DATA, &profile_dir));
CommandLine* command_line = CommandLine::ForCurrentProcess();
const FilePath policy_dir =
profile_dir
.Append(command_line->GetSwitchValuePath(switches::kLoginProfile))
.Append(kPolicyDir);
const FilePath policy_cache_file = policy_dir.Append(kPolicyCacheFile);
const FilePath token_cache_file = policy_dir.Append(kTokenCacheFile);
return scoped_ptr<CloudPolicyStore>(new UserCloudPolicyStoreChromeOS(
chromeos::DBusThreadManager::Get()->GetSessionManagerClient(),
token_cache_file, policy_cache_file));
}
} // namespace policy