cloud_external_data_manager_base.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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/location.h"
14#include "base/logging.h"
15#include "base/message_loop/message_loop_proxy.h"
16#include "base/sequenced_task_runner.h"
17#include "base/strings/string_number_conversions.h"
18#include "base/values.h"
19#include "chrome/browser/chromeos/policy/cloud_external_data_store.h"
20#include "chrome/browser/policy/cloud/cloud_policy_store.h"
21#include "chrome/browser/policy/cloud/external_policy_data_fetcher.h"
22#include "chrome/browser/policy/cloud/external_policy_data_updater.h"
23#include "chrome/browser/policy/external_data_fetcher.h"
24#include "chrome/browser/policy/policy_map.h"
25#include "net/url_request/url_request_context_getter.h"
26#include "policy/policy_constants.h"
27
28namespace policy {
29
30namespace {
31
32// Fetch data for at most two external data references at the same time.
33const 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.
37int 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.
43class CloudExternalDataManagerBase::Backend {
44 public:
45  // The |policy_definitions| are 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 PolicyDefinitionList* policy_definitions,
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 in
105  // |policy_definitions_|.
106  size_t GetMaxExternalDataSize(const std::string& policy) const;
107
108  // Invokes |callback| via the |callback_task_runner_|, passing |data| as a
109  // parameter.
110  void RunCallback(const ExternalDataFetcher::FetchCallback& callback,
111                   scoped_ptr<std::string> data) const;
112
113  // Tells the |updater_| to download the external data referenced by |policy|.
114  // If Connect() was not called yet and no |updater_| exists, does nothing.
115  void StartDownload(const std::string& policy);
116
117  // Used to determine the maximum size that the data referenced by each policy
118  // can have.
119  const PolicyDefinitionList* policy_definitions_;
120
121  scoped_refptr<base::SequencedTaskRunner> task_runner_;
122  scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
123
124  // Contains the policies for which a download of the referenced external data
125  // has been requested. Each policy is mapped to a list of callbacks to invoke
126  // when the download completes successfully or fails permanently. If no
127  // callback needs to be invoked (because the download was requested via
128  // FetchAll()), a map entry will still exist but the list of callbacks it maps
129  // to will be empty.
130  FetchCallbackMap pending_downloads_;
131
132  // Indicates that OnMetadataUpdated() has been called at least once and the
133  // contents of |metadata_| is initialized.
134  bool metadata_set_;
135
136  // Maps from policy names to the metadata specifying the external data that
137  // each of the policies references.
138  Metadata metadata_;
139
140  // Used to cache external data referenced by policies.
141  scoped_ptr<CloudExternalDataStore> external_data_store_;
142
143  // Used to download external data referenced by policies.
144  scoped_ptr<ExternalPolicyDataUpdater> updater_;
145
146  DISALLOW_COPY_AND_ASSIGN(Backend);
147};
148
149CloudExternalDataManagerBase::Backend::Backend(
150    const PolicyDefinitionList* policy_definitions,
151    scoped_refptr<base::SequencedTaskRunner> task_runner,
152    scoped_refptr<base::SequencedTaskRunner> callback_task_runner)
153    : policy_definitions_(policy_definitions),
154      task_runner_(task_runner),
155      callback_task_runner_(callback_task_runner),
156      metadata_set_(false) {
157}
158
159void CloudExternalDataManagerBase::Backend::SetExternalDataStore(
160    scoped_ptr<CloudExternalDataStore> external_data_store) {
161  external_data_store_.reset(external_data_store.release());
162  if (metadata_set_ && external_data_store_)
163    external_data_store_->Prune(metadata_);
164}
165
166void CloudExternalDataManagerBase::Backend::Connect(
167    scoped_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher) {
168  DCHECK(!updater_);
169  updater_.reset(new ExternalPolicyDataUpdater(
170      task_runner_,
171      external_policy_data_fetcher.Pass(),
172      kMaxParallelFetches));
173  for (FetchCallbackMap::const_iterator it = pending_downloads_.begin();
174       it != pending_downloads_.end(); ++it) {
175    StartDownload(it->first);
176  }
177}
178
179void CloudExternalDataManagerBase::Backend::Disconnect() {
180  updater_.reset();
181}
182
183void CloudExternalDataManagerBase::Backend::OnMetadataUpdated(
184    scoped_ptr<Metadata> metadata) {
185  metadata_set_ = true;
186  Metadata old_metadata;
187  metadata_.swap(old_metadata);
188  if (metadata)
189    metadata_.swap(*metadata);
190
191  if (external_data_store_)
192    external_data_store_->Prune(metadata_);
193
194  for (FetchCallbackMap::iterator it = pending_downloads_.begin();
195       it != pending_downloads_.end(); ) {
196    const std::string policy = it->first;
197    Metadata::const_iterator metadata = metadata_.find(policy);
198    if (metadata == metadata_.end()) {
199      // |policy| no longer references external data.
200      if (updater_) {
201        // Cancel the external data download.
202        updater_->CancelExternalDataFetch(policy);
203      }
204      for (FetchCallbackList::const_iterator callback = it->second.begin();
205           callback != it->second.end(); ++callback) {
206        // Invoke all callbacks for |policy|, indicating permanent failure.
207        RunCallback(*callback, scoped_ptr<std::string>());
208      }
209      pending_downloads_.erase(it++);
210      continue;
211    }
212
213    if (updater_ && metadata->second != old_metadata[policy]) {
214      // |policy| still references external data but the reference has changed.
215      // Cancel the external data download and start a new one.
216      updater_->CancelExternalDataFetch(policy);
217      StartDownload(policy);
218    }
219    ++it;
220  }
221}
222
223bool CloudExternalDataManagerBase::Backend::OnDownloadSuccess(
224    const std::string& policy,
225    const std::string& hash,
226    const std::string& data) {
227  DCHECK(metadata_.find(policy) != metadata_.end());
228  DCHECK_EQ(hash, metadata_[policy].hash);
229  if (external_data_store_)
230    external_data_store_->Store(policy, hash, data);
231
232  const FetchCallbackList& pending_callbacks = pending_downloads_[policy];
233  for (FetchCallbackList::const_iterator it = pending_callbacks.begin();
234       it != pending_callbacks.end(); ++it) {
235    RunCallback(*it, make_scoped_ptr(new std::string(data)));
236  }
237  pending_downloads_.erase(policy);
238  return true;
239}
240
241void CloudExternalDataManagerBase::Backend::Fetch(
242    const std::string& policy,
243    const ExternalDataFetcher::FetchCallback& callback) {
244  Metadata::const_iterator metadata = metadata_.find(policy);
245  if (metadata == metadata_.end()) {
246    // If |policy| does not reference any external data, indicate permanent
247    // failure.
248    RunCallback(callback, scoped_ptr<std::string>());
249    return;
250  }
251
252  if (pending_downloads_.find(policy) != pending_downloads_.end()) {
253    // If a download of the external data referenced by |policy| has already
254    // been requested, add |callback| to the list of callbacks for |policy| and
255    // return.
256    pending_downloads_[policy].push_back(callback);
257    return;
258  }
259
260  scoped_ptr<std::string> data(new std::string);
261  if (external_data_store_ && external_data_store_->Load(
262          policy, metadata->second.hash, GetMaxExternalDataSize(policy),
263          data.get())) {
264    // If the external data referenced by |policy| exists in the cache and
265    // matches the expected hash, pass it to the callback.
266    RunCallback(callback, data.Pass());
267    return;
268  }
269
270  // Request a download of the the external data referenced by |policy| and
271  // initialize the list of callbacks by adding |callback|.
272  pending_downloads_[policy].push_back(callback);
273  StartDownload(policy);
274}
275
276void CloudExternalDataManagerBase::Backend::FetchAll() {
277  // Loop through all external data references.
278  for (Metadata::const_iterator it = metadata_.begin(); it != metadata_.end();
279       ++it) {
280    const std::string& policy = it->first;
281    scoped_ptr<std::string> data(new std::string);
282    if (pending_downloads_.find(policy) != pending_downloads_.end() ||
283        (external_data_store_ && external_data_store_->Load(
284             policy, it->second.hash, GetMaxExternalDataSize(policy),
285             data.get()))) {
286      // If a download of the external data referenced by |policy| has already
287      // been requested or the data exists in the cache and matches the expected
288      // hash, there is nothing to be done.
289      continue;
290    }
291    // Request a download of the the external data referenced by |policy| and
292    // initialize the list of callbacks to an empty list.
293    pending_downloads_[policy];
294    StartDownload(policy);
295  }
296}
297
298size_t CloudExternalDataManagerBase::Backend::GetMaxExternalDataSize(
299    const std::string& policy) const {
300  if (max_external_data_size_for_testing)
301    return max_external_data_size_for_testing;
302
303  // Look up the maximum size that the data referenced by |policy| can have in
304  // policy_definitions_, which is constructed from the information in
305  // policy_templates.json, allowing the maximum data size to be specified as
306  // part of the policy definition.
307  for (const PolicyDefinitionList::Entry* entry = policy_definitions_->begin;
308       entry != policy_definitions_->end; ++entry) {
309    if (entry->name == policy)
310      return entry->max_external_data_size;
311  }
312  NOTREACHED();
313  return 0;
314}
315
316void CloudExternalDataManagerBase::Backend::RunCallback(
317    const ExternalDataFetcher::FetchCallback& callback,
318    scoped_ptr<std::string> data) const {
319  callback_task_runner_->PostTask(FROM_HERE,
320                                  base::Bind(callback, base::Passed(&data)));
321}
322
323void CloudExternalDataManagerBase::Backend::StartDownload(
324    const std::string& policy) {
325  DCHECK(pending_downloads_.find(policy) != pending_downloads_.end());
326  if (!updater_)
327    return;
328
329  const MetadataEntry& metadata = metadata_[policy];
330  updater_->FetchExternalData(
331      policy,
332      ExternalPolicyDataUpdater::Request(metadata.url,
333                                         metadata.hash,
334                                         GetMaxExternalDataSize(policy)),
335      base::Bind(&CloudExternalDataManagerBase::Backend::OnDownloadSuccess,
336                 base::Unretained(this),
337                 policy,
338                 metadata.hash));
339}
340
341CloudExternalDataManagerBase::CloudExternalDataManagerBase(
342    const PolicyDefinitionList* policy_definitions,
343    scoped_refptr<base::SequencedTaskRunner> backend_task_runner,
344    scoped_refptr<base::SequencedTaskRunner> io_task_runner)
345    : backend_task_runner_(backend_task_runner),
346      io_task_runner_(io_task_runner),
347      backend_(new Backend(policy_definitions,
348                           backend_task_runner_,
349                           base::MessageLoopProxy::current())) {
350}
351
352CloudExternalDataManagerBase::~CloudExternalDataManagerBase() {
353  DCHECK(CalledOnValidThread());
354  io_task_runner_->DeleteSoon(FROM_HERE,
355                              external_policy_data_fetcher_backend_.release());
356  backend_task_runner_->DeleteSoon(FROM_HERE, backend_.release());
357}
358
359void CloudExternalDataManagerBase::SetExternalDataStore(
360    scoped_ptr<CloudExternalDataStore> external_data_store) {
361  DCHECK(CalledOnValidThread());
362  backend_task_runner_->PostTask(FROM_HERE, base::Bind(
363      &Backend::SetExternalDataStore,
364      base::Unretained(backend_.get()),
365      base::Passed(&external_data_store)));
366}
367
368void CloudExternalDataManagerBase::SetPolicyStore(
369    CloudPolicyStore* policy_store) {
370  DCHECK(CalledOnValidThread());
371  CloudExternalDataManager::SetPolicyStore(policy_store);
372  if (policy_store_ && policy_store_->is_initialized())
373    OnPolicyStoreLoaded();
374}
375
376void CloudExternalDataManagerBase::OnPolicyStoreLoaded() {
377  // Collect all external data references made by policies in |policy_store_|
378  // and pass them to the |backend_|.
379  DCHECK(CalledOnValidThread());
380  scoped_ptr<Metadata> metadata(new Metadata);
381  const PolicyMap& policy_map = policy_store_->policy_map();
382  for (PolicyMap::const_iterator it = policy_map.begin();
383       it != policy_map.end(); ++it) {
384    if (!it->second.external_data_fetcher) {
385      // Skip policies that do not reference external data.
386      continue;
387    }
388    const base::DictionaryValue* dict = NULL;
389    std::string url;
390    std::string hex_hash;
391    std::vector<uint8> hash;
392    if (it->second.value && it->second.value->GetAsDictionary(&dict) &&
393        dict->GetStringWithoutPathExpansion("url", &url) &&
394        dict->GetStringWithoutPathExpansion("hash", &hex_hash) &&
395        !url.empty() && !hex_hash.empty() &&
396        base::HexStringToBytes(hex_hash, &hash)) {
397      // Add the external data reference to |metadata| if it is valid (URL and
398      // hash are not empty, hash can be decoded as a hex string).
399      (*metadata)[it->first] =
400          MetadataEntry(url, std::string(hash.begin(), hash.end()));
401    }
402  }
403
404  backend_task_runner_->PostTask(FROM_HERE, base::Bind(
405      &Backend::OnMetadataUpdated,
406      base::Unretained(backend_.get()),
407      base::Passed(&metadata)));
408}
409
410void CloudExternalDataManagerBase::Connect(
411    scoped_refptr<net::URLRequestContextGetter> request_context) {
412  DCHECK(CalledOnValidThread());
413  DCHECK(!external_policy_data_fetcher_backend_);
414  external_policy_data_fetcher_backend_.reset(
415      new ExternalPolicyDataFetcherBackend(io_task_runner_,
416                                           request_context));
417  backend_task_runner_->PostTask(FROM_HERE, base::Bind(
418      &Backend::Connect,
419      base::Unretained(backend_.get()),
420      base::Passed(external_policy_data_fetcher_backend_->CreateFrontend(
421          backend_task_runner_))));
422}
423
424void CloudExternalDataManagerBase::Disconnect() {
425  DCHECK(CalledOnValidThread());
426  io_task_runner_->DeleteSoon(FROM_HERE,
427                              external_policy_data_fetcher_backend_.release());
428  backend_task_runner_->PostTask(FROM_HERE, base::Bind(
429      &Backend::Disconnect, base::Unretained(backend_.get())));
430}
431
432void CloudExternalDataManagerBase::Fetch(
433    const std::string& policy,
434    const ExternalDataFetcher::FetchCallback& callback) {
435  DCHECK(CalledOnValidThread());
436  backend_task_runner_->PostTask(FROM_HERE, base::Bind(
437      &Backend::Fetch, base::Unretained(backend_.get()), policy, callback));
438}
439
440// static
441void CloudExternalDataManagerBase::SetMaxExternalDataSizeForTesting(
442    int max_size) {
443  max_external_data_size_for_testing = max_size;
444}
445
446void CloudExternalDataManagerBase::FetchAll() {
447  DCHECK(CalledOnValidThread());
448  backend_task_runner_->PostTask(FROM_HERE, base::Bind(
449      &Backend::FetchAll, base::Unretained(backend_.get())));
450}
451
452}  // namespace policy
453