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