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/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