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