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