app_pack_updater.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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/files/file_enumerator.h" 11#include "base/location.h" 12#include "base/stl_util.h" 13#include "base/strings/string_util.h" 14#include "base/values.h" 15#include "base/version.h" 16#include "chrome/browser/browser_process.h" 17#include "chrome/browser/chrome_notification_types.h" 18#include "chrome/browser/chromeos/policy/enterprise_install_attributes.h" 19#include "chrome/browser/chromeos/settings/cros_settings.h" 20#include "chrome/browser/chromeos/settings/cros_settings_names.h" 21#include "chrome/browser/extensions/crx_installer.h" 22#include "chrome/browser/extensions/external_loader.h" 23#include "chrome/browser/extensions/external_provider_impl.h" 24#include "chrome/browser/extensions/updater/extension_downloader.h" 25#include "chrome/common/extensions/extension.h" 26#include "chrome/common/extensions/extension_constants.h" 27#include "content/public/browser/browser_thread.h" 28#include "content/public/browser/notification_details.h" 29#include "content/public/browser/notification_service.h" 30#include "content/public/browser/notification_source.h" 31 32using content::BrowserThread; 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_RETAIL_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_RETAIL_KIOSK and the 95 // app pack 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_RETAIL_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 (!base::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 = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES | 254 base::FileEnumerator::SHOW_SYM_LINKS; 255 base::FileEnumerator enumerator(dir, false /* recursive */, types); 256 257 for (base::FilePath path = enumerator.Next(); 258 !path.empty(); path = enumerator.Next()) { 259 base::FileEnumerator::FileInfo info = enumerator.GetInfo(); 260 std::string basename = path.BaseName().value(); 261 262 if (info.IsDirectory() || file_util::IsLink(info.GetName())) { 263 LOG(ERROR) << "Erasing bad file in AppPack directory: " << basename; 264 base::DeleteFile(path, true /* recursive */); 265 continue; 266 } 267 268 // crx files in the cache are named <extension-id>-<version>.crx. 269 std::string id; 270 std::string version; 271 272 if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) { 273 size_t n = basename.find('-'); 274 if (n != std::string::npos && n + 1 < basename.size() - 4) { 275 id = basename.substr(0, n); 276 // Size of |version| = total size - "<id>" - "-" - ".crx" 277 version = basename.substr(n + 1, basename.size() - 5 - id.size()); 278 } 279 } 280 281 if (!extensions::Extension::IdIsValid(id)) { 282 LOG(ERROR) << "Bad AppPack extension id in cache: " << id; 283 id.clear(); 284 } else if (!ContainsKey(*valid_ids, id)) { 285 LOG(WARNING) << basename << " is in the cache but is not configured by " 286 << "the AppPack policy, and will be erased."; 287 id.clear(); 288 } 289 290 if (!Version(version).IsValid()) { 291 LOG(ERROR) << "Bad AppPack extension version in cache: " << version; 292 version.clear(); 293 } 294 295 if (id.empty() || version.empty()) { 296 LOG(ERROR) << "Invalid file in AppPack cache, erasing: " << basename; 297 base::DeleteFile(path, true /* recursive */); 298 continue; 299 } 300 301 // Enforce a lower-case id. 302 id = StringToLowerASCII(id); 303 304 // File seems good so far. Make sure there isn't another entry with the 305 // same id but a different version. 306 307 if (ContainsKey(*entries, id)) { 308 LOG(ERROR) << "Found two AppPack files for the same extension, will " 309 "erase the oldest version"; 310 CacheEntry& entry = (*entries)[id]; 311 Version vEntry(entry.cached_version); 312 Version vCurrent(version); 313 DCHECK(vEntry.IsValid()); 314 DCHECK(vCurrent.IsValid()); 315 if (vEntry.CompareTo(vCurrent) < 0) { 316 base::DeleteFile(base::FilePath(entry.path), true /* recursive */); 317 entry.path = path.value(); 318 } else { 319 base::DeleteFile(path, true /* recursive */); 320 } 321 continue; 322 } 323 324 // This is the only file for this |id| so far, add it. 325 326 CacheEntry& entry = (*entries)[id]; 327 entry.path = path.value(); 328 entry.cached_version = version; 329 } 330} 331 332void AppPackUpdater::OnCacheUpdated(CacheEntryMap* cache_entries) { 333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 334 cached_extensions_.swap(*cache_entries); 335 336 CacheEntryMap::iterator it = cached_extensions_.find(screen_saver_id_); 337 if (it != cached_extensions_.end()) 338 SetScreenSaverPath(base::FilePath(it->second.path)); 339 else 340 SetScreenSaverPath(base::FilePath()); 341 342 VLOG(1) << "Updated AppPack cache, there are " << cached_extensions_.size() 343 << " extensions cached and " 344 << (screen_saver_path_.empty() ? "no" : "the") << " screensaver"; 345 UpdateExtensionLoader(); 346 DownloadMissingExtensions(); 347} 348 349void AppPackUpdater::UpdateExtensionLoader() { 350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 351 if (!extension_loader_) { 352 VLOG(1) << "No AppPack loader created yet, not pushing extensions."; 353 return; 354 } 355 356 // Build a DictionaryValue with the format that 357 // extensions::ExternalProviderImpl expects, containing info about the locally 358 // cached extensions. 359 360 scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue()); 361 for (CacheEntryMap::iterator it = cached_extensions_.begin(); 362 it != cached_extensions_.end(); ++it) { 363 const std::string& id = it->first; 364 // The screensaver isn't installed into the Profile. 365 if (id == screen_saver_id_) 366 continue; 367 368 base::DictionaryValue* dict = new base::DictionaryValue(); 369 dict->SetString(extensions::ExternalProviderImpl::kExternalCrx, 370 it->second.path); 371 dict->SetString(extensions::ExternalProviderImpl::kExternalVersion, 372 it->second.cached_version); 373 374 // Include this optional flag if the extension's update url is the Webstore. 375 PolicyEntryMap::iterator policy_entry = app_pack_extensions_.find(id); 376 if (policy_entry != app_pack_extensions_.end() && 377 extension_urls::IsWebstoreUpdateUrl( 378 GURL(policy_entry->second))) { 379 dict->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true); 380 } 381 382 prefs->Set(it->first, dict); 383 384 VLOG(1) << "Updating AppPack extension loader, added " << it->second.path; 385 } 386 387 extension_loader_->SetCurrentAppPackExtensions(prefs.Pass()); 388} 389 390void AppPackUpdater::DownloadMissingExtensions() { 391 // Check for updates for all extensions configured by the policy. Some of 392 // them may already be in the cache; only those with updated version will be 393 // downloaded, in that case. 394 if (!downloader_.get()) { 395 downloader_.reset(new extensions::ExtensionDownloader(this, 396 request_context_)); 397 } 398 for (PolicyEntryMap::iterator it = app_pack_extensions_.begin(); 399 it != app_pack_extensions_.end(); ++it) { 400 downloader_->AddPendingExtension(it->first, GURL(it->second), 0); 401 } 402 VLOG(1) << "Downloading AppPack update manifest now"; 403 downloader_->StartAllPending(); 404} 405 406void AppPackUpdater::OnExtensionDownloadFailed( 407 const std::string& id, 408 extensions::ExtensionDownloaderDelegate::Error error, 409 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, 410 const std::set<int>& request_ids) { 411 if (error == NO_UPDATE_AVAILABLE) { 412 if (!ContainsKey(cached_extensions_, id)) 413 LOG(ERROR) << "AppPack extension " << id << " not found on update server"; 414 } else { 415 LOG(ERROR) << "AppPack failed to download extension " << id 416 << ", error " << error; 417 } 418} 419 420void AppPackUpdater::OnExtensionDownloadFinished( 421 const std::string& id, 422 const base::FilePath& path, 423 const GURL& download_url, 424 const std::string& version, 425 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, 426 const std::set<int>& request_ids) { 427 // The explicit copy ctors are to make sure that Bind() binds a copy and not 428 // a reference to the arguments. 429 PostBlockingTask(FROM_HERE, 430 base::Bind(&AppPackUpdater::BlockingInstallCacheEntry, 431 weak_ptr_factory_.GetWeakPtr(), 432 std::string(id), 433 base::FilePath(path), 434 std::string(version))); 435} 436 437void AppPackUpdater::OnBlacklistDownloadFinished( 438 const std::string& data, 439 const std::string& package_hash, 440 const std::string& version, 441 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result, 442 const std::set<int>& request_ids) { 443 NOTREACHED(); 444} 445 446bool AppPackUpdater::IsExtensionPending(const std::string& id) { 447 // Pending means that there is no installed version yet. 448 return ContainsKey(app_pack_extensions_, id) && 449 !ContainsKey(cached_extensions_, id); 450} 451 452bool AppPackUpdater::GetExtensionExistingVersion(const std::string& id, 453 std::string* version) { 454 if (!ContainsKey(app_pack_extensions_, id) || 455 !ContainsKey(cached_extensions_, id)) { 456 return false; 457 } 458 459 *version = cached_extensions_[id].cached_version; 460 return true; 461} 462 463// static 464void AppPackUpdater::BlockingInstallCacheEntry( 465 base::WeakPtr<AppPackUpdater> app_pack_updater, 466 const std::string& id, 467 const base::FilePath& path, 468 const std::string& version) { 469 Version version_validator(version); 470 if (!version_validator.IsValid()) { 471 LOG(ERROR) << "AppPack downloaded extension " << id << " but got bad " 472 << "version: " << version; 473 base::DeleteFile(path, true /* recursive */); 474 return; 475 } 476 477 std::string basename = id + "-" + version + kCRXFileExtension; 478 base::FilePath cache_dir(kAppPackCacheDir); 479 base::FilePath cached_crx_path = cache_dir.Append(basename); 480 481 if (base::PathExists(cached_crx_path)) { 482 LOG(WARNING) << "AppPack downloaded a crx whose filename will overwrite " 483 << "an existing cached crx."; 484 base::DeleteFile(cached_crx_path, true /* recursive */); 485 } 486 487 if (!base::DirectoryExists(cache_dir)) { 488 LOG(ERROR) << "AppPack cache directory does not exist, creating now: " 489 << cache_dir.value(); 490 if (!file_util::CreateDirectory(cache_dir)) { 491 LOG(ERROR) << "Failed to create the AppPack cache dir!"; 492 base::DeleteFile(path, true /* recursive */); 493 return; 494 } 495 } 496 497 if (!base::Move(path, cached_crx_path)) { 498 LOG(ERROR) << "Failed to move AppPack crx from " << path.value() 499 << " to " << cached_crx_path.value(); 500 base::DeleteFile(path, true /* recursive */); 501 return; 502 } 503 504 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 505 base::Bind(&AppPackUpdater::OnCacheEntryInstalled, 506 app_pack_updater, 507 std::string(id), 508 cached_crx_path.value(), 509 std::string(version))); 510} 511 512void AppPackUpdater::OnCacheEntryInstalled(const std::string& id, 513 const std::string& path, 514 const std::string& version) { 515 VLOG(1) << "AppPack installed a new extension in the cache: " << path; 516 // Add to the list of cached extensions. 517 CacheEntry& entry = cached_extensions_[id]; 518 entry.path = path; 519 entry.cached_version = version; 520 521 if (id == screen_saver_id_) { 522 VLOG(1) << "AppPack got the screen saver extension at " << path; 523 SetScreenSaverPath(base::FilePath(path)); 524 } else { 525 UpdateExtensionLoader(); 526 } 527} 528 529void AppPackUpdater::OnDamagedFileDetected(const base::FilePath& path) { 530 // Search for |path| in |cached_extensions_|, and delete it if found. 531 for (CacheEntryMap::iterator it = cached_extensions_.begin(); 532 it != cached_extensions_.end(); ++it) { 533 if (it->second.path == path.value()) { 534 LOG(ERROR) << "AppPack extension at " << path.value() << " failed to " 535 << "install, deleting it."; 536 cached_extensions_.erase(it); 537 UpdateExtensionLoader(); 538 539 // The file will be downloaded again on the next restart. 540 BrowserThread::PostTask( 541 BrowserThread::FILE, FROM_HERE, 542 base::Bind(base::IgnoreResult(base::DeleteFile), path, true)); 543 544 // Don't try to DownloadMissingExtensions() from here, 545 // since it can cause a fail/retry loop. 546 break; 547 } 548 } 549} 550 551void AppPackUpdater::PostBlockingTask(const tracked_objects::Location& location, 552 const base::Closure& task) { 553 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 554 worker_pool_token_, location, task, 555 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 556} 557 558void AppPackUpdater::SetScreenSaverPath(const base::FilePath& path) { 559 // Don't invoke the callback if the path isn't changing. 560 if (path != screen_saver_path_) { 561 screen_saver_path_ = path; 562 if (!screen_saver_update_callback_.is_null()) 563 screen_saver_update_callback_.Run(screen_saver_path_); 564 } 565} 566 567} // namespace policy 568