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