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