• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetInstance()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.
RecordSharedInstall(const std::string & id,const std::string & version,const base::FilePath & unpacked_extension_root,const base::FilePath & local_install_dir,Profile * profile,ExtensionAssetsManager::InstallExtensionCallback callback)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|.
SharedInstallDone(const std::string & id,const std::string & version,PendingInstallList * 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 
ExtensionAssetsManagerHelper()96   ExtensionAssetsManagerHelper() {}
~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 
ExtensionAssetsManagerChromeOS()119 ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { }
120 
~ExtensionAssetsManagerChromeOS()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
GetInstance()129 ExtensionAssetsManagerChromeOS* ExtensionAssetsManagerChromeOS::GetInstance() {
130   return Singleton<ExtensionAssetsManagerChromeOS>::get();
131 }
132 
133 // static
RegisterPrefs(PrefRegistrySimple * registry)134 void ExtensionAssetsManagerChromeOS::RegisterPrefs(
135     PrefRegistrySimple* registry) {
136   registry->RegisterDictionaryPref(kSharedExtensions);
137 }
138 
InstallExtension(const Extension * extension,const base::FilePath & unpacked_extension_root,const base::FilePath & local_install_dir,Profile * profile,InstallExtensionCallback callback)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 
UninstallExtension(const std::string & id,Profile * profile,const base::FilePath & local_install_dir,const base::FilePath & extension_root)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
GetSharedInstallDir()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
CleanUpSharedExtensions(std::multimap<std::string,base::FilePath> * live_extension_paths)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
SetSharedInstallDirForTesting(const base::FilePath & install_dir)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
GetFileTaskRunner(Profile * profile)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
CanShareAssets(const Extension * extension)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
CheckSharedExtension(const std::string & id,const std::string & version,const base::FilePath & unpacked_extension_root,const base::FilePath & local_install_dir,Profile * profile,InstallExtensionCallback callback)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
InstallSharedExtension(const std::string & id,const std::string & version,const base::FilePath & unpacked_extension_root)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
InstallSharedExtensionDone(const std::string & id,const std::string & version,const base::FilePath & shared_version_dir)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
InstallLocalExtension(const std::string & id,const std::string & version,const base::FilePath & unpacked_extension_root,const base::FilePath & local_install_dir,InstallExtensionCallback callback)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
MarkSharedExtensionUnused(const std::string & id,Profile * profile)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
DeleteSharedVersion(const base::FilePath & shared_version_dir)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
CleanUpExtension(const std::string & id,base::DictionaryValue * extension_info,std::multimap<std::string,base::FilePath> * live_extension_paths)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