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..b9d9a77e582a0ea7af472974fd3d5bcb78529afe |
--- /dev/null |
+++ b/chrome/browser/policy/cros_user_policy_cache.cc |
@@ -0,0 +1,496 @@ |
+// 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/policy/cros_user_policy_identity_strategy.h" |
+#include "chrome/browser/policy/proto/cloud_policy.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"; |
+ |
+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)); |
gfeher
2011/06/22 12:41:31
How about collapsing these arguments into fewer li
Mattias Nissler (ping if slow)
2011/06/22 17:17:35
Done.
|
+} |
+ |
+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]), |
gfeher
2011/06/22 12:41:31
Nit: indent.
Mattias Nissler (ping if slow)
2011/06/22 17:17:35
Done.
|
+ 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; |
+ |
+ StorePolicyOperation(const em::PolicyFetchResponse& policy, |
+ chromeos::LoginLibrary* login_library, |
+ const StatusCallback& callback); |
+ |
+ // Executes the operation. |
+ void Run(); |
+ |
+ // Cancels the operation, making sure that any pending callbacks get killed. |
+ void Cancel(); |
+ |
+ const em::PolicyFetchResponse& policy() { return policy_; } |
+ |
+ private: |
+ // A callback function suitable for passing to login_library. |
+ static void StorePolicyCallback(void* delegate, bool result); |
+ |
+ em::PolicyFetchResponse policy_; |
+ chromeos::LoginLibrary* login_library_; |
+ StatusCallback callback_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(StorePolicyOperation); |
+}; |
+ |
+CrosUserPolicyCache::StorePolicyOperation::StorePolicyOperation( |
+ const em::PolicyFetchResponse& policy, |
+ chromeos::LoginLibrary* login_library, |
+ const StatusCallback& callback) |
+ : policy_(policy), |
+ login_library_(login_library), |
+ callback_(callback) {} |
+ |
+void CrosUserPolicyCache::StorePolicyOperation::Run() { |
+ std::string serialized; |
+ if (!policy_.SerializeToString(&serialized)) { |
+ LOG(ERROR) << "Failed to serialize policy protobuf!"; |
+ callback_.Run(false); |
+ delete this; |
gfeher
2011/06/22 12:41:31
Here and all the similar cases: please make sure t
Mattias Nissler (ping if slow)
2011/06/22 17:17:35
Done.
|
+ return; |
+ } |
+ login_library_->RequestStoreUserPolicy(serialized, StorePolicyCallback, this); |
+} |
+ |
+void CrosUserPolicyCache::StorePolicyOperation::Cancel() { |
gfeher
2011/06/22 12:41:31
delete this?
Mattias Nissler (ping if slow)
2011/06/22 17:17:35
Actually no, since the operation may be in flight.
|
+ callback_.Reset(); |
+} |
+ |
+// static |
+void CrosUserPolicyCache::StorePolicyOperation::StorePolicyCallback( |
+ void* delegate, |
+ bool result) { |
+ scoped_ptr<StorePolicyOperation> op( |
+ static_cast<StorePolicyOperation*>(delegate)); |
+ if (!op->callback_.is_null()) |
+ op->callback_.Run(result); |
+} |
+ |
+class CrosUserPolicyCache::RetrievePolicyOperation { |
+ public: |
+ typedef base::Callback<void(bool, const em::PolicyFetchResponse&)> |
+ ResultCallback; |
+ |
+ RetrievePolicyOperation(PolicyKey* key, |
+ chromeos::LoginLibrary* login_library, |
+ bool reload_key, |
+ const ResultCallback& callback); |
+ |
+ // Executes the operation. |
+ void Run(); |
+ |
+ // Cancels the operation, disengaging all pending callbacks. |
+ void Cancel(); |
+ |
+ private: |
+ // 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_; |
+ chromeos::LoginLibrary* login_library_; |
+ bool reload_key_; |
+ ResultCallback callback_; |
+ em::PolicyFetchResponse policy_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(RetrievePolicyOperation); |
+}; |
+ |
+CrosUserPolicyCache::RetrievePolicyOperation::RetrievePolicyOperation( |
+ PolicyKey* key, |
+ chromeos::LoginLibrary* login_library, |
+ bool reload_key, |
+ const ResultCallback& callback) |
+ : key_(key), |
+ login_library_(login_library), |
+ reload_key_(reload_key), |
+ callback_(callback) {} |
+ |
+void CrosUserPolicyCache::RetrievePolicyOperation::Run() { |
+ login_library_->RequestRetrieveUserPolicy(RetrievePolicyCallback, this); |
+} |
+ |
+void CrosUserPolicyCache::RetrievePolicyOperation::Cancel() { |
gfeher
2011/06/22 12:41:31
delete this?
Mattias Nissler (ping if slow)
2011/06/22 17:17:35
See above.
|
+ callback_.Reset(); |
+} |
+ |
+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) { |
+ scoped_ptr<RetrievePolicyOperation> scoped_killer(this); |
gfeher
2011/06/22 12:41:31
Please also try and implement this in a way that t
Mattias Nissler (ping if slow)
2011/06/22 17:17:35
Done.
|
+ if (!callback_.is_null()) |
+ callback_.Run(status, policy_); |
+} |
+ |
+// 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, |
+ CrosUserPolicyIdentityStrategy* identity_strategy, |
+ const FilePath& legacy_cache_file) |
+ : key_(new PolicyKey(FilePath(kUserPolicyKeyFile))), |
+ login_library_(login_library), |
+ identity_strategy_(identity_strategy), |
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), |
+ legacy_cache_file_(legacy_cache_file), |
+ store_operation_(NULL), |
+ retrieve_operation_(NULL) { |
+ legacy_token_cache_ = new UserPolicyTokenCache(weak_ptr_factory_.GetWeakPtr(), |
+ legacy_cache_file); |
+} |
+ |
+CrosUserPolicyCache::~CrosUserPolicyCache() { |
+ CancelStore(); |
+ CancelRetrieve(); |
+} |
+ |
+void CrosUserPolicyCache::Load() { |
+ key_->LoadKey(); |
+ retrieve_operation_ = |
+ new RetrievePolicyOperation( |
+ key_, |
+ login_library_, |
+ false, |
+ base::Bind(&CrosUserPolicyCache::OnPolicyLoadDone, |
+ base::Unretained(this))); |
+ retrieve_operation_->Run(); |
+} |
+ |
+void CrosUserPolicyCache::SetPolicy(const em::PolicyFetchResponse& policy) { |
+ CancelStore(); |
+ set_last_policy_refresh_time(base::Time::NowFromSystemTime()); |
+ store_operation_ = |
+ new StorePolicyOperation(policy, |
+ login_library_, |
+ base::Bind(&CrosUserPolicyCache::OnPolicyStored, |
+ base::Unretained(this))); |
+ store_operation_->Run(); |
+} |
+ |
+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(identity_strategy_->GetPolicyType()); |
+ 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::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::OnTokenCacheLoaded(const std::string& token, |
+ const std::string& device_id) { |
+ if (!token.empty() && !device_id.empty()) { |
+ identity_strategy_->SetDeviceCredentials(device_id, token); |
+ BrowserThread::PostTask(BrowserThread::FILE, |
+ FROM_HERE, |
+ NewRunnableFunction(&RemoveLegacyCacheDir, |
+ legacy_cache_file_.DirName())); |
+ } else { |
+ identity_strategy_->EnableRegistration(); |
+ } |
+} |
+ |
+void CrosUserPolicyCache::OnPolicyStored(bool result) { |
+ DCHECK(store_operation_); |
+ if (result) { |
gfeher
2011/06/22 12:41:31
So after storing a policy you immediately retrieve
Mattias Nissler (ping if slow)
2011/06/22 17:17:35
Oh, the idea is to only have one read path and use
|
+ CancelRetrieve(); |
+ retrieve_operation_ = |
+ new RetrievePolicyOperation( |
+ key_, |
+ login_library_, |
+ store_operation_->policy().has_new_public_key(), |
+ base::Bind(&CrosUserPolicyCache::OnPolicyReloadDone, |
+ base::Unretained(this))); |
+ retrieve_operation_->Run(); |
+ } 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 cache. |
+ legacy_token_cache_->Load(); |
gfeher
2011/06/22 12:41:31
Do we get a SetPolicy callback if this succeeds?
Mattias Nissler (ping if slow)
2011/06/22 17:17:35
No, we just pull down policy again. I'm not trying
gfeher
2011/06/22 22:18:17
I was formulating my arguments against wasting tim
|
+ 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); |
+ identity_strategy_->EnableRegistration(); |
+ 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); |
+ identity_strategy_->EnableRegistration(); |
+ return; |
+ } |
+ identity_strategy_->SetDeviceCredentials(policy_data.device_id(), |
+ policy_data.request_token()); |
+ SetPolicyInternal(policy, NULL, true); |
+} |
+ |
+void CrosUserPolicyCache::OnPolicyReloadDone( |
+ bool result, |
+ const em::PolicyFetchResponse& policy) { |
+ DCHECK(retrieve_operation_); |
+ CancelRetrieve(); |
+ if (result) { |
+ SetPolicyInternal(policy, NULL, false); |
+ } 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 |