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
22namespace extensions {
23
24namespace {
25
26// Return an extension with |id| which imports a module with the given
27// |import_id|.
28scoped_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
50class 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
65void SharedModuleServiceUnitTest::SetUp() {
66  ExtensionServiceTestBase::SetUp();
67  InitializeGoodInstalledExtensionService();
68  service()->Init();
69}
70
71testing::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
102TEST_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
121TEST_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
161TEST_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
234TEST_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