external_provider_impl.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
1// Copyright (c) 2012 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/external_provider_impl.h"
6
7#include <set>
8#include <vector>
9
10#include "base/command_line.h"
11#include "base/files/file_path.h"
12#include "base/logging.h"
13#include "base/memory/linked_ptr.h"
14#include "base/metrics/field_trial.h"
15#include "base/path_service.h"
16#include "base/strings/string_util.h"
17#include "base/values.h"
18#include "base/version.h"
19#include "chrome/browser/app_mode/app_mode_utils.h"
20#include "chrome/browser/browser_process.h"
21#include "chrome/browser/extensions/extension_service.h"
22#include "chrome/browser/extensions/external_component_loader.h"
23#include "chrome/browser/extensions/external_policy_loader.h"
24#include "chrome/browser/extensions/external_pref_loader.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chrome/common/chrome_paths.h"
27#include "chrome/common/chrome_switches.h"
28#include "chrome/common/pref_names.h"
29#include "content/public/browser/browser_thread.h"
30#include "extensions/browser/extension_system.h"
31#include "extensions/browser/external_provider_interface.h"
32#include "extensions/common/extension.h"
33#include "extensions/common/manifest.h"
34#include "ui/base/l10n/l10n_util.h"
35
36#if defined(OS_CHROMEOS)
37#include "chrome/browser/chromeos/customization_document.h"
38#include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
39#include "chrome/browser/chromeos/login/user.h"
40#include "chrome/browser/chromeos/login/user_manager.h"
41#include "chrome/browser/chromeos/policy/app_pack_updater.h"
42#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
43#include "chrome/browser/chromeos/policy/device_local_account.h"
44#include "chrome/browser/chromeos/policy/device_local_account_policy_service.h"
45#else
46#include "chrome/browser/extensions/default_apps.h"
47#endif
48
49#if defined(OS_WIN)
50#include "chrome/browser/extensions/external_registry_loader_win.h"
51#endif
52
53using content::BrowserThread;
54
55namespace extensions {
56
57// Constants for keeping track of extension preferences in a dictionary.
58const char ExternalProviderImpl::kInstallParam[] = "install_parameter";
59const char ExternalProviderImpl::kExternalCrx[] = "external_crx";
60const char ExternalProviderImpl::kExternalVersion[] = "external_version";
61const char ExternalProviderImpl::kExternalUpdateUrl[] = "external_update_url";
62const char ExternalProviderImpl::kSupportedLocales[] = "supported_locales";
63const char ExternalProviderImpl::kIsBookmarkApp[] = "is_bookmark_app";
64const char ExternalProviderImpl::kIsFromWebstore[] = "is_from_webstore";
65const char ExternalProviderImpl::kKeepIfPresent[] = "keep_if_present";
66
67ExternalProviderImpl::ExternalProviderImpl(
68    VisitorInterface* service,
69    const scoped_refptr<ExternalLoader>& loader,
70    Profile* profile,
71    Manifest::Location crx_location,
72    Manifest::Location download_location,
73    int creation_flags)
74    : crx_location_(crx_location),
75      download_location_(download_location),
76      service_(service),
77      ready_(false),
78      loader_(loader),
79      profile_(profile),
80      creation_flags_(creation_flags),
81      auto_acknowledge_(false) {
82  loader_->Init(this);
83}
84
85ExternalProviderImpl::~ExternalProviderImpl() {
86  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
87  loader_->OwnerShutdown();
88}
89
90void ExternalProviderImpl::VisitRegisteredExtension() {
91  // The loader will call back to SetPrefs.
92  loader_->StartLoading();
93}
94
95void ExternalProviderImpl::SetPrefs(base::DictionaryValue* prefs) {
96  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
97
98  // Check if the service is still alive. It is possible that it went
99  // away while |loader_| was working on the FILE thread.
100  if (!service_) return;
101
102  prefs_.reset(prefs);
103  ready_ = true;  // Queries for extensions are allowed from this point.
104
105  // Set of unsupported extensions that need to be deleted from prefs_.
106  std::set<std::string> unsupported_extensions;
107
108  // Notify ExtensionService about all the extensions this provider has.
109  for (base::DictionaryValue::Iterator i(*prefs_); !i.IsAtEnd(); i.Advance()) {
110    const std::string& extension_id = i.key();
111    const base::DictionaryValue* extension = NULL;
112
113    if (!Extension::IdIsValid(extension_id)) {
114      LOG(WARNING) << "Malformed extension dictionary: key "
115                   << extension_id.c_str() << " is not a valid id.";
116      continue;
117    }
118
119    if (!i.value().GetAsDictionary(&extension)) {
120      LOG(WARNING) << "Malformed extension dictionary: key "
121                   << extension_id.c_str()
122                   << " has a value that is not a dictionary.";
123      continue;
124    }
125
126    base::FilePath::StringType external_crx;
127    const base::Value* external_version_value = NULL;
128    std::string external_version;
129    std::string external_update_url;
130
131    bool has_external_crx = extension->GetString(kExternalCrx, &external_crx);
132
133    bool has_external_version = false;
134    if (extension->Get(kExternalVersion, &external_version_value)) {
135      if (external_version_value->IsType(base::Value::TYPE_STRING)) {
136        external_version_value->GetAsString(&external_version);
137        has_external_version = true;
138      } else {
139        LOG(WARNING) << "Malformed extension dictionary for extension: "
140                     << extension_id.c_str() << ". " << kExternalVersion
141                     << " value must be a string.";
142        continue;
143      }
144    }
145
146    bool has_external_update_url = extension->GetString(kExternalUpdateUrl,
147                                                        &external_update_url);
148    if (has_external_crx != has_external_version) {
149      LOG(WARNING) << "Malformed extension dictionary for extension: "
150                   << extension_id.c_str() << ".  " << kExternalCrx
151                   << " and " << kExternalVersion << " must be used together.";
152      continue;
153    }
154
155    if (has_external_crx == has_external_update_url) {
156      LOG(WARNING) << "Malformed extension dictionary for extension: "
157                   << extension_id.c_str() << ".  Exactly one of the "
158                   << "followng keys should be used: " << kExternalCrx
159                   << ", " << kExternalUpdateUrl << ".";
160      continue;
161    }
162
163    // Check that extension supports current browser locale.
164    const base::ListValue* supported_locales = NULL;
165    if (extension->GetList(kSupportedLocales, &supported_locales)) {
166      std::vector<std::string> browser_locales;
167      l10n_util::GetParentLocales(g_browser_process->GetApplicationLocale(),
168                                  &browser_locales);
169
170      size_t num_locales = supported_locales->GetSize();
171      bool locale_supported = false;
172      for (size_t j = 0; j < num_locales; j++) {
173        std::string current_locale;
174        if (supported_locales->GetString(j, &current_locale) &&
175            l10n_util::IsValidLocaleSyntax(current_locale)) {
176          current_locale = l10n_util::NormalizeLocale(current_locale);
177          if (std::find(browser_locales.begin(), browser_locales.end(),
178                        current_locale) != browser_locales.end()) {
179            locale_supported = true;
180            break;
181          }
182        } else {
183          LOG(WARNING) << "Unrecognized locale '" << current_locale
184                       << "' found as supported locale for extension: "
185                       << extension_id;
186        }
187      }
188
189      if (!locale_supported) {
190        unsupported_extensions.insert(extension_id);
191        VLOG(1) << "Skip installing (or uninstall) external extension: "
192                << extension_id << " because the extension doesn't support "
193                << "the browser locale.";
194        continue;
195      }
196    }
197
198    int creation_flags = creation_flags_;
199    bool is_bookmark_app;
200    if (extension->GetBoolean(kIsBookmarkApp, &is_bookmark_app) &&
201        is_bookmark_app) {
202      creation_flags |= Extension::FROM_BOOKMARK;
203    }
204    bool is_from_webstore;
205    if (extension->GetBoolean(kIsFromWebstore, &is_from_webstore) &&
206        is_from_webstore) {
207      creation_flags |= Extension::FROM_WEBSTORE;
208    }
209    bool keep_if_present;
210    if (extension->GetBoolean(kKeepIfPresent, &keep_if_present) &&
211        keep_if_present && profile_) {
212      ExtensionServiceInterface* extension_service =
213          ExtensionSystem::Get(profile_)->extension_service();
214      const Extension* extension = extension_service ?
215          extension_service->GetExtensionById(extension_id, true) : NULL;
216      if (!extension) {
217        VLOG(1) << "Skip installing (or uninstall) external extension: "
218                << extension_id << " because the extension should be kept "
219                << "only if it is already installed.";
220        continue;
221      }
222    }
223
224    std::string install_parameter;
225    extension->GetString(kInstallParam, &install_parameter);
226
227    if (has_external_crx) {
228      if (crx_location_ == Manifest::INVALID_LOCATION) {
229        LOG(WARNING) << "This provider does not support installing external "
230                     << "extensions from crx files.";
231        continue;
232      }
233      if (external_crx.find(base::FilePath::kParentDirectory) !=
234          base::StringPiece::npos) {
235        LOG(WARNING) << "Path traversal not allowed in path: "
236                     << external_crx.c_str();
237        continue;
238      }
239
240      // If the path is relative, and the provider has a base path,
241      // build the absolute path to the crx file.
242      base::FilePath path(external_crx);
243      if (!path.IsAbsolute()) {
244        base::FilePath base_path = loader_->GetBaseCrxFilePath();
245        if (base_path.empty()) {
246          LOG(WARNING) << "File path " << external_crx.c_str()
247                       << " is relative.  An absolute path is required.";
248          continue;
249        }
250        path = base_path.Append(external_crx);
251      }
252
253      Version version(external_version);
254      if (!version.IsValid()) {
255        LOG(WARNING) << "Malformed extension dictionary for extension: "
256                     << extension_id.c_str() << ".  Invalid version string \""
257                     << external_version << "\".";
258        continue;
259      }
260      service_->OnExternalExtensionFileFound(extension_id, &version, path,
261                                             crx_location_, creation_flags,
262                                             auto_acknowledge_);
263    } else {  // if (has_external_update_url)
264      CHECK(has_external_update_url);  // Checking of keys above ensures this.
265      if (download_location_ == Manifest::INVALID_LOCATION) {
266        LOG(WARNING) << "This provider does not support installing external "
267                     << "extensions from update URLs.";
268        continue;
269      }
270      GURL update_url(external_update_url);
271      if (!update_url.is_valid()) {
272        LOG(WARNING) << "Malformed extension dictionary for extension: "
273                     << extension_id.c_str() << ".  Key " << kExternalUpdateUrl
274                     << " has value \"" << external_update_url
275                     << "\", which is not a valid URL.";
276        continue;
277      }
278      service_->OnExternalExtensionUpdateUrlFound(extension_id,
279                                                  install_parameter,
280                                                  update_url,
281                                                  download_location_,
282                                                  creation_flags,
283                                                  auto_acknowledge_);
284    }
285  }
286
287  for (std::set<std::string>::iterator it = unsupported_extensions.begin();
288       it != unsupported_extensions.end(); ++it) {
289    // Remove extension for the list of know external extensions. The extension
290    // will be uninstalled later because provider doesn't provide it anymore.
291    prefs_->Remove(*it, NULL);
292  }
293
294  service_->OnExternalProviderReady(this);
295}
296
297void ExternalProviderImpl::ServiceShutdown() {
298  service_ = NULL;
299}
300
301bool ExternalProviderImpl::IsReady() const {
302  return ready_;
303}
304
305bool ExternalProviderImpl::HasExtension(
306    const std::string& id) const {
307  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308  CHECK(prefs_.get());
309  CHECK(ready_);
310  return prefs_->HasKey(id);
311}
312
313bool ExternalProviderImpl::GetExtensionDetails(
314    const std::string& id, Manifest::Location* location,
315    scoped_ptr<Version>* version) const {
316  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
317  CHECK(prefs_.get());
318  CHECK(ready_);
319  base::DictionaryValue* extension = NULL;
320  if (!prefs_->GetDictionary(id, &extension))
321    return false;
322
323  Manifest::Location loc = Manifest::INVALID_LOCATION;
324  if (extension->HasKey(kExternalUpdateUrl)) {
325    loc = download_location_;
326
327  } else if (extension->HasKey(kExternalCrx)) {
328    loc = crx_location_;
329
330    std::string external_version;
331    if (!extension->GetString(kExternalVersion, &external_version))
332      return false;
333
334    if (version)
335      version->reset(new Version(external_version));
336
337  } else {
338    NOTREACHED();  // Chrome should not allow prefs to get into this state.
339    return false;
340  }
341
342  if (location)
343    *location = loc;
344
345  return true;
346}
347
348// static
349void ExternalProviderImpl::CreateExternalProviders(
350    VisitorInterface* service,
351    Profile* profile,
352    ProviderCollection* provider_list) {
353  scoped_refptr<ExternalLoader> external_loader;
354  extensions::Manifest::Location crx_location = Manifest::INVALID_LOCATION;
355#if defined(OS_CHROMEOS)
356  policy::BrowserPolicyConnectorChromeOS* connector =
357      g_browser_process->platform_part()->browser_policy_connector_chromeos();
358  bool is_chrome_os_public_session = false;
359  const chromeos::User* user =
360      chromeos::UserManager::Get()->GetUserByProfile(profile);
361  policy::DeviceLocalAccount::Type account_type;
362  if (user && policy::IsDeviceLocalAccountUser(user->email(), &account_type)) {
363    if (account_type == policy::DeviceLocalAccount::TYPE_PUBLIC_SESSION)
364      is_chrome_os_public_session = true;
365    policy::DeviceLocalAccountPolicyBroker* broker =
366        connector->GetDeviceLocalAccountPolicyService()->GetBrokerForUser(
367            user->email());
368    if (broker) {
369      external_loader = broker->extension_loader();
370      crx_location = Manifest::EXTERNAL_POLICY;
371    } else {
372      NOTREACHED();
373    }
374  } else {
375    external_loader = new ExternalPolicyLoader(profile);
376  }
377#else
378  external_loader = new ExternalPolicyLoader(profile);
379#endif
380
381  // Policies are mandatory so they can't be skipped with command line flag.
382  if (external_loader) {
383    provider_list->push_back(
384        linked_ptr<ExternalProviderInterface>(
385            new ExternalProviderImpl(
386                service,
387                external_loader,
388                profile,
389                crx_location,
390                Manifest::EXTERNAL_POLICY_DOWNLOAD,
391                Extension::NO_FLAGS)));
392  }
393
394  // In tests don't install extensions from default external sources.
395  // It would only slowdown tests and make them flaky.
396  if (CommandLine::ForCurrentProcess()->HasSwitch(
397      switches::kDisableDefaultApps))
398    return;
399
400  // No external app install in app mode.
401  if (chrome::IsRunningInForcedAppMode())
402    return;
403
404  // On Mac OS, items in /Library/... should be written by the superuser.
405  // Check that all components of the path are writable by root only.
406  ExternalPrefLoader::Options check_admin_permissions_on_mac;
407#if defined(OS_MACOSX)
408  check_admin_permissions_on_mac =
409    ExternalPrefLoader::ENSURE_PATH_CONTROLLED_BY_ADMIN;
410#else
411  check_admin_permissions_on_mac = ExternalPrefLoader::NONE;
412#endif
413
414  bool is_chromeos_demo_session = false;
415  int bundled_extension_creation_flags = Extension::NO_FLAGS;
416#if defined(OS_CHROMEOS)
417  chromeos::UserManager* user_manager = chromeos::UserManager::Get();
418  is_chromeos_demo_session =
419      user_manager && user_manager->IsLoggedInAsDemoUser() &&
420      connector->GetDeviceMode() == policy::DEVICE_MODE_RETAIL_KIOSK;
421  bundled_extension_creation_flags = Extension::FROM_WEBSTORE |
422      Extension::WAS_INSTALLED_BY_DEFAULT;
423#endif
424
425#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
426  if (!profile->IsManaged()) {
427    provider_list->push_back(
428        linked_ptr<ExternalProviderInterface>(
429            new ExternalProviderImpl(
430                service,
431                new ExternalPrefLoader(
432                    chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
433                    ExternalPrefLoader::NONE),
434                profile,
435                Manifest::EXTERNAL_PREF,
436                Manifest::EXTERNAL_PREF_DOWNLOAD,
437                bundled_extension_creation_flags)));
438  }
439#endif
440
441#if defined(OS_CHROMEOS)
442  if (!is_chromeos_demo_session && !is_chrome_os_public_session) {
443    int external_apps_path_id = profile->IsManaged() ?
444        chrome::DIR_MANAGED_USERS_DEFAULT_APPS :
445        chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS;
446    provider_list->push_back(
447        linked_ptr<ExternalProviderInterface>(new ExternalProviderImpl(
448            service,
449            new ExternalPrefLoader(external_apps_path_id,
450                                   ExternalPrefLoader::NONE),
451            profile,
452            Manifest::EXTERNAL_PREF,
453            Manifest::EXTERNAL_PREF_DOWNLOAD,
454            bundled_extension_creation_flags)));
455
456    // OEM default apps.
457    int oem_extension_creation_flags =
458        bundled_extension_creation_flags | Extension::WAS_INSTALLED_BY_OEM;
459    chromeos::ServicesCustomizationDocument* customization =
460        chromeos::ServicesCustomizationDocument::GetInstance();
461    provider_list->push_back(linked_ptr<ExternalProviderInterface>(
462        new ExternalProviderImpl(service,
463                                 customization->CreateExternalLoader(profile),
464                                 profile,
465                                 Manifest::EXTERNAL_PREF,
466                                 Manifest::EXTERNAL_PREF_DOWNLOAD,
467                                 oem_extension_creation_flags)));
468  }
469
470  policy::AppPackUpdater* app_pack_updater = connector->GetAppPackUpdater();
471  if (is_chromeos_demo_session && app_pack_updater &&
472      !app_pack_updater->created_external_loader()) {
473    provider_list->push_back(
474        linked_ptr<ExternalProviderInterface>(
475          new ExternalProviderImpl(
476              service,
477              app_pack_updater->CreateExternalLoader(),
478              profile,
479              Manifest::EXTERNAL_PREF,
480              Manifest::INVALID_LOCATION,
481              Extension::NO_FLAGS)));
482  }
483#endif
484
485  if (!profile->IsManaged() && !is_chromeos_demo_session) {
486#if !defined(OS_WIN)
487    provider_list->push_back(
488        linked_ptr<ExternalProviderInterface>(
489            new ExternalProviderImpl(
490                service,
491                new ExternalPrefLoader(chrome::DIR_EXTERNAL_EXTENSIONS,
492                                       check_admin_permissions_on_mac),
493                profile,
494                Manifest::EXTERNAL_PREF,
495                Manifest::EXTERNAL_PREF_DOWNLOAD,
496                bundled_extension_creation_flags)));
497#endif
498
499    // Define a per-user source of external extensions.
500#if defined(OS_MACOSX)
501    provider_list->push_back(
502        linked_ptr<ExternalProviderInterface>(
503            new ExternalProviderImpl(
504                service,
505                new ExternalPrefLoader(chrome::DIR_USER_EXTERNAL_EXTENSIONS,
506                                       ExternalPrefLoader::NONE),
507                profile,
508                Manifest::EXTERNAL_PREF,
509                Manifest::EXTERNAL_PREF_DOWNLOAD,
510                Extension::NO_FLAGS)));
511#endif
512
513#if defined(OS_WIN)
514    provider_list->push_back(
515        linked_ptr<ExternalProviderInterface>(
516            new ExternalProviderImpl(
517                service,
518                new ExternalRegistryLoader,
519                profile,
520                Manifest::EXTERNAL_REGISTRY,
521                Manifest::EXTERNAL_PREF_DOWNLOAD,
522                Extension::NO_FLAGS)));
523#endif
524
525#if !defined(OS_CHROMEOS)
526    // The default apps are installed as INTERNAL but use the external
527    // extension installer codeflow.
528    provider_list->push_back(
529        linked_ptr<ExternalProviderInterface>(
530            new default_apps::Provider(
531                profile,
532                service,
533                new ExternalPrefLoader(chrome::DIR_DEFAULT_APPS,
534                                       ExternalPrefLoader::NONE),
535                Manifest::INTERNAL,
536                Manifest::INVALID_LOCATION,
537                Extension::FROM_WEBSTORE |
538                    Extension::WAS_INSTALLED_BY_DEFAULT)));
539#endif
540
541    provider_list->push_back(
542      linked_ptr<ExternalProviderInterface>(
543        new ExternalProviderImpl(
544            service,
545            new ExternalComponentLoader(profile),
546            profile,
547            Manifest::INVALID_LOCATION,
548            Manifest::EXTERNAL_COMPONENT,
549            Extension::FROM_WEBSTORE | Extension::WAS_INSTALLED_BY_DEFAULT)));
550  }
551}
552
553}  // namespace extensions
554