| // Copyright 2013 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/chromeos/policy/cloud_external_data_manager_base.h" |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/chromeos/policy/cloud_external_data_store.h" |
| #include "chrome/browser/policy/cloud/cloud_policy_store.h" |
| #include "chrome/browser/policy/cloud/external_policy_data_fetcher.h" |
| #include "chrome/browser/policy/cloud/external_policy_data_updater.h" |
| #include "chrome/browser/policy/external_data_fetcher.h" |
| #include "chrome/browser/policy/policy_map.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "policy/policy_constants.h" |
| |
| namespace policy { |
| |
| namespace { |
| |
| // Fetch data for at most two external data references at the same time. |
| const int kMaxParallelFetches = 2; |
| |
| // Allows policies to reference |max_external_data_size_for_testing| bytes of |
| // external data even if no |max_size| was specified in policy_templates.json. |
| int max_external_data_size_for_testing = 0; |
| |
| } // namespace |
| |
| // Backend for the CloudExternalDataManagerBase that handles all data download, |
| // verification, caching and retrieval. |
| class CloudExternalDataManagerBase::Backend { |
| public: |
| // The |policy_definitions| are used to determine the maximum size that the |
| // data referenced by each policy can have. This class can be instantiated on |
| // any thread but from then on, may be accessed via the |task_runner_| only. |
| // All FetchCallbacks will be invoked via |callback_task_runner|. |
| Backend(const PolicyDefinitionList* policy_definitions, |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| scoped_refptr<base::SequencedTaskRunner> callback_task_runner); |
| |
| // Allows downloaded external data to be cached in |external_data_store|. |
| // Ownership of the store is taken. The store can be destroyed by calling |
| // SetExternalDataStore(scoped_ptr<CloudExternalDataStore>()). |
| void SetExternalDataStore( |
| scoped_ptr<CloudExternalDataStore> external_data_store); |
| |
| // Allows downloading of external data via the |external_policy_data_fetcher|. |
| void Connect( |
| scoped_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher); |
| |
| // Prevents further external data downloads and aborts any downloads currently |
| // in progress |
| void Disconnect(); |
| |
| // Called when the external data references that this backend is responsible |
| // for change. |metadata| maps from policy names to the metadata specifying |
| // the external data that each of the policies references. |
| void OnMetadataUpdated(scoped_ptr<Metadata> metadata); |
| |
| // Called by the |updater_| when the external |data| referenced by |policy| |
| // has been successfully downloaded and verified to match |hash|. |
| bool OnDownloadSuccess(const std::string& policy, |
| const std::string& hash, |
| const std::string& data); |
| |
| // Retrieves the external data referenced by |policy| and invokes |callback| |
| // with the result. If |policy| does not reference any external data, the |
| // |callback| is invoked with a NULL pointer. Otherwise, the |callback| is |
| // invoked with the referenced data once it has been successfully retrieved. |
| // If retrieval is temporarily impossible (e.g. the data is not cached yet and |
| // there is no network connectivity), the |callback| will be invoked when the |
| // temporary hindrance is resolved. If retrieval is permanently impossible |
| // (e.g. |policy| references data that does not exist on the server), the |
| // |callback| will never be invoked. |
| // If the data for |policy| is not cached yet, only one download is started, |
| // even if this method is invoked multiple times. The |callback|s passed are |
| // enqueued and all invoked once the data has been successfully retrieved. |
| void Fetch(const std::string& policy, |
| const ExternalDataFetcher::FetchCallback& callback); |
| |
| // Try to download and cache all external data referenced by |metadata_|. |
| void FetchAll(); |
| |
| private: |
| // List of callbacks to invoke when the attempt to retrieve external data |
| // referenced by a policy completes successfully or fails permanently. |
| typedef std::vector<ExternalDataFetcher::FetchCallback> FetchCallbackList; |
| |
| // Map from policy names to the lists of callbacks defined above. |
| typedef std::map<std::string, FetchCallbackList> FetchCallbackMap; |
| |
| // Looks up the maximum size that the data referenced by |policy| can have in |
| // |policy_definitions_|. |
| size_t GetMaxExternalDataSize(const std::string& policy) const; |
| |
| // Invokes |callback| via the |callback_task_runner_|, passing |data| as a |
| // parameter. |
| void RunCallback(const ExternalDataFetcher::FetchCallback& callback, |
| scoped_ptr<std::string> data) const; |
| |
| // Tells the |updater_| to download the external data referenced by |policy|. |
| // If Connect() was not called yet and no |updater_| exists, does nothing. |
| void StartDownload(const std::string& policy); |
| |
| // Used to determine the maximum size that the data referenced by each policy |
| // can have. |
| const PolicyDefinitionList* policy_definitions_; |
| |
| scoped_refptr<base::SequencedTaskRunner> task_runner_; |
| scoped_refptr<base::SequencedTaskRunner> callback_task_runner_; |
| |
| // Contains the policies for which a download of the referenced external data |
| // has been requested. Each policy is mapped to a list of callbacks to invoke |
| // when the download completes successfully or fails permanently. If no |
| // callback needs to be invoked (because the download was requested via |
| // FetchAll()), a map entry will still exist but the list of callbacks it maps |
| // to will be empty. |
| FetchCallbackMap pending_downloads_; |
| |
| // Indicates that OnMetadataUpdated() has been called at least once and the |
| // contents of |metadata_| is initialized. |
| bool metadata_set_; |
| |
| // Maps from policy names to the metadata specifying the external data that |
| // each of the policies references. |
| Metadata metadata_; |
| |
| // Used to cache external data referenced by policies. |
| scoped_ptr<CloudExternalDataStore> external_data_store_; |
| |
| // Used to download external data referenced by policies. |
| scoped_ptr<ExternalPolicyDataUpdater> updater_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Backend); |
| }; |
| |
| CloudExternalDataManagerBase::Backend::Backend( |
| const PolicyDefinitionList* policy_definitions, |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| scoped_refptr<base::SequencedTaskRunner> callback_task_runner) |
| : policy_definitions_(policy_definitions), |
| task_runner_(task_runner), |
| callback_task_runner_(callback_task_runner), |
| metadata_set_(false) { |
| } |
| |
| void CloudExternalDataManagerBase::Backend::SetExternalDataStore( |
| scoped_ptr<CloudExternalDataStore> external_data_store) { |
| external_data_store_.reset(external_data_store.release()); |
| if (metadata_set_ && external_data_store_) |
| external_data_store_->Prune(metadata_); |
| } |
| |
| void CloudExternalDataManagerBase::Backend::Connect( |
| scoped_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher) { |
| DCHECK(!updater_); |
| updater_.reset(new ExternalPolicyDataUpdater( |
| task_runner_, |
| external_policy_data_fetcher.Pass(), |
| kMaxParallelFetches)); |
| for (FetchCallbackMap::const_iterator it = pending_downloads_.begin(); |
| it != pending_downloads_.end(); ++it) { |
| StartDownload(it->first); |
| } |
| } |
| |
| void CloudExternalDataManagerBase::Backend::Disconnect() { |
| updater_.reset(); |
| } |
| |
| void CloudExternalDataManagerBase::Backend::OnMetadataUpdated( |
| scoped_ptr<Metadata> metadata) { |
| metadata_set_ = true; |
| Metadata old_metadata; |
| metadata_.swap(old_metadata); |
| if (metadata) |
| metadata_.swap(*metadata); |
| |
| if (external_data_store_) |
| external_data_store_->Prune(metadata_); |
| |
| for (FetchCallbackMap::iterator it = pending_downloads_.begin(); |
| it != pending_downloads_.end(); ) { |
| const std::string policy = it->first; |
| Metadata::const_iterator metadata = metadata_.find(policy); |
| if (metadata == metadata_.end()) { |
| // |policy| no longer references external data. |
| if (updater_) { |
| // Cancel the external data download. |
| updater_->CancelExternalDataFetch(policy); |
| } |
| for (FetchCallbackList::const_iterator callback = it->second.begin(); |
| callback != it->second.end(); ++callback) { |
| // Invoke all callbacks for |policy|, indicating permanent failure. |
| RunCallback(*callback, scoped_ptr<std::string>()); |
| } |
| pending_downloads_.erase(it++); |
| continue; |
| } |
| |
| if (updater_ && metadata->second != old_metadata[policy]) { |
| // |policy| still references external data but the reference has changed. |
| // Cancel the external data download and start a new one. |
| updater_->CancelExternalDataFetch(policy); |
| StartDownload(policy); |
| } |
| ++it; |
| } |
| } |
| |
| bool CloudExternalDataManagerBase::Backend::OnDownloadSuccess( |
| const std::string& policy, |
| const std::string& hash, |
| const std::string& data) { |
| DCHECK(metadata_.find(policy) != metadata_.end()); |
| DCHECK_EQ(hash, metadata_[policy].hash); |
| if (external_data_store_) |
| external_data_store_->Store(policy, hash, data); |
| |
| const FetchCallbackList& pending_callbacks = pending_downloads_[policy]; |
| for (FetchCallbackList::const_iterator it = pending_callbacks.begin(); |
| it != pending_callbacks.end(); ++it) { |
| RunCallback(*it, make_scoped_ptr(new std::string(data))); |
| } |
| pending_downloads_.erase(policy); |
| return true; |
| } |
| |
| void CloudExternalDataManagerBase::Backend::Fetch( |
| const std::string& policy, |
| const ExternalDataFetcher::FetchCallback& callback) { |
| Metadata::const_iterator metadata = metadata_.find(policy); |
| if (metadata == metadata_.end()) { |
| // If |policy| does not reference any external data, indicate permanent |
| // failure. |
| RunCallback(callback, scoped_ptr<std::string>()); |
| return; |
| } |
| |
| if (pending_downloads_.find(policy) != pending_downloads_.end()) { |
| // If a download of the external data referenced by |policy| has already |
| // been requested, add |callback| to the list of callbacks for |policy| and |
| // return. |
| pending_downloads_[policy].push_back(callback); |
| return; |
| } |
| |
| scoped_ptr<std::string> data(new std::string); |
| if (external_data_store_ && external_data_store_->Load( |
| policy, metadata->second.hash, GetMaxExternalDataSize(policy), |
| data.get())) { |
| // If the external data referenced by |policy| exists in the cache and |
| // matches the expected hash, pass it to the callback. |
| RunCallback(callback, data.Pass()); |
| return; |
| } |
| |
| // Request a download of the the external data referenced by |policy| and |
| // initialize the list of callbacks by adding |callback|. |
| pending_downloads_[policy].push_back(callback); |
| StartDownload(policy); |
| } |
| |
| void CloudExternalDataManagerBase::Backend::FetchAll() { |
| // Loop through all external data references. |
| for (Metadata::const_iterator it = metadata_.begin(); it != metadata_.end(); |
| ++it) { |
| const std::string& policy = it->first; |
| scoped_ptr<std::string> data(new std::string); |
| if (pending_downloads_.find(policy) != pending_downloads_.end() || |
| (external_data_store_ && external_data_store_->Load( |
| policy, it->second.hash, GetMaxExternalDataSize(policy), |
| data.get()))) { |
| // If a download of the external data referenced by |policy| has already |
| // been requested or the data exists in the cache and matches the expected |
| // hash, there is nothing to be done. |
| continue; |
| } |
| // Request a download of the the external data referenced by |policy| and |
| // initialize the list of callbacks to an empty list. |
| pending_downloads_[policy]; |
| StartDownload(policy); |
| } |
| } |
| |
| size_t CloudExternalDataManagerBase::Backend::GetMaxExternalDataSize( |
| const std::string& policy) const { |
| if (max_external_data_size_for_testing) |
| return max_external_data_size_for_testing; |
| |
| // Look up the maximum size that the data referenced by |policy| can have in |
| // policy_definitions_, which is constructed from the information in |
| // policy_templates.json, allowing the maximum data size to be specified as |
| // part of the policy definition. |
| for (const PolicyDefinitionList::Entry* entry = policy_definitions_->begin; |
| entry != policy_definitions_->end; ++entry) { |
| if (entry->name == policy) |
| return entry->max_external_data_size; |
| } |
| NOTREACHED(); |
| return 0; |
| } |
| |
| void CloudExternalDataManagerBase::Backend::RunCallback( |
| const ExternalDataFetcher::FetchCallback& callback, |
| scoped_ptr<std::string> data) const { |
| callback_task_runner_->PostTask(FROM_HERE, |
| base::Bind(callback, base::Passed(&data))); |
| } |
| |
| void CloudExternalDataManagerBase::Backend::StartDownload( |
| const std::string& policy) { |
| DCHECK(pending_downloads_.find(policy) != pending_downloads_.end()); |
| if (!updater_) |
| return; |
| |
| const MetadataEntry& metadata = metadata_[policy]; |
| updater_->FetchExternalData( |
| policy, |
| ExternalPolicyDataUpdater::Request(metadata.url, |
| metadata.hash, |
| GetMaxExternalDataSize(policy)), |
| base::Bind(&CloudExternalDataManagerBase::Backend::OnDownloadSuccess, |
| base::Unretained(this), |
| policy, |
| metadata.hash)); |
| } |
| |
| CloudExternalDataManagerBase::CloudExternalDataManagerBase( |
| const PolicyDefinitionList* policy_definitions, |
| scoped_refptr<base::SequencedTaskRunner> backend_task_runner, |
| scoped_refptr<base::SequencedTaskRunner> io_task_runner) |
| : backend_task_runner_(backend_task_runner), |
| io_task_runner_(io_task_runner), |
| backend_(new Backend(policy_definitions, |
| backend_task_runner_, |
| base::MessageLoopProxy::current())) { |
| } |
| |
| CloudExternalDataManagerBase::~CloudExternalDataManagerBase() { |
| DCHECK(CalledOnValidThread()); |
| io_task_runner_->DeleteSoon(FROM_HERE, |
| external_policy_data_fetcher_backend_.release()); |
| backend_task_runner_->DeleteSoon(FROM_HERE, backend_.release()); |
| } |
| |
| void CloudExternalDataManagerBase::SetExternalDataStore( |
| scoped_ptr<CloudExternalDataStore> external_data_store) { |
| DCHECK(CalledOnValidThread()); |
| backend_task_runner_->PostTask(FROM_HERE, base::Bind( |
| &Backend::SetExternalDataStore, |
| base::Unretained(backend_.get()), |
| base::Passed(&external_data_store))); |
| } |
| |
| void CloudExternalDataManagerBase::SetPolicyStore( |
| CloudPolicyStore* policy_store) { |
| DCHECK(CalledOnValidThread()); |
| CloudExternalDataManager::SetPolicyStore(policy_store); |
| if (policy_store_ && policy_store_->is_initialized()) |
| OnPolicyStoreLoaded(); |
| } |
| |
| void CloudExternalDataManagerBase::OnPolicyStoreLoaded() { |
| // Collect all external data references made by policies in |policy_store_| |
| // and pass them to the |backend_|. |
| DCHECK(CalledOnValidThread()); |
| scoped_ptr<Metadata> metadata(new Metadata); |
| const PolicyMap& policy_map = policy_store_->policy_map(); |
| for (PolicyMap::const_iterator it = policy_map.begin(); |
| it != policy_map.end(); ++it) { |
| if (!it->second.external_data_fetcher) { |
| // Skip policies that do not reference external data. |
| continue; |
| } |
| const base::DictionaryValue* dict = NULL; |
| std::string url; |
| std::string hex_hash; |
| std::vector<uint8> hash; |
| if (it->second.value && it->second.value->GetAsDictionary(&dict) && |
| dict->GetStringWithoutPathExpansion("url", &url) && |
| dict->GetStringWithoutPathExpansion("hash", &hex_hash) && |
| !url.empty() && !hex_hash.empty() && |
| base::HexStringToBytes(hex_hash, &hash)) { |
| // Add the external data reference to |metadata| if it is valid (URL and |
| // hash are not empty, hash can be decoded as a hex string). |
| (*metadata)[it->first] = |
| MetadataEntry(url, std::string(hash.begin(), hash.end())); |
| } |
| } |
| |
| backend_task_runner_->PostTask(FROM_HERE, base::Bind( |
| &Backend::OnMetadataUpdated, |
| base::Unretained(backend_.get()), |
| base::Passed(&metadata))); |
| } |
| |
| void CloudExternalDataManagerBase::Connect( |
| scoped_refptr<net::URLRequestContextGetter> request_context) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!external_policy_data_fetcher_backend_); |
| external_policy_data_fetcher_backend_.reset( |
| new ExternalPolicyDataFetcherBackend(io_task_runner_, |
| request_context)); |
| backend_task_runner_->PostTask(FROM_HERE, base::Bind( |
| &Backend::Connect, |
| base::Unretained(backend_.get()), |
| base::Passed(external_policy_data_fetcher_backend_->CreateFrontend( |
| backend_task_runner_)))); |
| } |
| |
| void CloudExternalDataManagerBase::Disconnect() { |
| DCHECK(CalledOnValidThread()); |
| io_task_runner_->DeleteSoon(FROM_HERE, |
| external_policy_data_fetcher_backend_.release()); |
| backend_task_runner_->PostTask(FROM_HERE, base::Bind( |
| &Backend::Disconnect, base::Unretained(backend_.get()))); |
| } |
| |
| void CloudExternalDataManagerBase::Fetch( |
| const std::string& policy, |
| const ExternalDataFetcher::FetchCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| backend_task_runner_->PostTask(FROM_HERE, base::Bind( |
| &Backend::Fetch, base::Unretained(backend_.get()), policy, callback)); |
| } |
| |
| // static |
| void CloudExternalDataManagerBase::SetMaxExternalDataSizeForTesting( |
| int max_size) { |
| max_external_data_size_for_testing = max_size; |
| } |
| |
| void CloudExternalDataManagerBase::FetchAll() { |
| DCHECK(CalledOnValidThread()); |
| backend_task_runner_->PostTask(FROM_HERE, base::Bind( |
| &Backend::FetchAll, base::Unretained(backend_.get()))); |
| } |
| |
| } // namespace policy |