Home | History | Annotate | Download | only in policy
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/policy/cloud_external_data_manager_base.h"
      6 
      7 #include <map>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/bind.h"
     12 #include "base/bind_helpers.h"
     13 #include "base/callback.h"
     14 #include "base/location.h"
     15 #include "base/logging.h"
     16 #include "base/message_loop/message_loop_proxy.h"
     17 #include "base/sequenced_task_runner.h"
     18 #include "base/strings/string_number_conversions.h"
     19 #include "base/values.h"
     20 #include "chrome/browser/chromeos/policy/cloud_external_data_store.h"
     21 #include "components/policy/core/common/cloud/cloud_policy_store.h"
     22 #include "components/policy/core/common/cloud/external_policy_data_fetcher.h"
     23 #include "components/policy/core/common/cloud/external_policy_data_updater.h"
     24 #include "components/policy/core/common/external_data_fetcher.h"
     25 #include "components/policy/core/common/policy_map.h"
     26 #include "net/url_request/url_request_context_getter.h"
     27 
     28 namespace policy {
     29 
     30 namespace {
     31 
     32 // Fetch data for at most two external data references at the same time.
     33 const int kMaxParallelFetches = 2;
     34 
     35 // Allows policies to reference |max_external_data_size_for_testing| bytes of
     36 // external data even if no |max_size| was specified in policy_templates.json.
     37 int max_external_data_size_for_testing = 0;
     38 
     39 }  // namespace
     40 
     41 // Backend for the CloudExternalDataManagerBase that handles all data download,
     42 // verification, caching and retrieval.
     43 class CloudExternalDataManagerBase::Backend {
     44  public:
     45   // |get_policy_details| is used to determine the maximum size that the
     46   // data referenced by each policy can have. This class can be instantiated on
     47   // any thread but from then on, may be accessed via the |task_runner_| only.
     48   // All FetchCallbacks will be invoked via |callback_task_runner|.
     49   Backend(const GetChromePolicyDetailsCallback& get_policy_details,
     50           scoped_refptr<base::SequencedTaskRunner> task_runner,
     51           scoped_refptr<base::SequencedTaskRunner> callback_task_runner);
     52 
     53   // Allows downloaded external data to be cached in |external_data_store|.
     54   // Ownership of the store is taken. The store can be destroyed by calling
     55   // SetExternalDataStore(scoped_ptr<CloudExternalDataStore>()).
     56   void SetExternalDataStore(
     57       scoped_ptr<CloudExternalDataStore> external_data_store);
     58 
     59   // Allows downloading of external data via the |external_policy_data_fetcher|.
     60   void Connect(
     61       scoped_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher);
     62 
     63   // Prevents further external data downloads and aborts any downloads currently
     64   // in progress
     65   void Disconnect();
     66 
     67   // Called when the external data references that this backend is responsible
     68   // for change. |metadata| maps from policy names to the metadata specifying
     69   // the external data that each of the policies references.
     70   void OnMetadataUpdated(scoped_ptr<Metadata> metadata);
     71 
     72   // Called by the |updater_| when the external |data| referenced by |policy|
     73   // has been successfully downloaded and verified to match |hash|.
     74   bool OnDownloadSuccess(const std::string& policy,
     75                          const std::string& hash,
     76                          const std::string& data);
     77 
     78   // Retrieves the external data referenced by |policy| and invokes |callback|
     79   // with the result. If |policy| does not reference any external data, the
     80   // |callback| is invoked with a NULL pointer. Otherwise, the |callback| is
     81   // invoked with the referenced data once it has been successfully retrieved.
     82   // If retrieval is temporarily impossible (e.g. the data is not cached yet and
     83   // there is no network connectivity), the |callback| will be invoked when the
     84   // temporary hindrance is resolved. If retrieval is permanently impossible
     85   // (e.g. |policy| references data that does not exist on the server), the
     86   // |callback| will never be invoked.
     87   // If the data for |policy| is not cached yet, only one download is started,
     88   // even if this method is invoked multiple times. The |callback|s passed are
     89   // enqueued and all invoked once the data has been successfully retrieved.
     90   void Fetch(const std::string& policy,
     91              const ExternalDataFetcher::FetchCallback& callback);
     92 
     93   // Try to download and cache all external data referenced by |metadata_|.
     94   void FetchAll();
     95 
     96  private:
     97   // List of callbacks to invoke when the attempt to retrieve external data
     98   // referenced by a policy completes successfully or fails permanently.
     99   typedef std::vector<ExternalDataFetcher::FetchCallback> FetchCallbackList;
    100 
    101   // Map from policy names to the lists of callbacks defined above.
    102   typedef std::map<std::string, FetchCallbackList> FetchCallbackMap;
    103 
    104   // Looks up the maximum size that the data referenced by |policy| can have.
    105   size_t GetMaxExternalDataSize(const std::string& policy) const;
    106 
    107   // Invokes |callback| via the |callback_task_runner_|, passing |data| as a
    108   // parameter.
    109   void RunCallback(const ExternalDataFetcher::FetchCallback& callback,
    110                    scoped_ptr<std::string> data) const;
    111 
    112   // Tells the |updater_| to download the external data referenced by |policy|.
    113   // If Connect() was not called yet and no |updater_| exists, does nothing.
    114   void StartDownload(const std::string& policy);
    115 
    116   // Used to determine the maximum size that the data referenced by each policy
    117   // can have.
    118   GetChromePolicyDetailsCallback get_policy_details_;
    119 
    120   scoped_refptr<base::SequencedTaskRunner> task_runner_;
    121   scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
    122 
    123   // Contains the policies for which a download of the referenced external data
    124   // has been requested. Each policy is mapped to a list of callbacks to invoke
    125   // when the download completes successfully or fails permanently. If no
    126   // callback needs to be invoked (because the download was requested via
    127   // FetchAll()), a map entry will still exist but the list of callbacks it maps
    128   // to will be empty.
    129   FetchCallbackMap pending_downloads_;
    130 
    131   // Indicates that OnMetadataUpdated() has been called at least once and the
    132   // contents of |metadata_| is initialized.
    133   bool metadata_set_;
    134 
    135   // Maps from policy names to the metadata specifying the external data that
    136   // each of the policies references.
    137   Metadata metadata_;
    138 
    139   // Used to cache external data referenced by policies.
    140   scoped_ptr<CloudExternalDataStore> external_data_store_;
    141 
    142   // Used to download external data referenced by policies.
    143   scoped_ptr<ExternalPolicyDataUpdater> updater_;
    144 
    145   DISALLOW_COPY_AND_ASSIGN(Backend);
    146 };
    147 
    148 CloudExternalDataManagerBase::Backend::Backend(
    149     const GetChromePolicyDetailsCallback& get_policy_details,
    150     scoped_refptr<base::SequencedTaskRunner> task_runner,
    151     scoped_refptr<base::SequencedTaskRunner> callback_task_runner)
    152     : get_policy_details_(get_policy_details),
    153       task_runner_(task_runner),
    154       callback_task_runner_(callback_task_runner),
    155       metadata_set_(false) {
    156 }
    157 
    158 void CloudExternalDataManagerBase::Backend::SetExternalDataStore(
    159     scoped_ptr<CloudExternalDataStore> external_data_store) {
    160   external_data_store_.reset(external_data_store.release());
    161   if (metadata_set_ && external_data_store_)
    162     external_data_store_->Prune(metadata_);
    163 }
    164 
    165 void CloudExternalDataManagerBase::Backend::Connect(
    166     scoped_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher) {
    167   DCHECK(!updater_);
    168   updater_.reset(new ExternalPolicyDataUpdater(
    169       task_runner_,
    170       external_policy_data_fetcher.Pass(),
    171       kMaxParallelFetches));
    172   for (FetchCallbackMap::const_iterator it = pending_downloads_.begin();
    173        it != pending_downloads_.end(); ++it) {
    174     StartDownload(it->first);
    175   }
    176 }
    177 
    178 void CloudExternalDataManagerBase::Backend::Disconnect() {
    179   updater_.reset();
    180 }
    181 
    182 void CloudExternalDataManagerBase::Backend::OnMetadataUpdated(
    183     scoped_ptr<Metadata> metadata) {
    184   metadata_set_ = true;
    185   Metadata old_metadata;
    186   metadata_.swap(old_metadata);
    187   if (metadata)
    188     metadata_.swap(*metadata);
    189 
    190   if (external_data_store_)
    191     external_data_store_->Prune(metadata_);
    192 
    193   for (FetchCallbackMap::iterator it = pending_downloads_.begin();
    194        it != pending_downloads_.end(); ) {
    195     const std::string policy = it->first;
    196     Metadata::const_iterator metadata = metadata_.find(policy);
    197     if (metadata == metadata_.end()) {
    198       // |policy| no longer references external data.
    199       if (updater_) {
    200         // Cancel the external data download.
    201         updater_->CancelExternalDataFetch(policy);
    202       }
    203       for (FetchCallbackList::const_iterator callback = it->second.begin();
    204            callback != it->second.end(); ++callback) {
    205         // Invoke all callbacks for |policy|, indicating permanent failure.
    206         RunCallback(*callback, scoped_ptr<std::string>());
    207       }
    208       pending_downloads_.erase(it++);
    209       continue;
    210     }
    211 
    212     if (updater_ && metadata->second != old_metadata[policy]) {
    213       // |policy| still references external data but the reference has changed.
    214       // Cancel the external data download and start a new one.
    215       updater_->CancelExternalDataFetch(policy);
    216       StartDownload(policy);
    217     }
    218     ++it;
    219   }
    220 }
    221 
    222 bool CloudExternalDataManagerBase::Backend::OnDownloadSuccess(
    223     const std::string& policy,
    224     const std::string& hash,
    225     const std::string& data) {
    226   DCHECK(metadata_.find(policy) != metadata_.end());
    227   DCHECK_EQ(hash, metadata_[policy].hash);
    228   if (external_data_store_)
    229     external_data_store_->Store(policy, hash, data);
    230 
    231   const FetchCallbackList& pending_callbacks = pending_downloads_[policy];
    232   for (FetchCallbackList::const_iterator it = pending_callbacks.begin();
    233        it != pending_callbacks.end(); ++it) {
    234     RunCallback(*it, make_scoped_ptr(new std::string(data)));
    235   }
    236   pending_downloads_.erase(policy);
    237   return true;
    238 }
    239 
    240 void CloudExternalDataManagerBase::Backend::Fetch(
    241     const std::string& policy,
    242     const ExternalDataFetcher::FetchCallback& callback) {
    243   Metadata::const_iterator metadata = metadata_.find(policy);
    244   if (metadata == metadata_.end()) {
    245     // If |policy| does not reference any external data, indicate permanent
    246     // failure.
    247     RunCallback(callback, scoped_ptr<std::string>());
    248     return;
    249   }
    250 
    251   if (pending_downloads_.find(policy) != pending_downloads_.end()) {
    252     // If a download of the external data referenced by |policy| has already
    253     // been requested, add |callback| to the list of callbacks for |policy| and
    254     // return.
    255     pending_downloads_[policy].push_back(callback);
    256     return;
    257   }
    258 
    259   scoped_ptr<std::string> data(new std::string);
    260   if (external_data_store_ && external_data_store_->Load(
    261           policy, metadata->second.hash, GetMaxExternalDataSize(policy),
    262           data.get())) {
    263     // If the external data referenced by |policy| exists in the cache and
    264     // matches the expected hash, pass it to the callback.
    265     RunCallback(callback, data.Pass());
    266     return;
    267   }
    268 
    269   // Request a download of the the external data referenced by |policy| and
    270   // initialize the list of callbacks by adding |callback|.
    271   pending_downloads_[policy].push_back(callback);
    272   StartDownload(policy);
    273 }
    274 
    275 void CloudExternalDataManagerBase::Backend::FetchAll() {
    276   // Loop through all external data references.
    277   for (Metadata::const_iterator it = metadata_.begin(); it != metadata_.end();
    278        ++it) {
    279     const std::string& policy = it->first;
    280     scoped_ptr<std::string> data(new std::string);
    281     if (pending_downloads_.find(policy) != pending_downloads_.end() ||
    282         (external_data_store_ && external_data_store_->Load(
    283              policy, it->second.hash, GetMaxExternalDataSize(policy),
    284              data.get()))) {
    285       // If a download of the external data referenced by |policy| has already
    286       // been requested or the data exists in the cache and matches the expected
    287       // hash, there is nothing to be done.
    288       continue;
    289     }
    290     // Request a download of the the external data referenced by |policy| and
    291     // initialize the list of callbacks to an empty list.
    292     pending_downloads_[policy];
    293     StartDownload(policy);
    294   }
    295 }
    296 
    297 size_t CloudExternalDataManagerBase::Backend::GetMaxExternalDataSize(
    298     const std::string& policy) const {
    299   if (max_external_data_size_for_testing)
    300     return max_external_data_size_for_testing;
    301 
    302   // Look up the maximum size that the data referenced by |policy| can have in
    303   // get_policy_details, which is constructed from the information in
    304   // policy_templates.json, allowing the maximum data size to be specified as
    305   // part of the policy definition.
    306   const PolicyDetails* details = get_policy_details_.Run(policy);
    307   if (details)
    308     return details->max_external_data_size;
    309   NOTREACHED();
    310   return 0;
    311 }
    312 
    313 void CloudExternalDataManagerBase::Backend::RunCallback(
    314     const ExternalDataFetcher::FetchCallback& callback,
    315     scoped_ptr<std::string> data) const {
    316   callback_task_runner_->PostTask(FROM_HERE,
    317                                   base::Bind(callback, base::Passed(&data)));
    318 }
    319 
    320 void CloudExternalDataManagerBase::Backend::StartDownload(
    321     const std::string& policy) {
    322   DCHECK(pending_downloads_.find(policy) != pending_downloads_.end());
    323   if (!updater_)
    324     return;
    325 
    326   const MetadataEntry& metadata = metadata_[policy];
    327   updater_->FetchExternalData(
    328       policy,
    329       ExternalPolicyDataUpdater::Request(metadata.url,
    330                                          metadata.hash,
    331                                          GetMaxExternalDataSize(policy)),
    332       base::Bind(&CloudExternalDataManagerBase::Backend::OnDownloadSuccess,
    333                  base::Unretained(this),
    334                  policy,
    335                  metadata.hash));
    336 }
    337 
    338 CloudExternalDataManagerBase::CloudExternalDataManagerBase(
    339     const GetChromePolicyDetailsCallback& get_policy_details,
    340     scoped_refptr<base::SequencedTaskRunner> backend_task_runner,
    341     scoped_refptr<base::SequencedTaskRunner> io_task_runner)
    342     : backend_task_runner_(backend_task_runner),
    343       io_task_runner_(io_task_runner),
    344       backend_(new Backend(get_policy_details,
    345                            backend_task_runner_,
    346                            base::MessageLoopProxy::current())) {
    347 }
    348 
    349 CloudExternalDataManagerBase::~CloudExternalDataManagerBase() {
    350   DCHECK(CalledOnValidThread());
    351   io_task_runner_->DeleteSoon(FROM_HERE,
    352                               external_policy_data_fetcher_backend_.release());
    353   backend_task_runner_->DeleteSoon(FROM_HERE, backend_.release());
    354 }
    355 
    356 void CloudExternalDataManagerBase::SetExternalDataStore(
    357     scoped_ptr<CloudExternalDataStore> external_data_store) {
    358   DCHECK(CalledOnValidThread());
    359   backend_task_runner_->PostTask(FROM_HERE, base::Bind(
    360       &Backend::SetExternalDataStore,
    361       base::Unretained(backend_.get()),
    362       base::Passed(&external_data_store)));
    363 }
    364 
    365 void CloudExternalDataManagerBase::SetPolicyStore(
    366     CloudPolicyStore* policy_store) {
    367   DCHECK(CalledOnValidThread());
    368   CloudExternalDataManager::SetPolicyStore(policy_store);
    369   if (policy_store_ && policy_store_->is_initialized())
    370     OnPolicyStoreLoaded();
    371 }
    372 
    373 void CloudExternalDataManagerBase::OnPolicyStoreLoaded() {
    374   // Collect all external data references made by policies in |policy_store_|
    375   // and pass them to the |backend_|.
    376   DCHECK(CalledOnValidThread());
    377   scoped_ptr<Metadata> metadata(new Metadata);
    378   const PolicyMap& policy_map = policy_store_->policy_map();
    379   for (PolicyMap::const_iterator it = policy_map.begin();
    380        it != policy_map.end(); ++it) {
    381     if (!it->second.external_data_fetcher) {
    382       // Skip policies that do not reference external data.
    383       continue;
    384     }
    385     const base::DictionaryValue* dict = NULL;
    386     std::string url;
    387     std::string hex_hash;
    388     std::vector<uint8> hash;
    389     if (it->second.value && it->second.value->GetAsDictionary(&dict) &&
    390         dict->GetStringWithoutPathExpansion("url", &url) &&
    391         dict->GetStringWithoutPathExpansion("hash", &hex_hash) &&
    392         !url.empty() && !hex_hash.empty() &&
    393         base::HexStringToBytes(hex_hash, &hash)) {
    394       // Add the external data reference to |metadata| if it is valid (URL and
    395       // hash are not empty, hash can be decoded as a hex string).
    396       (*metadata)[it->first] =
    397           MetadataEntry(url, std::string(hash.begin(), hash.end()));
    398     }
    399   }
    400 
    401   backend_task_runner_->PostTask(FROM_HERE, base::Bind(
    402       &Backend::OnMetadataUpdated,
    403       base::Unretained(backend_.get()),
    404       base::Passed(&metadata)));
    405 }
    406 
    407 void CloudExternalDataManagerBase::Connect(
    408     scoped_refptr<net::URLRequestContextGetter> request_context) {
    409   DCHECK(CalledOnValidThread());
    410   DCHECK(!external_policy_data_fetcher_backend_);
    411   external_policy_data_fetcher_backend_.reset(
    412       new ExternalPolicyDataFetcherBackend(io_task_runner_,
    413                                            request_context));
    414   backend_task_runner_->PostTask(FROM_HERE, base::Bind(
    415       &Backend::Connect,
    416       base::Unretained(backend_.get()),
    417       base::Passed(external_policy_data_fetcher_backend_->CreateFrontend(
    418           backend_task_runner_))));
    419 }
    420 
    421 void CloudExternalDataManagerBase::Disconnect() {
    422   DCHECK(CalledOnValidThread());
    423   io_task_runner_->DeleteSoon(FROM_HERE,
    424                               external_policy_data_fetcher_backend_.release());
    425   backend_task_runner_->PostTask(FROM_HERE, base::Bind(
    426       &Backend::Disconnect, base::Unretained(backend_.get())));
    427 }
    428 
    429 void CloudExternalDataManagerBase::Fetch(
    430     const std::string& policy,
    431     const ExternalDataFetcher::FetchCallback& callback) {
    432   DCHECK(CalledOnValidThread());
    433   backend_task_runner_->PostTask(FROM_HERE, base::Bind(
    434       &Backend::Fetch, base::Unretained(backend_.get()), policy, callback));
    435 }
    436 
    437 // static
    438 void CloudExternalDataManagerBase::SetMaxExternalDataSizeForTesting(
    439     int max_size) {
    440   max_external_data_size_for_testing = max_size;
    441 }
    442 
    443 void CloudExternalDataManagerBase::FetchAll() {
    444   DCHECK(CalledOnValidThread());
    445   backend_task_runner_->PostTask(FROM_HERE, base::Bind(
    446       &Backend::FetchAll, base::Unretained(backend_.get())));
    447 }
    448 
    449 }  // namespace policy
    450