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..a77ada530ac9665e6cbdab174733a52158f78132 |
--- /dev/null |
+++ b/chrome/browser/policy/cros_user_policy_cache.cc |
@@ -0,0 +1,556 @@ |
+// 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 "base/path_service.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/chrome_paths.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[] = FILE_PATH_LITERAL("key"); |
+ |
+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. |
gfeher
2011/07/21 23:37:15
This doesn't really cancel the operation, just kil
Mattias Nissler (ping if slow)
2011/07/22 11:29:48
Done.
|
+ 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 { |
+ 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. |
gfeher
2011/07/21 23:37:15
Same here.
Mattias Nissler (ping if slow)
2011/07/22 11:29:48
Done.
|
+ 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) |
+ : 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()); |
+ |
+ FilePath key_file; |
+ if (PathService::Get(chrome::DIR_USER_POLICY, &key_file)) |
+ key_file.Append(kUserPolicyKeyFile); |
+ else |
+ NOTREACHED(); |
+ key_ = new PolicyKey(key_file); |
+ |
+ 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()); |
gfeher
2011/07/21 23:37:15
I don't think it makes sense to stuff that into th
Mattias Nissler (ping if slow)
2011/07/22 11:29:48
Makes sense :) I'll keep it in mind when doing mor
|
+ 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 |