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