Home | History | Annotate | Download | only in extensions
      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