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