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 "chrome/browser/extensions/shared_module_service.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/memory/ref_counted.h"
11 #include "base/strings/string16.h"
12 #include "base/values.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/extension_service_test_base.h"
15 #include "chrome/browser/extensions/pending_extension_manager.h"
16 #include "components/crx_file/id_util.h"
17 #include "components/sync/model/string_ordinal.h"
18 #include "components/version_info/version_info.h"
19 #include "extensions/browser/extension_registry.h"
20 #include "extensions/browser/install_flag.h"
21 #include "extensions/browser/uninstall_reason.h"
22 #include "extensions/common/extension_builder.h"
23 #include "extensions/common/features/feature_channel.h"
24 #include "extensions/common/value_builder.h"
25
26 namespace extensions {
27
28 namespace {
29
30 // Return an extension with |id| which imports all the modules that are in the
31 // container |import_ids|.
CreateExtensionImportingModules(const std::vector<std::string> & import_ids,const std::string & id,const std::string & version)32 scoped_refptr<const Extension> CreateExtensionImportingModules(
33 const std::vector<std::string>& import_ids,
34 const std::string& id,
35 const std::string& version) {
36 DictionaryBuilder builder;
37 builder.Set("name", "Has Dependent Modules")
38 .Set("version", version)
39 .Set("manifest_version", 2);
40 if (!import_ids.empty()) {
41 ListBuilder import_list;
42 for (const std::string& id : import_ids)
43 import_list.Append(DictionaryBuilder().Set("id", id).Build());
44 builder.Set("import", import_list.Build());
45 }
46 return ExtensionBuilder()
47 .SetManifest(builder.Build())
48 .AddFlags(Extension::FROM_WEBSTORE)
49 .SetID(id)
50 .Build();
51 }
52
CreateSharedModule(const std::string & module_id)53 scoped_refptr<const Extension> CreateSharedModule(
54 const std::string& module_id) {
55 std::unique_ptr<base::DictionaryValue> manifest =
56 DictionaryBuilder()
57 .Set("name", "Shared Module")
58 .Set("version", "1.0")
59 .Set("manifest_version", 2)
60 .Set("export",
61 DictionaryBuilder()
62 .Set("resources", ListBuilder().Append("foo.js").Build())
63 .Build())
64 .Build();
65
66 return ExtensionBuilder()
67 .SetManifest(std::move(manifest))
68 .AddFlags(Extension::FROM_WEBSTORE)
69 .SetID(crx_file::id_util::GenerateId(module_id))
70 .Build();
71 }
72
73 } // namespace
74
75 class SharedModuleServiceUnitTest : public ExtensionServiceTestBase {
76 public:
SharedModuleServiceUnitTest()77 SharedModuleServiceUnitTest() :
78 // The "export" key is open for dev-channel only, but unit tests
79 // run as stable channel on the official Windows build.
80 current_channel_(version_info::Channel::UNKNOWN) {}
81 protected:
82 void SetUp() override;
83
84 // Install an extension and notify the ExtensionService.
85 testing::AssertionResult InstallExtension(const Extension* extension,
86 bool is_update);
87 ScopedCurrentChannel current_channel_;
88 };
89
SetUp()90 void SharedModuleServiceUnitTest::SetUp() {
91 ExtensionServiceTestBase::SetUp();
92 InitializeGoodInstalledExtensionService();
93 service()->Init();
94 }
95
InstallExtension(const Extension * extension,bool is_update)96 testing::AssertionResult SharedModuleServiceUnitTest::InstallExtension(
97 const Extension* extension,
98 bool is_update) {
99
100 const Extension* old = registry()->GetExtensionById(
101 extension->id(),
102 ExtensionRegistry::ENABLED);
103
104 // Verify the extension is not already installed, if it is not update.
105 if (!is_update) {
106 if (old)
107 return testing::AssertionFailure() << "Extension already installed.";
108 } else {
109 if (!old)
110 return testing::AssertionFailure() << "The extension does not exist.";
111 }
112
113 // Notify the service that the extension is installed. This adds it to the
114 // registry, notifies interested parties, etc.
115 service()->OnExtensionInstalled(
116 extension, syncer::StringOrdinal(), kInstallFlagInstallImmediately);
117
118 // Verify that the extension is now installed.
119 if (!registry()->GetExtensionById(extension->id(),
120 ExtensionRegistry::ENABLED)) {
121 return testing::AssertionFailure() << "Could not install extension.";
122 }
123
124 return testing::AssertionSuccess();
125 }
126
TEST_F(SharedModuleServiceUnitTest,AddDependentSharedModules)127 TEST_F(SharedModuleServiceUnitTest, AddDependentSharedModules) {
128 // Create an extension that has a dependency.
129 std::string import_id = crx_file::id_util::GenerateId("id");
130 std::string extension_id = crx_file::id_util::GenerateId("extension_id");
131 scoped_refptr<const Extension> extension = CreateExtensionImportingModules(
132 std::vector<std::string>(1, import_id), extension_id, "1.0");
133
134 PendingExtensionManager* pending_extension_manager =
135 service()->pending_extension_manager();
136
137 // Verify that we don't currently want to install the imported module.
138 EXPECT_FALSE(pending_extension_manager->IsIdPending(import_id));
139
140 // Try to satisfy imports for the extension. This should queue the imported
141 // module's installation.
142 service()->shared_module_service()->SatisfyImports(extension.get());
143 EXPECT_TRUE(pending_extension_manager->IsIdPending(import_id));
144 }
145
TEST_F(SharedModuleServiceUnitTest,PruneSharedModulesOnUninstall)146 TEST_F(SharedModuleServiceUnitTest, PruneSharedModulesOnUninstall) {
147 // Create a module which exports a resource, and install it.
148 scoped_refptr<const Extension> shared_module =
149 CreateSharedModule("shared_module");
150
151 EXPECT_TRUE(InstallExtension(shared_module.get(), false));
152
153 std::string extension_id = crx_file::id_util::GenerateId("extension_id");
154 // Create and install an extension that imports our new module.
155 scoped_refptr<const Extension> importing_extension =
156 CreateExtensionImportingModules(
157 std::vector<std::string>(1, shared_module->id()), extension_id,
158 "1.0");
159 EXPECT_TRUE(InstallExtension(importing_extension.get(), false));
160
161 // Uninstall the extension that imports our module.
162 base::string16 error;
163 service()->UninstallExtension(importing_extension->id(),
164 extensions::UNINSTALL_REASON_FOR_TESTING,
165 &error);
166 EXPECT_TRUE(error.empty());
167
168 // Since the module was only referenced by that single extension, it should
169 // have been uninstalled as a side-effect of uninstalling the extension that
170 // depended upon it.
171 EXPECT_FALSE(registry()->GetExtensionById(shared_module->id(),
172 ExtensionRegistry::EVERYTHING));
173 }
174
TEST_F(SharedModuleServiceUnitTest,PruneSharedModulesOnUpdate)175 TEST_F(SharedModuleServiceUnitTest, PruneSharedModulesOnUpdate) {
176 // Create two modules which export a resource, and install them.
177 scoped_refptr<const Extension> shared_module_1 =
178 CreateSharedModule("shared_module_1");
179 EXPECT_TRUE(InstallExtension(shared_module_1.get(), false));
180
181 std::unique_ptr<base::DictionaryValue> manifest_2 =
182 DictionaryBuilder()
183 .Set("name", "Shared Module 2")
184 .Set("version", "1.0")
185 .Set("manifest_version", 2)
186 .Set("export",
187 DictionaryBuilder()
188 .Set("resources", ListBuilder().Append("foo.js").Build())
189 .Build())
190 .Build();
191 scoped_refptr<const Extension> shared_module_2 =
192 CreateSharedModule("shared_module_2");
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<const Extension> importing_extension_1 =
199 CreateExtensionImportingModules(
200 std::vector<std::string>(1, shared_module_1->id()), 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<const Extension> importing_extension_2 =
207 CreateExtensionImportingModules(
208 std::vector<std::string>(1, shared_module_2->id()), 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<const Extension> importing_extension_3 =
222 CreateExtensionImportingModules(std::vector<std::string>(), extension_id,
223 "1.2");
224 EXPECT_TRUE(InstallExtension(importing_extension_3.get(), true));
225
226 // Since the extension v1.2 does not depend any module, so the all models
227 // should have been uninstalled.
228 EXPECT_FALSE(registry()->GetExtensionById(shared_module_1->id(),
229 ExtensionRegistry::EVERYTHING));
230 EXPECT_FALSE(registry()->GetExtensionById(shared_module_2->id(),
231 ExtensionRegistry::EVERYTHING));
232
233 }
234
TEST_F(SharedModuleServiceUnitTest,AllowlistedImports)235 TEST_F(SharedModuleServiceUnitTest, AllowlistedImports) {
236 std::string allowlisted_id = crx_file::id_util::GenerateId("allowlisted");
237 std::string nonallowlisted_id =
238 crx_file::id_util::GenerateId("nonallowlisted");
239 // Create a module which exports to a restricted allowlist.
240 std::unique_ptr<base::DictionaryValue> manifest =
241 DictionaryBuilder()
242 .Set("name", "Shared Module")
243 .Set("version", "1.0")
244 .Set("manifest_version", 2)
245 .Set("export",
246 DictionaryBuilder()
247 .Set("allowlist",
248 ListBuilder().Append(allowlisted_id).Build())
249 .Set("resources", ListBuilder().Append("*").Build())
250 .Build())
251 .Build();
252 scoped_refptr<const Extension> shared_module =
253 ExtensionBuilder()
254 .SetManifest(std::move(manifest))
255 .AddFlags(Extension::FROM_WEBSTORE)
256 .SetID(crx_file::id_util::GenerateId("shared_module"))
257 .Build();
258
259 EXPECT_TRUE(InstallExtension(shared_module.get(), false));
260
261 // Create and install an extension with the allowlisted ID.
262 scoped_refptr<const Extension> allowlisted_extension =
263 CreateExtensionImportingModules(
264 std::vector<std::string>(1, shared_module->id()), allowlisted_id,
265 "1.0");
266 EXPECT_TRUE(InstallExtension(allowlisted_extension.get(), false));
267
268 // Try to install an extension with an ID that is not allowlisted.
269 scoped_refptr<const Extension> nonallowlisted_extension =
270 CreateExtensionImportingModules(
271 std::vector<std::string>(1, shared_module->id()), nonallowlisted_id,
272 "1.0");
273 // This should succeed because only CRX installer (and by extension the
274 // WebStore Installer) checks the shared module allowlist. InstallExtension
275 // bypasses the allowlist check because the SharedModuleService does not
276 // care about allowlists.
277 EXPECT_TRUE(InstallExtension(nonallowlisted_extension.get(), false));
278 }
279
TEST_F(SharedModuleServiceUnitTest,PruneMultipleSharedModules)280 TEST_F(SharedModuleServiceUnitTest, PruneMultipleSharedModules) {
281 // Create two modules which export a resource each, and install it.
282 scoped_refptr<const Extension> shared_module_one =
283 CreateSharedModule("shared_module_one");
284 EXPECT_TRUE(InstallExtension(shared_module_one.get(), false));
285 scoped_refptr<const Extension> shared_module_two =
286 CreateSharedModule("shared_module_two");
287 EXPECT_TRUE(InstallExtension(shared_module_two.get(), false));
288
289 std::string extension_id = crx_file::id_util::GenerateId("extension_id");
290 std::vector<std::string> module_ids;
291 module_ids.push_back(shared_module_one->id());
292 module_ids.push_back(shared_module_two->id());
293 // Create and install an extension that imports both the modules.
294 scoped_refptr<const Extension> importing_extension =
295 CreateExtensionImportingModules(module_ids, extension_id, "1.0");
296 EXPECT_TRUE(InstallExtension(importing_extension.get(), false));
297
298 // Uninstall the extension that imports our modules.
299 base::string16 error;
300 service()->UninstallExtension(importing_extension->id(),
301 extensions::UNINSTALL_REASON_FOR_TESTING,
302 &error);
303 EXPECT_TRUE(error.empty());
304
305 // Since the modules were only referenced by that single extension, they
306 // should have been uninstalled as a side-effect of uninstalling the extension
307 // that depended upon it.
308 EXPECT_FALSE(registry()->GetExtensionById(shared_module_one->id(),
309 ExtensionRegistry::EVERYTHING));
310 EXPECT_FALSE(registry()->GetExtensionById(shared_module_two->id(),
311 ExtensionRegistry::EVERYTHING));
312 }
313
314 } // namespace extensions
315