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