Home | History | Annotate | Download | only in extensions
      1 // Copyright 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 "base/memory/ref_counted.h"
      6 #include "base/memory/scoped_ptr.h"
      7 #include "base/strings/string16.h"
      8 #include "base/values.h"
      9 #include "chrome/browser/extensions/extension_service.h"
     10 #include "chrome/browser/extensions/extension_service_test_base.h"
     11 #include "chrome/browser/extensions/pending_extension_manager.h"
     12 #include "chrome/browser/extensions/shared_module_service.h"
     13 #include "chrome/common/extensions/features/feature_channel.h"
     14 #include "components/crx_file/id_util.h"
     15 #include "extensions/browser/extension_registry.h"
     16 #include "extensions/browser/install_flag.h"
     17 #include "extensions/browser/uninstall_reason.h"
     18 #include "extensions/common/extension_builder.h"
     19 #include "extensions/common/value_builder.h"
     20 #include "sync/api/string_ordinal.h"
     21 
     22 namespace extensions {
     23 
     24 namespace {
     25 
     26 // Return an extension with |id| which imports a module with the given
     27 // |import_id|.
     28 scoped_refptr<Extension> CreateExtensionImportingModule(
     29     const std::string& import_id,
     30     const std::string& id,
     31     const std::string& version) {
     32   DictionaryBuilder builder;
     33   builder.Set("name", "Has Dependent Modules")
     34          .Set("version", version)
     35          .Set("manifest_version", 2);
     36   if (!import_id.empty()) {
     37     builder.Set("import",
     38                 ListBuilder().Append(DictionaryBuilder().Set("id", import_id)));
     39   }
     40   scoped_ptr<base::DictionaryValue> manifest = builder.Build();
     41 
     42   return ExtensionBuilder().SetManifest(manifest.Pass())
     43                            .AddFlags(Extension::FROM_WEBSTORE)
     44                            .SetID(id)
     45                            .Build();
     46 }
     47 
     48 }  // namespace
     49 
     50 class SharedModuleServiceUnitTest : public ExtensionServiceTestBase {
     51  public:
     52   SharedModuleServiceUnitTest() :
     53       // The "export" key is open for dev-channel only, but unit tests
     54       // run as stable channel on the official Windows build.
     55       current_channel_(chrome::VersionInfo::CHANNEL_UNKNOWN) {}
     56  protected:
     57   virtual void SetUp() OVERRIDE;
     58 
     59   // Install an extension and notify the ExtensionService.
     60   testing::AssertionResult InstallExtension(const Extension* extension,
     61                                             bool is_update);
     62   ScopedCurrentChannel current_channel_;
     63 };
     64 
     65 void SharedModuleServiceUnitTest::SetUp() {
     66   ExtensionServiceTestBase::SetUp();
     67   InitializeGoodInstalledExtensionService();
     68   service()->Init();
     69 }
     70 
     71 testing::AssertionResult SharedModuleServiceUnitTest::InstallExtension(
     72     const Extension* extension,
     73     bool is_update) {
     74 
     75   const Extension* old = registry()->GetExtensionById(
     76       extension->id(),
     77       ExtensionRegistry::ENABLED);
     78 
     79   // Verify the extension is not already installed, if it is not update.
     80   if (!is_update) {
     81     if (old)
     82       return testing::AssertionFailure() << "Extension already installed.";
     83   } else {
     84     if (!old)
     85       return testing::AssertionFailure() << "The extension does not exist.";
     86   }
     87 
     88   // Notify the service that the extension is installed. This adds it to the
     89   // registry, notifies interested parties, etc.
     90   service()->OnExtensionInstalled(
     91       extension, syncer::StringOrdinal(), kInstallFlagInstallImmediately);
     92 
     93   // Verify that the extension is now installed.
     94   if (!registry()->GetExtensionById(extension->id(),
     95                                     ExtensionRegistry::ENABLED)) {
     96     return testing::AssertionFailure() << "Could not install extension.";
     97   }
     98 
     99   return testing::AssertionSuccess();
    100 }
    101 
    102 TEST_F(SharedModuleServiceUnitTest, AddDependentSharedModules) {
    103   // Create an extension that has a dependency.
    104   std::string import_id = crx_file::id_util::GenerateId("id");
    105   std::string extension_id = crx_file::id_util::GenerateId("extension_id");
    106   scoped_refptr<Extension> extension =
    107       CreateExtensionImportingModule(import_id, extension_id, "1.0");
    108 
    109   PendingExtensionManager* pending_extension_manager =
    110       service()->pending_extension_manager();
    111 
    112   // Verify that we don't currently want to install the imported module.
    113   EXPECT_FALSE(pending_extension_manager->IsIdPending(import_id));
    114 
    115   // Try to satisfy imports for the extension. This should queue the imported
    116   // module's installation.
    117   service()->shared_module_service()->SatisfyImports(extension.get());
    118   EXPECT_TRUE(pending_extension_manager->IsIdPending(import_id));
    119 }
    120 
    121 TEST_F(SharedModuleServiceUnitTest, PruneSharedModulesOnUninstall) {
    122   // Create a module which exports a resource, and install it.
    123   scoped_ptr<base::DictionaryValue> manifest =
    124       DictionaryBuilder()
    125           .Set("name", "Shared Module")
    126           .Set("version", "1.0")
    127           .Set("manifest_version", 2)
    128           .Set("export",
    129                DictionaryBuilder().Set("resources",
    130                                        ListBuilder().Append("foo.js"))).Build();
    131   scoped_refptr<Extension> shared_module =
    132       ExtensionBuilder()
    133           .SetManifest(manifest.Pass())
    134           .AddFlags(Extension::FROM_WEBSTORE)
    135           .SetID(crx_file::id_util::GenerateId("shared_module"))
    136           .Build();
    137 
    138   EXPECT_TRUE(InstallExtension(shared_module.get(), false));
    139 
    140   std::string extension_id = crx_file::id_util::GenerateId("extension_id");
    141   // Create and install an extension that imports our new module.
    142   scoped_refptr<Extension> importing_extension =
    143       CreateExtensionImportingModule(shared_module->id(), extension_id, "1.0");
    144   EXPECT_TRUE(InstallExtension(importing_extension.get(), false));
    145 
    146   // Uninstall the extension that imports our module.
    147   base::string16 error;
    148   service()->UninstallExtension(importing_extension->id(),
    149                                 extensions::UNINSTALL_REASON_FOR_TESTING,
    150                                 base::Bind(&base::DoNothing),
    151                                 &error);
    152   EXPECT_TRUE(error.empty());
    153 
    154   // Since the module was only referenced by that single extension, it should
    155   // have been uninstalled as a side-effect of uninstalling the extension that
    156   // depended upon it.
    157   EXPECT_FALSE(registry()->GetExtensionById(shared_module->id(),
    158                                             ExtensionRegistry::EVERYTHING));
    159 }
    160 
    161 TEST_F(SharedModuleServiceUnitTest, PruneSharedModulesOnUpdate) {
    162   // Create two modules which export a resource, and install them.
    163   scoped_ptr<base::DictionaryValue> manifest_1 =
    164       DictionaryBuilder()
    165           .Set("name", "Shared Module 1")
    166           .Set("version", "1.0")
    167           .Set("manifest_version", 2)
    168           .Set("export",
    169                DictionaryBuilder().Set("resources",
    170                                        ListBuilder().Append("foo.js"))).Build();
    171   scoped_refptr<Extension> shared_module_1 =
    172       ExtensionBuilder()
    173           .SetManifest(manifest_1.Pass())
    174           .AddFlags(Extension::FROM_WEBSTORE)
    175           .SetID(crx_file::id_util::GenerateId("shared_module_1"))
    176           .Build();
    177   EXPECT_TRUE(InstallExtension(shared_module_1.get(), false));
    178 
    179   scoped_ptr<base::DictionaryValue> manifest_2 =
    180       DictionaryBuilder()
    181           .Set("name", "Shared Module 2")
    182           .Set("version", "1.0")
    183           .Set("manifest_version", 2)
    184           .Set("export",
    185                DictionaryBuilder().Set("resources",
    186                                        ListBuilder().Append("foo.js"))).Build();
    187   scoped_refptr<Extension> shared_module_2 =
    188       ExtensionBuilder()
    189           .SetManifest(manifest_2.Pass())
    190           .AddFlags(Extension::FROM_WEBSTORE)
    191           .SetID(crx_file::id_util::GenerateId("shared_module_2"))
    192           .Build();
    193   EXPECT_TRUE(InstallExtension(shared_module_2.get(), false));
    194 
    195   std::string extension_id = crx_file::id_util::GenerateId("extension_id");
    196 
    197   // Create and install an extension v1.0 that imports our new module 1.
    198   scoped_refptr<Extension> importing_extension_1 =
    199       CreateExtensionImportingModule(shared_module_1->id(),
    200                                      extension_id,
    201                                      "1.0");
    202   EXPECT_TRUE(InstallExtension(importing_extension_1.get(), false));
    203 
    204   // Create and install a new version of the extension that imports our new
    205   // module 2.
    206   scoped_refptr<Extension> importing_extension_2 =
    207       CreateExtensionImportingModule(shared_module_2->id(),
    208                                      extension_id,
    209                                      "1.1");
    210   EXPECT_TRUE(InstallExtension(importing_extension_2.get(), true));
    211 
    212   // Since the extension v1.1 depends the module 2 insteand module 1.
    213   // So the module 1 should be uninstalled.
    214   EXPECT_FALSE(registry()->GetExtensionById(shared_module_1->id(),
    215                                             ExtensionRegistry::EVERYTHING));
    216   EXPECT_TRUE(registry()->GetExtensionById(shared_module_2->id(),
    217                                             ExtensionRegistry::EVERYTHING));
    218 
    219   // Create and install a new version of the extension that does not import any
    220   // module.
    221   scoped_refptr<Extension> importing_extension_3 =
    222       CreateExtensionImportingModule("", extension_id, "1.2");
    223   EXPECT_TRUE(InstallExtension(importing_extension_3.get(), true));
    224 
    225   // Since the extension v1.2 does not depend any module, so the all models
    226   // should have been uninstalled.
    227   EXPECT_FALSE(registry()->GetExtensionById(shared_module_1->id(),
    228                                             ExtensionRegistry::EVERYTHING));
    229   EXPECT_FALSE(registry()->GetExtensionById(shared_module_2->id(),
    230                                             ExtensionRegistry::EVERYTHING));
    231 
    232 }
    233 
    234 TEST_F(SharedModuleServiceUnitTest, WhitelistedImports) {
    235   std::string whitelisted_id = crx_file::id_util::GenerateId("whitelisted");
    236   std::string nonwhitelisted_id =
    237       crx_file::id_util::GenerateId("nonwhitelisted");
    238   // Create a module which exports to a restricted whitelist.
    239   scoped_ptr<base::DictionaryValue> manifest =
    240       DictionaryBuilder()
    241           .Set("name", "Shared Module")
    242           .Set("version", "1.0")
    243           .Set("manifest_version", 2)
    244           .Set("export",
    245                DictionaryBuilder().Set("whitelist",
    246                                        ListBuilder()
    247                                            .Append(whitelisted_id))
    248                                   .Set("resources",
    249                                        ListBuilder().Append("*"))).Build();
    250   scoped_refptr<Extension> shared_module =
    251       ExtensionBuilder()
    252           .SetManifest(manifest.Pass())
    253           .AddFlags(Extension::FROM_WEBSTORE)
    254           .SetID(crx_file::id_util::GenerateId("shared_module"))
    255           .Build();
    256 
    257   EXPECT_TRUE(InstallExtension(shared_module.get(), false));
    258 
    259   // Create and install an extension with the whitelisted ID.
    260   scoped_refptr<Extension> whitelisted_extension =
    261       CreateExtensionImportingModule(shared_module->id(),
    262                                      whitelisted_id,
    263                                      "1.0");
    264   EXPECT_TRUE(InstallExtension(whitelisted_extension.get(), false));
    265 
    266   // Try to install an extension with an ID that is not whitelisted.
    267   scoped_refptr<Extension> nonwhitelisted_extension =
    268       CreateExtensionImportingModule(shared_module->id(),
    269                                      nonwhitelisted_id,
    270                                      "1.0");
    271   // This should succeed because only CRX installer (and by extension the
    272   // WebStore Installer) checks the shared module whitelist.  InstallExtension
    273   // bypasses the whitelist check because the SharedModuleService does not
    274   // care about whitelists.
    275   EXPECT_TRUE(InstallExtension(nonwhitelisted_extension.get(), false));
    276 }
    277 
    278 }  // namespace extensions
    279