extension_assets_manager_chromeos.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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/profiles/profile_helper.h"
20#include "chrome/browser/extensions/extension_service.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/common/extensions/extension_constants.h"
23#include "chrome/common/extensions/manifest_url_handler.h"
24#include "chromeos/chromeos_switches.h"
25#include "components/user_manager/user_manager.h"
26#include "content/public/browser/browser_thread.h"
27#include "extensions/browser/extension_prefs.h"
28#include "extensions/browser/extension_system.h"
29#include "extensions/common/extension.h"
30#include "extensions/common/file_util.h"
31#include "extensions/common/manifest.h"
32
33using content::BrowserThread;
34
35namespace extensions {
36namespace {
37
38// Path to shared extensions install dir.
39const char kSharedExtensionsDir[] = "/var/cache/shared_extensions";
40
41// Shared install dir overrider for tests only.
42static 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.
46class 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
115const char ExtensionAssetsManagerChromeOS::kSharedExtensions[] =
116    "SharedExtensions";
117
118const char ExtensionAssetsManagerChromeOS::kSharedExtensionPath[] = "path";
119
120const char ExtensionAssetsManagerChromeOS::kSharedExtensionUsers[] = "users";
121
122ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { }
123
124ExtensionAssetsManagerChromeOS::~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
132ExtensionAssetsManagerChromeOS* ExtensionAssetsManagerChromeOS::GetInstance() {
133  return Singleton<ExtensionAssetsManagerChromeOS>::get();
134}
135
136// static
137void ExtensionAssetsManagerChromeOS::RegisterPrefs(
138    PrefRegistrySimple* registry) {
139  registry->RegisterDictionaryPref(kSharedExtensions);
140}
141
142void 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
167void 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
188base::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
196bool ExtensionAssetsManagerChromeOS::IsSharedInstall(
197    const Extension* extension) {
198  return GetSharedInstallDir().IsParent(extension->path());
199}
200
201// static
202bool 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
237void 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
244base::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
253bool 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
274void 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
352void 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
365void 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
421void 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
432void 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
489void 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
496bool 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