1 // Copyright (c) 2014 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/extensions/extension_assets_manager_chromeos.h" 6 7 #include <map> 8 #include <vector> 9 10 #include "base/command_line.h" 11 #include "base/files/file_util.h" 12 #include "base/memory/singleton.h" 13 #include "base/prefs/pref_registry_simple.h" 14 #include "base/prefs/pref_service.h" 15 #include "base/prefs/scoped_user_pref_update.h" 16 #include "base/sequenced_task_runner.h" 17 #include "base/sys_info.h" 18 #include "chrome/browser/browser_process.h" 19 #include "chrome/browser/chromeos/profiles/profile_helper.h" 20 #include "chrome/browser/extensions/extension_service.h" 21 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/common/extensions/manifest_url_handler.h" 23 #include "chromeos/chromeos_switches.h" 24 #include "components/user_manager/user_manager.h" 25 #include "content/public/browser/browser_thread.h" 26 #include "extensions/browser/extension_prefs.h" 27 #include "extensions/browser/extension_system.h" 28 #include "extensions/common/extension.h" 29 #include "extensions/common/extension_urls.h" 30 #include "extensions/common/file_util.h" 31 #include "extensions/common/manifest.h" 32 33 using content::BrowserThread; 34 35 namespace extensions { 36 namespace { 37 38 // Path to shared extensions install dir. 39 const char kSharedExtensionsDir[] = "/var/cache/shared_extensions"; 40 41 // Shared install dir overrider for tests only. 42 static const base::FilePath* g_shared_install_dir_override = NULL; 43 44 // This helper class lives on UI thread only. Main purpose of this class is to 45 // track shared installation in progress between multiple profiles. 46 class ExtensionAssetsManagerHelper { 47 public: 48 // Info about pending install request. 49 struct PendingInstallInfo { 50 base::FilePath unpacked_extension_root; 51 base::FilePath local_install_dir; 52 Profile* profile; 53 ExtensionAssetsManager::InstallExtensionCallback callback; 54 }; 55 typedef std::vector<PendingInstallInfo> PendingInstallList; 56 57 static ExtensionAssetsManagerHelper* GetInstance() { 58 DCHECK_CURRENTLY_ON(BrowserThread::UI); 59 return Singleton<ExtensionAssetsManagerHelper>::get(); 60 } 61 62 // Remember that shared install is in progress. Return true if there is no 63 // other installs for given id and version. 64 bool RecordSharedInstall( 65 const std::string& id, 66 const std::string& version, 67 const base::FilePath& unpacked_extension_root, 68 const base::FilePath& local_install_dir, 69 Profile* profile, 70 ExtensionAssetsManager::InstallExtensionCallback callback) { 71 PendingInstallInfo install_info; 72 install_info.unpacked_extension_root = unpacked_extension_root; 73 install_info.local_install_dir = local_install_dir; 74 install_info.profile = profile; 75 install_info.callback = callback; 76 77 std::vector<PendingInstallInfo>& callbacks = 78 install_queue_[InstallQueue::key_type(id, version)]; 79 callbacks.push_back(install_info); 80 81 return callbacks.size() == 1; 82 } 83 84 // Remove record about shared installation in progress and return 85 // |pending_installs|. 86 void SharedInstallDone(const std::string& id, 87 const std::string& version, 88 PendingInstallList* pending_installs) { 89 InstallQueue::iterator it = install_queue_.find( 90 InstallQueue::key_type(id, version)); 91 DCHECK(it != install_queue_.end()); 92 pending_installs->swap(it->second); 93 install_queue_.erase(it); 94 } 95 96 private: 97 friend struct DefaultSingletonTraits<ExtensionAssetsManagerHelper>; 98 99 ExtensionAssetsManagerHelper() {} 100 ~ExtensionAssetsManagerHelper() {} 101 102 // Extension ID + version pair. 103 typedef std::pair<std::string, std::string> InstallItem; 104 105 // Queue of pending installs in progress. 106 typedef std::map<InstallItem, std::vector<PendingInstallInfo> > InstallQueue; 107 108 InstallQueue install_queue_; 109 110 DISALLOW_COPY_AND_ASSIGN(ExtensionAssetsManagerHelper); 111 }; 112 113 } // namespace 114 115 const char ExtensionAssetsManagerChromeOS::kSharedExtensions[] = 116 "SharedExtensions"; 117 118 const char ExtensionAssetsManagerChromeOS::kSharedExtensionPath[] = "path"; 119 120 const char ExtensionAssetsManagerChromeOS::kSharedExtensionUsers[] = "users"; 121 122 ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { } 123 124 ExtensionAssetsManagerChromeOS::~ExtensionAssetsManagerChromeOS() { 125 if (g_shared_install_dir_override) { 126 delete g_shared_install_dir_override; 127 g_shared_install_dir_override = NULL; 128 } 129 } 130 131 // static 132 ExtensionAssetsManagerChromeOS* ExtensionAssetsManagerChromeOS::GetInstance() { 133 return Singleton<ExtensionAssetsManagerChromeOS>::get(); 134 } 135 136 // static 137 void ExtensionAssetsManagerChromeOS::RegisterPrefs( 138 PrefRegistrySimple* registry) { 139 registry->RegisterDictionaryPref(kSharedExtensions); 140 } 141 142 void ExtensionAssetsManagerChromeOS::InstallExtension( 143 const Extension* extension, 144 const base::FilePath& unpacked_extension_root, 145 const base::FilePath& local_install_dir, 146 Profile* profile, 147 InstallExtensionCallback callback) { 148 if (!CanShareAssets(extension, unpacked_extension_root)) { 149 InstallLocalExtension(extension->id(), 150 extension->VersionString(), 151 unpacked_extension_root, 152 local_install_dir, 153 callback); 154 return; 155 } 156 157 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 158 base::Bind(&ExtensionAssetsManagerChromeOS::CheckSharedExtension, 159 extension->id(), 160 extension->VersionString(), 161 unpacked_extension_root, 162 local_install_dir, 163 profile, 164 callback)); 165 } 166 167 void ExtensionAssetsManagerChromeOS::UninstallExtension( 168 const std::string& id, 169 Profile* profile, 170 const base::FilePath& local_install_dir, 171 const base::FilePath& extension_root) { 172 if (local_install_dir.IsParent(extension_root)) { 173 file_util::UninstallExtension(local_install_dir, id); 174 return; 175 } 176 177 if (GetSharedInstallDir().IsParent(extension_root)) { 178 // In some test extensions installed outside local_install_dir emulate 179 // previous behavior that just do nothing in this case. 180 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 181 base::Bind(&ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused, 182 id, 183 profile)); 184 } 185 } 186 187 // static 188 base::FilePath ExtensionAssetsManagerChromeOS::GetSharedInstallDir() { 189 if (g_shared_install_dir_override) 190 return *g_shared_install_dir_override; 191 else 192 return base::FilePath(kSharedExtensionsDir); 193 } 194 195 // static 196 bool ExtensionAssetsManagerChromeOS::IsSharedInstall( 197 const Extension* extension) { 198 return GetSharedInstallDir().IsParent(extension->path()); 199 } 200 201 // static 202 bool ExtensionAssetsManagerChromeOS::CleanUpSharedExtensions( 203 std::multimap<std::string, base::FilePath>* live_extension_paths) { 204 DCHECK_CURRENTLY_ON(BrowserThread::UI); 205 206 PrefService* local_state = g_browser_process->local_state(); 207 // It happens in many unit tests. 208 if (!local_state) 209 return false; 210 211 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); 212 std::vector<std::string> extensions; 213 extensions.reserve(shared_extensions->size()); 214 for (base::DictionaryValue::Iterator it(*shared_extensions); 215 !it.IsAtEnd(); it.Advance()) { 216 extensions.push_back(it.key()); 217 } 218 219 for (std::vector<std::string>::iterator it = extensions.begin(); 220 it != extensions.end(); it++) { 221 base::DictionaryValue* extension_info = NULL; 222 if (!shared_extensions->GetDictionary(*it, &extension_info)) { 223 NOTREACHED(); 224 return false; 225 } 226 if (!CleanUpExtension(*it, extension_info, live_extension_paths)) { 227 return false; 228 } 229 if (!extension_info->size()) 230 shared_extensions->RemoveWithoutPathExpansion(*it, NULL); 231 } 232 233 return true; 234 } 235 236 // static 237 void ExtensionAssetsManagerChromeOS::SetSharedInstallDirForTesting( 238 const base::FilePath& install_dir) { 239 DCHECK(!g_shared_install_dir_override); 240 g_shared_install_dir_override = new base::FilePath(install_dir); 241 } 242 243 // static 244 base::SequencedTaskRunner* ExtensionAssetsManagerChromeOS::GetFileTaskRunner( 245 Profile* profile) { 246 DCHECK_CURRENTLY_ON(BrowserThread::UI); 247 ExtensionService* extension_service = 248 ExtensionSystem::Get(profile)->extension_service(); 249 return extension_service->GetFileTaskRunner(); 250 } 251 252 // static 253 bool ExtensionAssetsManagerChromeOS::CanShareAssets( 254 const Extension* extension, 255 const base::FilePath& unpacked_extension_root) { 256 if (!CommandLine::ForCurrentProcess()->HasSwitch( 257 chromeos::switches::kEnableExtensionAssetsSharing)) { 258 return false; 259 } 260 261 GURL update_url = ManifestURL::GetUpdateURL(extension); 262 if (!update_url.is_empty() && 263 !extension_urls::IsWebstoreUpdateUrl(update_url)) { 264 return false; 265 } 266 267 // Chrome caches crx files for installed by default apps so sharing assets is 268 // also possible. User specific apps should be excluded to not expose apps 269 // unique for the user outside of user's cryptohome. 270 return Manifest::IsExternalLocation(extension->location()); 271 } 272 273 // static 274 void ExtensionAssetsManagerChromeOS::CheckSharedExtension( 275 const std::string& id, 276 const std::string& version, 277 const base::FilePath& unpacked_extension_root, 278 const base::FilePath& local_install_dir, 279 Profile* profile, 280 InstallExtensionCallback callback) { 281 DCHECK_CURRENTLY_ON(BrowserThread::UI); 282 283 const std::string& user_id = profile->GetProfileName(); 284 user_manager::UserManager* user_manager = user_manager::UserManager::Get(); 285 if (!user_manager) { 286 NOTREACHED(); 287 return; 288 } 289 290 if (user_manager->IsUserNonCryptohomeDataEphemeral(user_id) || 291 !user_manager->IsLoggedInAsRegularUser()) { 292 // Don't cache anything in shared location for ephemeral user or special 293 // user types. 294 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( 295 FROM_HERE, 296 base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension, 297 id, 298 version, 299 unpacked_extension_root, 300 local_install_dir, 301 callback)); 302 return; 303 } 304 305 PrefService* local_state = g_browser_process->local_state(); 306 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); 307 base::DictionaryValue* extension_info = NULL; 308 base::DictionaryValue* version_info = NULL; 309 base::ListValue* users = NULL; 310 std::string shared_path; 311 if (shared_extensions->GetDictionary(id, &extension_info) && 312 extension_info->GetDictionaryWithoutPathExpansion( 313 version, &version_info) && 314 version_info->GetString(kSharedExtensionPath, &shared_path) && 315 version_info->GetList(kSharedExtensionUsers, &users)) { 316 // This extension version already in shared location. 317 size_t users_size = users->GetSize(); 318 bool user_found = false; 319 for (size_t i = 0; i < users_size; i++) { 320 std::string temp; 321 if (users->GetString(i, &temp) && temp == user_id) { 322 // Re-installation for the same user. 323 user_found = true; 324 break; 325 } 326 } 327 if (!user_found) 328 users->AppendString(user_id); 329 330 // unpacked_extension_root will be deleted by CrxInstaller. 331 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( 332 FROM_HERE, 333 base::Bind(callback, base::FilePath(shared_path))); 334 } else { 335 // Desired version is not found in shared location. 336 ExtensionAssetsManagerHelper* helper = 337 ExtensionAssetsManagerHelper::GetInstance(); 338 if (helper->RecordSharedInstall(id, version, unpacked_extension_root, 339 local_install_dir, profile, callback)) { 340 // There is no install in progress for given <id, version> so run install. 341 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( 342 FROM_HERE, 343 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtension, 344 id, 345 version, 346 unpacked_extension_root)); 347 } 348 } 349 } 350 351 // static 352 void ExtensionAssetsManagerChromeOS::InstallSharedExtension( 353 const std::string& id, 354 const std::string& version, 355 const base::FilePath& unpacked_extension_root) { 356 base::FilePath shared_install_dir = GetSharedInstallDir(); 357 base::FilePath shared_version_dir = file_util::InstallExtension( 358 unpacked_extension_root, id, version, shared_install_dir); 359 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 360 base::Bind(&ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone, 361 id, version, shared_version_dir)); 362 } 363 364 // static 365 void ExtensionAssetsManagerChromeOS::InstallSharedExtensionDone( 366 const std::string& id, 367 const std::string& version, 368 const base::FilePath& shared_version_dir) { 369 DCHECK_CURRENTLY_ON(BrowserThread::UI); 370 371 ExtensionAssetsManagerHelper* helper = 372 ExtensionAssetsManagerHelper::GetInstance(); 373 ExtensionAssetsManagerHelper::PendingInstallList pending_installs; 374 helper->SharedInstallDone(id, version, &pending_installs); 375 376 if (shared_version_dir.empty()) { 377 // Installation to shared location failed, try local dir. 378 // TODO(dpolukhin): add UMA stats reporting. 379 for (size_t i = 0; i < pending_installs.size(); i++) { 380 ExtensionAssetsManagerHelper::PendingInstallInfo& info = 381 pending_installs[i]; 382 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask( 383 FROM_HERE, 384 base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension, 385 id, 386 version, 387 info.unpacked_extension_root, 388 info.local_install_dir, 389 info.callback)); 390 } 391 return; 392 } 393 394 PrefService* local_state = g_browser_process->local_state(); 395 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); 396 base::DictionaryValue* extension_info = NULL; 397 if (!shared_extensions->GetDictionary(id, &extension_info)) { 398 extension_info = new base::DictionaryValue; 399 shared_extensions->Set(id, extension_info); 400 } 401 402 CHECK(!shared_extensions->HasKey(version)); 403 base::DictionaryValue* version_info = new base::DictionaryValue; 404 extension_info->SetWithoutPathExpansion(version, version_info); 405 version_info->SetString(kSharedExtensionPath, shared_version_dir.value()); 406 407 base::ListValue* users = new base::ListValue; 408 version_info->Set(kSharedExtensionUsers, users); 409 for (size_t i = 0; i < pending_installs.size(); i++) { 410 ExtensionAssetsManagerHelper::PendingInstallInfo& info = 411 pending_installs[i]; 412 users->AppendString(info.profile->GetProfileName()); 413 414 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(info.profile)->PostTask( 415 FROM_HERE, 416 base::Bind(info.callback, shared_version_dir)); 417 } 418 } 419 420 // static 421 void ExtensionAssetsManagerChromeOS::InstallLocalExtension( 422 const std::string& id, 423 const std::string& version, 424 const base::FilePath& unpacked_extension_root, 425 const base::FilePath& local_install_dir, 426 InstallExtensionCallback callback) { 427 callback.Run(file_util::InstallExtension( 428 unpacked_extension_root, id, version, local_install_dir)); 429 } 430 431 // static 432 void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused( 433 const std::string& id, 434 Profile* profile) { 435 DCHECK_CURRENTLY_ON(BrowserThread::UI); 436 437 PrefService* local_state = g_browser_process->local_state(); 438 DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); 439 base::DictionaryValue* extension_info = NULL; 440 if (!shared_extensions->GetDictionary(id, &extension_info)) { 441 NOTREACHED(); 442 return; 443 } 444 445 std::vector<std::string> versions; 446 versions.reserve(extension_info->size()); 447 for (base::DictionaryValue::Iterator it(*extension_info); 448 !it.IsAtEnd(); 449 it.Advance()) { 450 versions.push_back(it.key()); 451 } 452 453 base::StringValue user_name(profile->GetProfileName()); 454 for (std::vector<std::string>::const_iterator it = versions.begin(); 455 it != versions.end(); it++) { 456 base::DictionaryValue* version_info = NULL; 457 if (!extension_info->GetDictionaryWithoutPathExpansion(*it, 458 &version_info)) { 459 NOTREACHED(); 460 continue; 461 } 462 base::ListValue* users = NULL; 463 if (!version_info->GetList(kSharedExtensionUsers, &users)) { 464 NOTREACHED(); 465 continue; 466 } 467 if (users->Remove(user_name, NULL) && !users->GetSize()) { 468 std::string shared_path; 469 if (!version_info->GetString(kSharedExtensionPath, &shared_path)) { 470 NOTREACHED(); 471 continue; 472 } 473 ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( 474 FROM_HERE, 475 base::Bind(&ExtensionAssetsManagerChromeOS::DeleteSharedVersion, 476 base::FilePath(shared_path))); 477 extension_info->RemoveWithoutPathExpansion(*it, NULL); 478 } 479 } 480 if (!extension_info->size()) { 481 shared_extensions->RemoveWithoutPathExpansion(id, NULL); 482 // Don't remove extension dir in shared location. It will be removed by GC 483 // when it is safe to do so, and this avoids a race condition between 484 // concurrent uninstall by one user and install by another. 485 } 486 } 487 488 // static 489 void ExtensionAssetsManagerChromeOS::DeleteSharedVersion( 490 const base::FilePath& shared_version_dir) { 491 CHECK(GetSharedInstallDir().IsParent(shared_version_dir)); 492 base::DeleteFile(shared_version_dir, true); // recursive. 493 } 494 495 // static 496 bool ExtensionAssetsManagerChromeOS::CleanUpExtension( 497 const std::string& id, 498 base::DictionaryValue* extension_info, 499 std::multimap<std::string, base::FilePath>* live_extension_paths) { 500 user_manager::UserManager* user_manager = user_manager::UserManager::Get(); 501 if (!user_manager) { 502 NOTREACHED(); 503 return false; 504 } 505 506 std::vector<std::string> versions; 507 versions.reserve(extension_info->size()); 508 for (base::DictionaryValue::Iterator it(*extension_info); 509 !it.IsAtEnd(); it.Advance()) { 510 versions.push_back(it.key()); 511 } 512 513 for (std::vector<std::string>::const_iterator it = versions.begin(); 514 it != versions.end(); it++) { 515 base::DictionaryValue* version_info = NULL; 516 base::ListValue* users = NULL; 517 std::string shared_path; 518 if (!extension_info->GetDictionaryWithoutPathExpansion(*it, 519 &version_info) || 520 !version_info->GetList(kSharedExtensionUsers, &users) || 521 !version_info->GetString(kSharedExtensionPath, &shared_path)) { 522 NOTREACHED(); 523 return false; 524 } 525 526 size_t num_users = users->GetSize(); 527 for (size_t i = 0; i < num_users; i++) { 528 std::string user_id; 529 if (!users->GetString(i, &user_id)) { 530 NOTREACHED(); 531 return false; 532 } 533 const user_manager::User* user = user_manager->FindUser(user_id); 534 bool not_used = false; 535 if (!user) { 536 not_used = true; 537 } else if (user->is_logged_in()) { 538 // For logged in user also check that this path is actually used as 539 // installed extension or as delayed install. 540 Profile* profile = 541 chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(user); 542 ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile); 543 if (!extension_prefs || extension_prefs->pref_service()->ReadOnly()) 544 return false; 545 546 scoped_ptr<ExtensionInfo> info = 547 extension_prefs->GetInstalledExtensionInfo(id); 548 if (!info || info->extension_path != base::FilePath(shared_path)) { 549 info = extension_prefs->GetDelayedInstallInfo(id); 550 if (!info || info->extension_path != base::FilePath(shared_path)) { 551 not_used = true; 552 } 553 } 554 } 555 556 if (not_used) { 557 users->Remove(i, NULL); 558 559 i--; 560 num_users--; 561 } 562 } 563 564 if (num_users) { 565 live_extension_paths->insert( 566 std::make_pair(id, base::FilePath(shared_path))); 567 } else { 568 extension_info->RemoveWithoutPathExpansion(*it, NULL); 569 } 570 } 571 572 return true; 573 } 574 575 } // namespace extensions 576