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