Chromium Code Reviews| Index: chrome/browser/policy/cros_user_policy_cache.cc |
| diff --git a/chrome/browser/policy/cros_user_policy_cache.cc b/chrome/browser/policy/cros_user_policy_cache.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..752dd7c5d7e3f2b236a11c5ef4f492f00dd4616e |
| --- /dev/null |
| +++ b/chrome/browser/policy/cros_user_policy_cache.cc |
| @@ -0,0 +1,548 @@ |
| +// 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/cros_user_policy_cache.h" |
| + |
| +#include <string> |
| +#include <vector> |
| + |
| +#include "base/bind.h" |
| +#include "base/callback.h" |
| +#include "base/file_path.h" |
| +#include "base/file_util.h" |
| +#include "chrome/browser/chromeos/cros/login_library.h" |
| +#include "chrome/browser/policy/proto/cloud_policy.pb.h" |
| +#include "chrome/browser/policy/proto/device_management_backend.pb.h" |
| +#include "chrome/browser/policy/proto/device_management_local.pb.h" |
| +#include "chrome/common/extensions/extension_constants.h" |
| +#include "content/browser/browser_thread.h" |
| +#include "crypto/signature_verifier.h" |
| + |
| +using extension_misc::kSignatureAlgorithm; |
| + |
| +static const FilePath::CharType kUserPolicyKeyFile[] = |
| + "/home/chronos/user/.enterprise/key"; |
|
gfeher
2011/07/21 12:24:23
How about moving this to chrome/common/chrome_path
Mattias Nissler (ping if slow)
2011/07/21 14:20:25
Done.
|
| + |
| +namespace policy { |
| + |
| +// Decodes a CloudPolicySettings object into two maps with mandatory and |
| +// recommended settings, respectively. The implementation is generated code |
| +// in policy/cloud_policy_generated.cc. |
| +void DecodePolicy(const em::CloudPolicySettings& policy, |
| + PolicyMap* mandatory, PolicyMap* recommended); |
| + |
| +// Manages the policy key and implements signature checking on the key. |
| +class CrosUserPolicyCache::PolicyKey |
| + : public base::RefCountedThreadSafe<PolicyKey> { |
| + public: |
| + typedef base::Callback<void(bool)> StatusCallback; |
| + |
| + explicit PolicyKey(const FilePath& key_file); |
| + |
| + // Loads the key asynchronously on the file thread. |
| + void LoadKey(); |
| + |
| + // Checks whether |signature| is a valid over |data|. The status will be |
| + // reported through |callback| once the check is complete. If the |reload_key| |
| + // flag is set, the key is first reloaded from the file. |
| + void CheckSignature(const std::string& data, |
| + const std::string& signature, |
| + bool reload_key, |
| + const StatusCallback& callback); |
| + |
| + private: |
| + // Reports the result of a signature check. Must be called on the UI thread. |
| + void ReportResult(const StatusCallback& callback, bool result); |
| + |
| + // Read the public key from |key_file_|. Must be called on the file thread. |
| + void ReadKeyFromFile(); |
| + |
| + // Runs the actual signature checking. Must be called on the file thread. |
| + void CheckSignatureOnFileThread(const std::string& data, |
| + const std::string& signature, |
| + bool reload_key, |
| + const StatusCallback& callback); |
| + |
| + // The data members should only be accessed from the file thread. |
| + const FilePath key_file_; |
| + std::vector<uint8> public_key_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(PolicyKey); |
| +}; |
| + |
| +CrosUserPolicyCache::PolicyKey::PolicyKey(const FilePath& key_file) |
| + : key_file_(key_file) {} |
| + |
| +void CrosUserPolicyCache::PolicyKey::LoadKey() { |
| + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| + NewRunnableMethod(this, &PolicyKey::ReadKeyFromFile)); |
| +} |
| + |
| +void CrosUserPolicyCache::PolicyKey::CheckSignature( |
| + const std::string& data, |
| + const std::string& signature, |
| + bool reload_key, |
| + const StatusCallback& callback) { |
| + BrowserThread::PostTask( |
| + BrowserThread::FILE, FROM_HERE, |
| + NewRunnableMethod(this, &PolicyKey::CheckSignatureOnFileThread, |
| + data, signature, reload_key, callback)); |
| +} |
| + |
| +void CrosUserPolicyCache::PolicyKey::CheckSignatureOnFileThread( |
| + const std::string& data, |
| + const std::string& signature, |
| + bool reload_key, |
| + const StatusCallback& callback) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + bool status = false; |
| + |
| + // Re-read the key if necessary. |
| + if (public_key_.empty() || reload_key) |
| + ReadKeyFromFile(); |
| + |
| + if (!public_key_.empty()) { |
| + crypto::SignatureVerifier verifier; |
| + if (verifier.VerifyInit(kSignatureAlgorithm, sizeof(kSignatureAlgorithm), |
| + reinterpret_cast<const uint8*>(&signature[0]), |
| + signature.size(), |
| + &public_key_[0], public_key_.size())) { |
| + verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()), |
| + data.length()); |
| + status = verifier.VerifyFinal(); |
| + } |
| + } |
| + |
| + BrowserThread::PostTask(BrowserThread::UI, |
| + FROM_HERE, |
| + NewRunnableMethod(this, &PolicyKey::ReportResult, |
| + callback, status)); |
| +} |
| + |
| +void CrosUserPolicyCache::PolicyKey::ReportResult( |
| + const StatusCallback& callback, |
| + bool result) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + callback.Run(result); |
| +} |
| + |
| +void CrosUserPolicyCache::PolicyKey::ReadKeyFromFile() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + |
| + public_key_.clear(); |
| + if (!file_util::PathExists(key_file_)) |
| + return; |
| + |
| + int64 size = -1; |
| + if (!file_util::GetFileSize(key_file_, &size)) { |
| + LOG(ERROR) << "Failed to get file size for " << key_file_.value(); |
| + return; |
| + } |
| + |
| + const int64 kMaxSize = 1 * 1024 * 1024; // 1M should be sufficient. |
| + if (size < 0 || size > kMaxSize) { |
| + LOG(ERROR) << "Bogus policy key file size " << size; |
| + return; |
| + } |
| + |
| + std::vector<uint8> key_data(size); |
| + if (!file_util::ReadFile(key_file_, |
| + reinterpret_cast<char*>(&key_data[0]), |
| + key_data.size())) { |
| + LOG(ERROR) << "Failed to read " << key_file_.value(); |
| + return; |
| + } |
| + |
| + public_key_.swap(key_data); |
| +} |
| + |
| +// Takes care of sending a new policy blob to session manager and reports back |
| +// the status through a callback. |
| +class CrosUserPolicyCache::StorePolicyOperation { |
| + public: |
| + typedef base::Callback<void(bool)> StatusCallback; |
| + |
| + // Creates and exectues an operation. |
| + static StorePolicyOperation* Run(const em::PolicyFetchResponse& policy, |
| + chromeos::LoginLibrary* login_library, |
| + const StatusCallback& callback); |
| + |
| + // Cancels the operation, making sure that any pending callbacks get killed. |
| + void Cancel(); |
| + |
| + const em::PolicyFetchResponse& policy() { return policy_; } |
| + |
| + private: |
| + StorePolicyOperation(const em::PolicyFetchResponse& policy, |
| + const StatusCallback& callback); |
| + |
| + // StorePolicyOperation manages its own lifetime. |
| + ~StorePolicyOperation() {} |
| + |
| + // A callback function suitable for passing to login_library. |
| + static void StorePolicyCallback(void* delegate, bool result); |
| + |
| + em::PolicyFetchResponse policy_; |
| + StatusCallback callback_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(StorePolicyOperation); |
| +}; |
| + |
| +// static |
| +CrosUserPolicyCache::StorePolicyOperation* |
| + CrosUserPolicyCache::StorePolicyOperation::Run( |
| + const em::PolicyFetchResponse& policy, |
| + chromeos::LoginLibrary* login_library, |
| + const StatusCallback& callback) { |
| + StorePolicyOperation* op = new StorePolicyOperation(policy, callback); |
| + std::string serialized; |
| + if (!policy.SerializeToString(&serialized)) { |
| + LOG(ERROR) << "Failed to serialize policy protobuf!"; |
| + callback.Run(false); |
| + delete op; |
| + return NULL; |
| + } |
| + login_library->RequestStoreUserPolicy(serialized, StorePolicyCallback, op); |
| + return op; |
| +} |
| + |
| +void CrosUserPolicyCache::StorePolicyOperation::Cancel() { |
| + callback_.Reset(); |
| +} |
| + |
| +CrosUserPolicyCache::StorePolicyOperation::StorePolicyOperation( |
| + const em::PolicyFetchResponse& policy, |
| + const StatusCallback& callback) |
| + : policy_(policy), |
| + callback_(callback) {} |
| + |
| +// static |
| +void CrosUserPolicyCache::StorePolicyOperation::StorePolicyCallback( |
| + void* delegate, |
| + bool result) { |
| + StorePolicyOperation* op(static_cast<StorePolicyOperation*>(delegate)); |
| + if (!op->callback_.is_null()) |
| + op->callback_.Run(result); |
| + delete op; |
| +} |
| + |
| +class CrosUserPolicyCache::RetrievePolicyOperation { |
|
gfeher
2011/07/21 12:24:23
I took a look at DevicePolicyCache and it seems li
Mattias Nissler (ping if slow)
2011/07/21 14:20:25
I thought about that but rejected that idea for se
|
| + public: |
| + typedef base::Callback<void(bool, const em::PolicyFetchResponse&)> |
| + ResultCallback; |
| + |
| + // Creates and executes an operation. |
| + static RetrievePolicyOperation* Run(PolicyKey* key, |
| + chromeos::LoginLibrary* login_library, |
| + bool reload_key, |
| + const ResultCallback& callback); |
| + |
| + // Cancels the operation, disengaging all pending callbacks. |
| + void Cancel(); |
| + |
| + private: |
| + RetrievePolicyOperation(PolicyKey* key, |
| + bool reload_key, |
| + const ResultCallback& callback); |
| + |
| + // RetrievePolicyOperation manages its own lifetime. |
| + ~RetrievePolicyOperation() {} |
| + |
| + // Decodes the policy data and triggers a signature check. |
| + void OnPolicyRetrieved(const char* data, unsigned int size); |
| + |
| + // Handles the signature verification callback and reports the result. |
| + void OnSignatureChecked(bool result); |
| + |
| + // Finishes the operation, makes the |callback_|, and deletes |this|. |
| + void Finish(bool status); |
| + |
| + // A callback function suitable for passing to login_library. |
| + static void RetrievePolicyCallback(void* delegate, |
| + const char* data, |
| + unsigned int size); |
| + scoped_refptr<PolicyKey> key_; |
| + bool reload_key_; |
| + ResultCallback callback_; |
| + em::PolicyFetchResponse policy_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(RetrievePolicyOperation); |
| +}; |
| + |
| +// static |
| +CrosUserPolicyCache::RetrievePolicyOperation* |
| + CrosUserPolicyCache::RetrievePolicyOperation::Run( |
| + PolicyKey* key, |
| + chromeos::LoginLibrary* login_library, |
| + bool reload_key, |
| + const ResultCallback& callback) { |
| + RetrievePolicyOperation* op = |
| + new RetrievePolicyOperation(key, reload_key, callback); |
| + login_library->RequestRetrieveUserPolicy(RetrievePolicyCallback, op); |
| + return op; |
| +} |
| + |
| +void CrosUserPolicyCache::RetrievePolicyOperation::Cancel() { |
| + callback_.Reset(); |
| +} |
| + |
| +CrosUserPolicyCache::RetrievePolicyOperation::RetrievePolicyOperation( |
| + PolicyKey* key, |
| + bool reload_key, |
| + const ResultCallback& callback) |
| + : key_(key), |
| + reload_key_(reload_key), |
| + callback_(callback) {} |
| + |
| +void CrosUserPolicyCache::RetrievePolicyOperation::OnPolicyRetrieved( |
| + const char* data, |
| + unsigned int size) { |
| + if (!policy_.ParseFromArray(data, size) || |
| + !policy_.has_policy_data() || |
| + !policy_.has_policy_data_signature()) { |
| + LOG(ERROR) << "Failed to decode policy"; |
| + Finish(false); |
| + return; |
| + } |
| + key_->CheckSignature(policy_.policy_data(), |
| + policy_.policy_data_signature(), |
| + reload_key_, |
| + base::Bind(&RetrievePolicyOperation::OnSignatureChecked, |
| + base::Unretained(this))); |
| +} |
| + |
| +void CrosUserPolicyCache::RetrievePolicyOperation::OnSignatureChecked( |
| + bool result) { |
| + if (!result) |
| + LOG(ERROR) << "User policy signature check failed."; |
| + Finish(result); |
| +} |
| + |
| +void CrosUserPolicyCache::RetrievePolicyOperation::Finish(bool status) { |
| + if (!callback_.is_null()) |
| + callback_.Run(status, policy_); |
| + delete this; |
| +} |
| + |
| +// static |
| +void CrosUserPolicyCache::RetrievePolicyOperation::RetrievePolicyCallback( |
| + void *delegate, |
| + const char* data, |
| + unsigned int size) { |
| + static_cast<RetrievePolicyOperation*>(delegate)->OnPolicyRetrieved(data, |
| + size); |
| +} |
| + |
| +CrosUserPolicyCache::CrosUserPolicyCache( |
| + chromeos::LoginLibrary* login_library, |
| + CloudPolicyDataStore* data_store, |
| + const FilePath& legacy_token_cache_file, |
| + const FilePath& legacy_policy_cache_file) |
| + : key_(new PolicyKey(FilePath(kUserPolicyKeyFile))), |
| + login_library_(login_library), |
| + data_store_(data_store), |
| + policy_cache_loaded_(false), |
| + store_operation_(NULL), |
| + retrieve_operation_(NULL), |
| + legacy_cache_dir_(legacy_token_cache_file.DirName()), |
| + ALLOW_THIS_IN_INITIALIZER_LIST( |
| + legacy_token_cache_delegate_factory_(this)), |
| + ALLOW_THIS_IN_INITIALIZER_LIST( |
| + legacy_policy_cache_delegate_factory_(this)) { |
| + DCHECK_EQ(legacy_token_cache_file.DirName().value(), |
| + legacy_policy_cache_file.DirName().value()); |
| + legacy_token_loader_ = |
| + new UserPolicyTokenLoader( |
| + legacy_token_cache_delegate_factory_.GetWeakPtr(), |
| + legacy_token_cache_file); |
| + legacy_policy_cache_ = |
| + new UserPolicyDiskCache( |
| + legacy_policy_cache_delegate_factory_.GetWeakPtr(), |
| + legacy_policy_cache_file); |
| +} |
| + |
| +CrosUserPolicyCache::~CrosUserPolicyCache() { |
| + CancelStore(); |
| + CancelRetrieve(); |
| +} |
| + |
| +void CrosUserPolicyCache::Load() { |
| + key_->LoadKey(); |
| + retrieve_operation_ = |
| + RetrievePolicyOperation::Run( |
| + key_, |
| + login_library_, |
| + false, |
| + base::Bind(&CrosUserPolicyCache::OnPolicyLoadDone, |
| + base::Unretained(this))); |
| +} |
| + |
| +void CrosUserPolicyCache::SetPolicy(const em::PolicyFetchResponse& policy) { |
| + CancelStore(); |
| + set_last_policy_refresh_time(base::Time::NowFromSystemTime()); |
| + store_operation_ = |
| + StorePolicyOperation::Run(policy, |
| + login_library_, |
| + base::Bind(&CrosUserPolicyCache::OnPolicyStored, |
| + base::Unretained(this))); |
| +} |
| + |
| +void CrosUserPolicyCache::SetUnmanaged() { |
| + base::Time now(base::Time::NowFromSystemTime()); |
| + SetUnmanagedInternal(now); |
| + |
| + // Construct a policy blob with unmanaged state. |
| + em::PolicyData policy_data; |
| + policy_data.set_policy_type(data_store_->policy_type()); |
| + policy_data.set_timestamp((now - base::Time::UnixEpoch()).InMilliseconds()); |
| + policy_data.set_state(em::PolicyData::UNMANAGED); |
| + |
| + em::PolicyFetchResponse policy; |
| + if (!policy_data.SerializeToString(policy.mutable_policy_data())) { |
| + LOG(ERROR) << "Failed to serialize policy_data"; |
| + return; |
| + } |
| + |
| + SetPolicy(policy); |
| +} |
| + |
| +bool CrosUserPolicyCache::IsReady() { |
| + return policy_cache_loaded_; |
| +} |
| + |
| +bool CrosUserPolicyCache::DecodePolicyData(const em::PolicyData& policy_data, |
| + PolicyMap* mandatory, |
| + PolicyMap* recommended) { |
| + em::CloudPolicySettings policy; |
| + if (!policy.ParseFromString(policy_data.policy_value())) { |
| + LOG(WARNING) << "Failed to parse CloudPolicySettings protobuf."; |
| + return false; |
| + } |
| + DecodePolicy(policy, mandatory, recommended); |
| + return true; |
| +} |
| + |
| +void CrosUserPolicyCache::OnTokenLoaded(const std::string& token, |
| + const std::string& device_id) { |
| + data_store_->set_device_id(device_id); |
| + data_store_->SetDeviceToken(token, true); |
| +} |
| + |
| +void CrosUserPolicyCache::OnDiskCacheLoaded( |
| + const em::CachedCloudPolicyResponse& policy) { |
| + policy_cache_loaded_ = true; |
| + if (policy.unmanaged()) { |
| + SetUnmanagedInternal(base::Time::FromTimeT(policy.timestamp())); |
| + } else if (policy.has_cloud_policy()) { |
| + base::Time timestamp; |
| + if (SetPolicyInternal(policy.cloud_policy(), ×tamp, true)) |
| + set_last_policy_refresh_time(timestamp); |
| + } |
| +} |
| + |
| +void CrosUserPolicyCache::OnPolicyStored(bool result) { |
| + DCHECK(store_operation_); |
| + if (result) { |
| + // Policy is stored successfully, reload from session_manager and apply. |
| + // This helps us making sure we only use policy that session_manager has |
| + // checked and confirmed to be good. |
| + CancelRetrieve(); |
| + retrieve_operation_ = |
| + RetrievePolicyOperation::Run( |
| + key_, |
| + login_library_, |
| + store_operation_->policy().has_new_public_key(), |
| + base::Bind(&CrosUserPolicyCache::OnPolicyReloadDone, |
| + base::Unretained(this))); |
| + |
| + // Now that we have installed the new policy blob, we can make sure that the |
| + // old cache directory is removed. |
| + if (!legacy_cache_dir_.empty()) { |
| + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| + NewRunnableFunction(&RemoveLegacyCacheDir, |
| + legacy_cache_dir_)); |
| + } |
| + } else { |
| + InformNotifier(CloudPolicySubsystem::LOCAL_ERROR, |
| + CloudPolicySubsystem::POLICY_LOCAL_ERROR); |
| + } |
| + CancelStore(); |
| +} |
| + |
| +void CrosUserPolicyCache::OnPolicyLoadDone( |
| + bool result, |
| + const em::PolicyFetchResponse& policy) { |
| + DCHECK(retrieve_operation_); |
| + CancelRetrieve(); |
| + if (!result) { |
| + // No policy present. Try to load the legacy token and policy cache. |
| + legacy_token_loader_->Load(); |
| + legacy_policy_cache_->Load(); |
| + return; |
| + } |
| + |
| + policy_cache_loaded_ = true; |
| + |
| + // We have new-style policy, no need to clean up. |
| + legacy_cache_dir_.clear(); |
| + |
| + 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); |
| + data_store_->SetDeviceToken(std::string(), true); |
| + return; |
| + } |
| + if (policy_data.request_token().empty() || |
| + policy_data.username().empty() || |
| + policy_data.device_id().empty()) { |
| + LOG(WARNING) << "Policy protobuf is missing credentials"; |
| + InformNotifier(CloudPolicySubsystem::LOCAL_ERROR, |
| + CloudPolicySubsystem::POLICY_LOCAL_ERROR); |
| + data_store_->SetDeviceToken(std::string(), true); |
| + return; |
| + } |
| + data_store_->set_device_id(policy_data.device_id()); |
| + data_store_->SetDeviceToken(policy_data.request_token(), true); |
| + if (SetPolicyInternal(policy, NULL, true)) |
| + set_last_policy_refresh_time(base::Time::NowFromSystemTime()); |
| +} |
| + |
| +void CrosUserPolicyCache::OnPolicyReloadDone( |
| + bool result, |
| + const em::PolicyFetchResponse& policy) { |
| + DCHECK(retrieve_operation_); |
| + policy_cache_loaded_ = true; |
| + CancelRetrieve(); |
| + if (result) { |
| + if (SetPolicyInternal(policy, NULL, false)) |
| + set_last_policy_refresh_time(base::Time::NowFromSystemTime()); |
| + } else { |
| + InformNotifier(CloudPolicySubsystem::LOCAL_ERROR, |
| + CloudPolicySubsystem::POLICY_LOCAL_ERROR); |
| + } |
| +} |
| + |
| +void CrosUserPolicyCache::CancelStore() { |
| + if (store_operation_) { |
| + store_operation_->Cancel(); |
| + store_operation_ = NULL; |
| + } |
| +} |
| + |
| +void CrosUserPolicyCache::CancelRetrieve() { |
| + if (retrieve_operation_) { |
| + retrieve_operation_->Cancel(); |
| + retrieve_operation_ = NULL; |
| + } |
| +} |
| + |
| +// static |
| +void CrosUserPolicyCache::RemoveLegacyCacheDir(const FilePath& dir) { |
| + if (!file_util::Delete(dir, true)) |
| + PLOG(ERROR) << "Failed to remove " << dir.value(); |
| +} |
| + |
| +} // namespace policy |