app_pack_updater.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 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/app_pack_updater.h" 6 7#include "base/bind.h" 8#include "base/bind_helpers.h" 9#include "base/file_util.h" 10#include "base/location.h" 11#include "base/stl_util.h" 12#include "base/string_util.h" 13#include "base/values.h" 14#include "base/version.h" 15#include "chrome/browser/browser_process.h" 16#include "chrome/browser/chromeos/policy/enterprise_install_attributes.h" 17#include "chrome/browser/chromeos/settings/cros_settings.h" 18#include "chrome/browser/chromeos/settings/cros_settings_names.h" 19#include "chrome/browser/extensions/crx_installer.h" 20#include "chrome/browser/extensions/external_loader.h" 21#include "chrome/browser/extensions/external_provider_impl.h" 22#include "chrome/browser/extensions/updater/extension_downloader.h" 23#include "chrome/common/chrome_notification_types.h" 24#include "chrome/common/extensions/extension.h" 25#include "chrome/common/extensions/extension_constants.h" 26#include "content/public/browser/browser_thread.h" 27#include "content/public/browser/notification_details.h" 28#include "content/public/browser/notification_service.h" 29#include "content/public/browser/notification_source.h" 30 31using content::BrowserThread; 32using file_util::FileEnumerator; 33 34namespace policy { 35 36namespace { 37 38// Directory where the AppPack extensions are cached. 39const char kAppPackCacheDir[] = "/var/cache/app_pack"; 40 41// File name extension for CRX files (not case sensitive). 42const char kCRXFileExtension[] = ".crx"; 43 44} // namespace 45 46// A custom extensions::ExternalLoader that the AppPackUpdater creates and uses 47// to publish AppPack updates to the extensions system. 48class AppPackExternalLoader 49 : public extensions::ExternalLoader, 50 public base::SupportsWeakPtr<AppPackExternalLoader> { 51 public: 52 AppPackExternalLoader() {} 53 54 // Used by the AppPackUpdater to update the current list of extensions. 55 // The format of |prefs| is detailed in the extensions::ExternalLoader/ 56 // Provider headers. 57 void SetCurrentAppPackExtensions(scoped_ptr<base::DictionaryValue> prefs) { 58 app_pack_prefs_.Swap(prefs.get()); 59 StartLoading(); 60 } 61 62 // Implementation of extensions::ExternalLoader: 63 virtual void StartLoading() OVERRIDE { 64 prefs_.reset(app_pack_prefs_.DeepCopy()); 65 VLOG(1) << "AppPack extension loader publishing " 66 << app_pack_prefs_.size() << " crx files."; 67 LoadFinished(); 68 } 69 70 protected: 71 virtual ~AppPackExternalLoader() {} 72 73 private: 74 base::DictionaryValue app_pack_prefs_; 75 76 DISALLOW_COPY_AND_ASSIGN(AppPackExternalLoader); 77}; 78 79AppPackUpdater::AppPackUpdater(net::URLRequestContextGetter* request_context, 80 EnterpriseInstallAttributes* install_attributes) 81 : weak_ptr_factory_(this), 82 initialized_(false), 83 created_extension_loader_(false), 84 request_context_(request_context), 85 install_attributes_(install_attributes) { 86 chromeos::CrosSettings::Get()->AddSettingsObserver(chromeos::kAppPack, this); 87 88 if (install_attributes_->GetMode() == DEVICE_MODE_KIOSK) { 89 // Already in Kiosk mode, start loading. 90 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 91 base::Bind(&AppPackUpdater::Init, 92 weak_ptr_factory_.GetWeakPtr())); 93 } else { 94 // Linger until the device switches to DEVICE_MODE_KIOSK and the app pack 95 // device setting appears. 96 } 97} 98 99AppPackUpdater::~AppPackUpdater() { 100 chromeos::CrosSettings::Get()->RemoveSettingsObserver( 101 chromeos::kAppPack, this); 102} 103 104extensions::ExternalLoader* AppPackUpdater::CreateExternalLoader() { 105 if (created_extension_loader_) { 106 NOTREACHED(); 107 return NULL; 108 } 109 created_extension_loader_ = true; 110 AppPackExternalLoader* loader = new AppPackExternalLoader(); 111 extension_loader_ = loader->AsWeakPtr(); 112 113 // The cache may have been already checked. In that case, load the current 114 // extensions into the loader immediately. 115 UpdateExtensionLoader(); 116 117 return loader; 118} 119 120void AppPackUpdater::SetScreenSaverUpdateCallback( 121 const AppPackUpdater::ScreenSaverUpdateCallback& callback) { 122 screen_saver_update_callback_ = callback; 123 if (!screen_saver_update_callback_.is_null() && !screen_saver_path_.empty()) { 124 BrowserThread::PostTask( 125 BrowserThread::UI, FROM_HERE, 126 base::Bind(screen_saver_update_callback_, screen_saver_path_)); 127 } 128} 129 130void AppPackUpdater::Init() { 131 if (initialized_) 132 return; 133 134 initialized_ = true; 135 worker_pool_token_ = BrowserThread::GetBlockingPool()->GetSequenceToken(); 136 notification_registrar_.Add( 137 this, 138 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, 139 content::NotificationService::AllBrowserContextsAndSources()); 140 LoadPolicy(); 141} 142 143void AppPackUpdater::Observe(int type, 144 const content::NotificationSource& source, 145 const content::NotificationDetails& details) { 146 switch (type) { 147 case chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED: 148 DCHECK_EQ(chromeos::kAppPack, 149 *content::Details<const std::string>(details).ptr()); 150 if (install_attributes_->GetMode() == DEVICE_MODE_KIOSK) { 151 if (!initialized_) 152 Init(); 153 else 154 LoadPolicy(); 155 } 156 break; 157 158 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: { 159 extensions::CrxInstaller* installer = 160 content::Source<extensions::CrxInstaller>(source).ptr(); 161 OnDamagedFileDetected(installer->source_file()); 162 break; 163 } 164 165 default: 166 NOTREACHED(); 167 } 168} 169 170void AppPackUpdater::LoadPolicy() { 171 chromeos::CrosSettings* settings = chromeos::CrosSettings::Get(); 172 if (chromeos::CrosSettingsProvider::TRUSTED != settings->PrepareTrustedValues( 173 base::Bind(&AppPackUpdater::LoadPolicy, 174 weak_ptr_factory_.GetWeakPtr()))) { 175 return; 176 } 177 178 app_pack_extensions_.clear(); 179 const base::Value* value = settings->GetPref(chromeos::kAppPack); 180 const base::ListValue* list = NULL; 181 if (value && value->GetAsList(&list)) { 182 for (base::ListValue::const_iterator it = list->begin(); 183 it != list->end(); ++it) { 184 base::DictionaryValue* dict = NULL; 185 if (!(*it)->GetAsDictionary(&dict)) { 186 LOG(WARNING) << "AppPack entry is not a dictionary, ignoring."; 187 continue; 188 } 189 std::string id; 190 std::string update_url; 191 if (dict->GetString(chromeos::kAppPackKeyExtensionId, &id) && 192 dict->GetString(chromeos::kAppPackKeyUpdateUrl, &update_url)) { 193 app_pack_extensions_[id] = update_url; 194 } else { 195 LOG(WARNING) << "Failed to read required fields for an AppPack entry, " 196 << "ignoring."; 197 } 198 } 199 } 200 201 VLOG(1) << "Refreshed AppPack policy, got " << app_pack_extensions_.size() 202 << " entries."; 203 204 value = settings->GetPref(chromeos::kScreenSaverExtensionId); 205 if (!value || !value->GetAsString(&screen_saver_id_)) { 206 screen_saver_id_.clear(); 207 SetScreenSaverPath(base::FilePath()); 208 } 209 210 CheckCacheNow(); 211} 212 213void AppPackUpdater::CheckCacheNow() { 214 std::set<std::string>* valid_ids = new std::set<std::string>(); 215 for (PolicyEntryMap::iterator it = app_pack_extensions_.begin(); 216 it != app_pack_extensions_.end(); ++it) { 217 valid_ids->insert(it->first); 218 } 219 PostBlockingTask(FROM_HERE, 220 base::Bind(&AppPackUpdater::BlockingCheckCache, 221 weak_ptr_factory_.GetWeakPtr(), 222 base::Owned(valid_ids))); 223} 224 225// static 226void AppPackUpdater::BlockingCheckCache( 227 base::WeakPtr<AppPackUpdater> app_pack_updater, 228 const std::set<std::string>* valid_ids) { 229 CacheEntryMap* entries = new CacheEntryMap(); 230 BlockingCheckCacheInternal(valid_ids, entries); 231 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 232 base::Bind(&AppPackUpdater::OnCacheUpdated, 233 app_pack_updater, 234 base::Owned(entries))); 235} 236 237// static 238void AppPackUpdater::BlockingCheckCacheInternal( 239 const std::set<std::string>* valid_ids, 240 CacheEntryMap* entries) { 241 // Start by verifying that the cache dir exists. 242 base::FilePath dir(kAppPackCacheDir); 243 if (!file_util::DirectoryExists(dir)) { 244 // Create it now. 245 if (!file_util::CreateDirectory(dir)) 246 LOG(ERROR) << "Failed to create AppPack directory at " << dir.value(); 247 // Nothing else to do. 248 return; 249 } 250 251 // Enumerate all the files in the cache |dir|, including directories 252 // and symlinks. Each unrecognized file will be erased. 253 int types = FileEnumerator::FILES | FileEnumerator::DIRECTORIES | 254 FileEnumerator::SHOW_SYM_LINKS; 255 FileEnumerator enumerator(dir, false /* recursive */, types); 256 257 for (base::FilePath path = enumerator.Next(); 258 !path.empty(); path = enumerator.Next()) { 259 FileEnumerator::FindInfo info; 260 enumerator.GetFindInfo(&info); 261 std::string basename = path.BaseName().value(); 262 263 if (FileEnumerator::IsDirectory(info) || 264 file_util::IsLink(FileEnumerator::GetFilename(info))) { 265 LOG(ERROR) << "Erasing bad file in AppPack directory: " << basename; 266 file_util::Delete(path, true /* recursive */); 267 continue; 268 } 269 270 // crx files in the cache are named <extension-id>-<version>.crx. 271 std::string id; 272 std::string version; 273 274 if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) { 275 size_t n = basename.find('-'); 276 if (n != std::string::npos && n + 1 < basename.size() - 4) { 277 id = basename.substr(0, n); 278 // Size of |version| = total size - "<id>" - "-" - ".crx" 279 version = basename.substr(n + 1, basename.size() - 5 - id.size()); 280 } 281 } 282 283 if (!extensions::Extension::IdIsValid(id)) { 284 LOG(ERROR) << "Bad AppPack extension id in cache: " << id; 285 id.clear(); 286 } else if (!ContainsKey(*valid_ids, id)) { 287 LOG(WARNING) << basename << " is in the cache but is not configured by " 288 << "the AppPack policy, and will be erased."; 289 id.clear(); 290 } 291 292 if (!Version(version).IsValid()) { 293 LOG(ERROR) << "Bad AppPack extension version in cache: " << version; 294 version.clear(); 295 } 296 297 if (id.empty() || version.empty()) { 298 LOG(ERROR) << "Invalid file in AppPack cache, erasing: " << basename; 299 file_util::Delete(path, true /* recursive */); 300 continue; 301 } 302 303 // Enforce a lower-case id. 304 id = StringToLowerASCII(id); 305 306 // File seems good so far. Make sure there isn't another entry with the 307 // same id but a different version. 308 309 if (ContainsKey(*entries, id)) { 310 LOG(ERROR) << "Found two AppPack files for the same extension, will " 311 "erase the oldest version"; 312 CacheEntry& entry = (*entries)[id]; 313 Version vEntry(entry.cached_version); 314 Version vCurrent(version); 315 DCHECK(vEntry.IsValid()); 316 DCHECK(vCurrent.IsValid()); 317 if (vEntry.CompareTo(vCurrent) < 0) { 318 file_util::Delete(base::FilePath(entry.path), true /* recursive */); 319 entry.path = path.value(); 320 } else { 321 file_util::Delete(path, true /* recursive */); 322 } 323 continue; 324 } 325 326 // This is the only file for this |id| so far, add it. 327 328 CacheEntry& entry = (*entries)[id]; 329 entry.path = path.value(); 330 entry.cached_version = version; 331 } 332} 333 334void AppPackUpdater::OnCacheUpdated(CacheEntryMap* cache_entries) { 335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 336 cached_extensions_.swap(*cache_entries); 337 338 CacheEntryMap::iterator it = cached_extensions_.find(screen_saver_id_); 339 if (it != cached_extensions_.end()) 340 SetScreenSaverPath(base::FilePath(it->second.path)); 341 else 342 SetScreenSaverPath(base::FilePath()); 343 344 VLOG(1) << "Updated AppPack cache, there are " << cached_extensions_.size() 345 << " extensions cached and " 346 << (screen_saver_path_.empty() ? "no" : "the") << " screensaver"; 347 UpdateExtensionLoader(); 348 DownloadMissingExtensions(); 349} 350 351void AppPackUpdater::UpdateExtensionLoader() { 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 353 if (!extension_loader_) { 354 VLOG(1) << "No AppPack loader created yet, not pushing extensions."; 355 return; 356 } 357 358 // Build a DictionaryValue with the format that 359 // extensions::ExternalProviderImpl expects, containing info about the locally 360 // cached extensions. 361 362 scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue()); 363 for (CacheEntryMap::iterator it = cached_extensions_.begin(); 364 it != cached_extensions_.end(); ++it) { 365 const std::string& id = it->first; 366 // The screensaver isn't installed into the Profile. 367 if (id == screen_saver_id_) 368 continue; 369 370 base::DictionaryValue* dict = new base::DictionaryValue(); 371 dict->SetString(extensions::ExternalProviderImpl::kExternalCrx, 372 it->second.path); 373 dict->SetString(extensions::ExternalProviderImpl::kExternalVersion, 374 it->second.cached_version); 375 376 // Include this optional flag if the extension's update url is the Webstore. 377 PolicyEntryMap::iterator policy_entry = app_pack_extensions_.find(id); 378 if (policy_entry != app_pack_extensions_.end() && 379 extension_urls::IsWebstoreUpdateUrl( 380 GURL(policy_entry->second))) { 381 dict->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true); 382 } 383 384 prefs->Set(it->first, dict); 385 386 VLOG(1) << "Updating AppPack extension loader, added " << it->second.path; 387 } 388 389 extension_loader_->SetCurrentAppPackExtensions(prefs.Pass()); 390} 391 392void AppPackUpdater::DownloadMissingExtensions() { 393 // Check for updates for all extensions configured by the policy. Some of 394 // them may already be in the cache; only those with updated version will be 395 // downloaded, in that case. 396 if (!downloader_.get()) { 397 downloader_.reset(new extensions::ExtensionDownloader(this, 398 request_context_)); 399 } 400 for (PolicyEntryMap::iterator it = app_pack_extensions_.begin(); 401 it != app_pack_extensions_.end(); ++it) { 402 downloader_->AddPendingExtension(it->first, GURL(it->second), 0); 403 } 404 VLOG(1) << "Downloading AppPack update manifest now"; 405 downloader_->StartAllPending(); 406} 407 408void AppPackUpdater::OnExtensionDownloadFailed( 409 const std::string& id, 410 extensions::ExtensionDownloaderDelegate::Error error, 411 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, 412 const std::set<int>& request_ids) { 413 if (error == NO_UPDATE_AVAILABLE) { 414 if (!ContainsKey(cached_extensions_, id)) 415 LOG(ERROR) << "AppPack extension " << id << " not found on update server"; 416 } else { 417 LOG(ERROR) << "AppPack failed to download extension " << id 418 << ", error " << error; 419 } 420} 421 422void AppPackUpdater::OnExtensionDownloadFinished( 423 const std::string& id, 424 const base::FilePath& path, 425 const GURL& download_url, 426 const std::string& version, 427 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, 428 const std::set<int>& request_ids) { 429 // The explicit copy ctors are to make sure that Bind() binds a copy and not 430 // a reference to the arguments. 431 PostBlockingTask(FROM_HERE, 432 base::Bind(&AppPackUpdater::BlockingInstallCacheEntry, 433 weak_ptr_factory_.GetWeakPtr(), 434 std::string(id), 435 base::FilePath(path), 436 std::string(version))); 437} 438 439void AppPackUpdater::OnBlacklistDownloadFinished( 440 const std::string& data, 441 const std::string& package_hash, 442 const std::string& version, 443 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, 444 const std::set<int>& request_ids) { 445 NOTREACHED(); 446} 447 448bool AppPackUpdater::IsExtensionPending(const std::string& id) { 449 // Pending means that there is no installed version yet. 450 return ContainsKey(app_pack_extensions_, id) && 451 !ContainsKey(cached_extensions_, id); 452} 453 454bool AppPackUpdater::GetExtensionExistingVersion(const std::string& id, 455 std::string* version) { 456 if (!ContainsKey(app_pack_extensions_, id) || 457 !ContainsKey(cached_extensions_, id)) { 458 return false; 459 } 460 461 *version = cached_extensions_[id].cached_version; 462 return true; 463} 464 465// static 466void AppPackUpdater::BlockingInstallCacheEntry( 467 base::WeakPtr<AppPackUpdater> app_pack_updater, 468 const std::string& id, 469 const base::FilePath& path, 470 const std::string& version) { 471 Version version_validator(version); 472 if (!version_validator.IsValid()) { 473 LOG(ERROR) << "AppPack downloaded extension " << id << " but got bad " 474 << "version: " << version; 475 file_util::Delete(path, true /* recursive */); 476 return; 477 } 478 479 std::string basename = id + "-" + version + kCRXFileExtension; 480 base::FilePath cache_dir(kAppPackCacheDir); 481 base::FilePath cached_crx_path = cache_dir.Append(basename); 482 483 if (file_util::PathExists(cached_crx_path)) { 484 LOG(WARNING) << "AppPack downloaded a crx whose filename will overwrite " 485 << "an existing cached crx."; 486 file_util::Delete(cached_crx_path, true /* recursive */); 487 } 488 489 if (!file_util::DirectoryExists(cache_dir)) { 490 LOG(ERROR) << "AppPack cache directory does not exist, creating now: " 491 << cache_dir.value(); 492 if (!file_util::CreateDirectory(cache_dir)) { 493 LOG(ERROR) << "Failed to create the AppPack cache dir!"; 494 file_util::Delete(path, true /* recursive */); 495 return; 496 } 497 } 498 499 if (!file_util::Move(path, cached_crx_path)) { 500 LOG(ERROR) << "Failed to move AppPack crx from " << path.value() 501 << " to " << cached_crx_path.value(); 502 file_util::Delete(path, true /* recursive */); 503 return; 504 } 505 506 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 507 base::Bind(&AppPackUpdater::OnCacheEntryInstalled, 508 app_pack_updater, 509 std::string(id), 510 cached_crx_path.value(), 511 std::string(version))); 512} 513 514void AppPackUpdater::OnCacheEntryInstalled(const std::string& id, 515 const std::string& path, 516 const std::string& version) { 517 VLOG(1) << "AppPack installed a new extension in the cache: " << path; 518 // Add to the list of cached extensions. 519 CacheEntry& entry = cached_extensions_[id]; 520 entry.path = path; 521 entry.cached_version = version; 522 523 if (id == screen_saver_id_) { 524 VLOG(1) << "AppPack got the screen saver extension at " << path; 525 SetScreenSaverPath(base::FilePath(path)); 526 } else { 527 UpdateExtensionLoader(); 528 } 529} 530 531void AppPackUpdater::OnDamagedFileDetected(const base::FilePath& path) { 532 // Search for |path| in |cached_extensions_|, and delete it if found. 533 for (CacheEntryMap::iterator it = cached_extensions_.begin(); 534 it != cached_extensions_.end(); ++it) { 535 if (it->second.path == path.value()) { 536 LOG(ERROR) << "AppPack extension at " << path.value() << " failed to " 537 << "install, deleting it."; 538 cached_extensions_.erase(it); 539 UpdateExtensionLoader(); 540 541 // The file will be downloaded again on the next restart. 542 BrowserThread::PostTask( 543 BrowserThread::FILE, FROM_HERE, 544 base::Bind(base::IgnoreResult(file_util::Delete), path, true)); 545 546 // Don't try to DownloadMissingExtensions() from here, 547 // since it can cause a fail/retry loop. 548 break; 549 } 550 } 551} 552 553void AppPackUpdater::PostBlockingTask(const tracked_objects::Location& location, 554 const base::Closure& task) { 555 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 556 worker_pool_token_, location, task, 557 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 558} 559 560void AppPackUpdater::SetScreenSaverPath(const base::FilePath& path) { 561 // Don't invoke the callback if the path isn't changing. 562 if (path != screen_saver_path_) { 563 screen_saver_path_ = path; 564 if (!screen_saver_update_callback_.is_null()) 565 screen_saver_update_callback_.Run(screen_saver_path_); 566 } 567} 568 569} // namespace policy 570