// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/common/extension.h" #include "base/command_line.h" #include "base/test/scoped_command_line.h" #include "base/test/scoped_feature_list.h" #include "extensions/common/error_utils.h" #include "extensions/common/extension_features.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/switches.h" #include "extensions/common/value_builder.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/types/optional.h" using extensions::mojom::ManifestLocation; namespace extensions { namespace { std::string GetVersionTooHighWarning(int max_version, int supplied_version) { return ErrorUtils::FormatErrorMessage( manifest_errors::kManifestVersionTooHighWarning, base::NumberToString(max_version), base::NumberToString(supplied_version)); } testing::AssertionResult RunManifestVersionSuccess( std::unique_ptr manifest, Manifest::Type expected_type, int expected_manifest_version, base::StringPiece expected_warning = "", Extension::InitFromValueFlags custom_flag = Extension::NO_FLAGS, ManifestLocation manifest_location = ManifestLocation::kInternal) { std::string error; scoped_refptr extension = Extension::Create( base::FilePath(), manifest_location, *manifest, custom_flag, &error); if (!extension) { return testing::AssertionFailure() << "Extension creation failed: " << error; } if (extension->GetType() != expected_type) { return testing::AssertionFailure() << "Wrong type: " << extension->GetType(); } if (extension->manifest_version() != expected_manifest_version) { return testing::AssertionFailure() << "Wrong manifest version: " << extension->manifest_version(); } std::string manifest_version_warning; for (const auto& warning : extension->install_warnings()) { if (warning.key == manifest_keys::kManifestVersion) { manifest_version_warning = warning.message; break; } } if (expected_warning != manifest_version_warning) { return testing::AssertionFailure() << "Expected warning: '" << expected_warning << "', Found Warning: '" << manifest_version_warning << "'"; } return testing::AssertionSuccess(); } testing::AssertionResult RunManifestVersionFailure( std::unique_ptr manifest, Extension::InitFromValueFlags custom_flag = Extension::NO_FLAGS) { std::string error; scoped_refptr extension = Extension::Create(base::FilePath(), ManifestLocation::kInternal, *manifest, custom_flag, &error); if (extension) return testing::AssertionFailure() << "Extension creation succeeded."; return testing::AssertionSuccess(); } testing::AssertionResult RunCreationWithFlags( const base::DictionaryValue* manifest, mojom::ManifestLocation location, Manifest::Type expected_type, Extension::InitFromValueFlags custom_flag = Extension::NO_FLAGS) { std::string error; scoped_refptr extension = Extension::Create( base::FilePath(), location, *manifest, custom_flag, &error); if (!extension) { return testing::AssertionFailure() << "Extension creation failed: " << error; } if (extension->GetType() != expected_type) { return testing::AssertionFailure() << "Wrong type: " << extension->GetType(); } return testing::AssertionSuccess(); } } // namespace // TODO(devlin): Move tests from chrome/common/extensions/extension_unittest.cc // that don't depend on //chrome into here. TEST(ExtensionTest, ExtensionManifestVersions) { auto get_manifest = [](absl::optional manifest_version) { DictionaryBuilder builder; builder.Set("name", "My Extension") .Set("version", "0.1") .Set("description", "An awesome extension"); if (manifest_version) builder.Set("manifest_version", *manifest_version); return builder.Build(); }; const Manifest::Type kType = Manifest::TYPE_EXTENSION; EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(2), kType, 2)); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(3), kType, 3)); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(4), kType, 4, GetVersionTooHighWarning(3, 4))); // Loading an unpacked MV2 extension should emit a warning. EXPECT_TRUE(RunManifestVersionSuccess( get_manifest(2), kType, 2, manifest_errors::kManifestV2IsDeprecatedWarning, Extension::NO_FLAGS, ManifestLocation::kUnpacked)); // Manifest v1 is deprecated, and should not load. EXPECT_TRUE(RunManifestVersionFailure(get_manifest(1))); // Omitting the key defaults to v1 for extensions. EXPECT_TRUE(RunManifestVersionFailure(get_manifest(absl::nullopt))); // '0' and '-1' are invalid values. EXPECT_TRUE(RunManifestVersionFailure(get_manifest(0))); EXPECT_TRUE(RunManifestVersionFailure(get_manifest(-1))); { // Manifest v1 should only load if a command line switch is used. base::test::ScopedCommandLine command_line; command_line.GetProcessCommandLine()->AppendSwitch( switches::kAllowLegacyExtensionManifests); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(1), kType, 1)); EXPECT_TRUE( RunManifestVersionSuccess(get_manifest(absl::nullopt), kType, 1)); } } TEST(ExtensionTest, PlatformAppManifestVersions) { auto get_manifest = [](absl::optional manifest_version) { DictionaryBuilder background; background.Set("scripts", ListBuilder().Append("background.js").Build()); DictionaryBuilder builder; builder.Set("name", "My Platform App") .Set("version", "0.1") .Set("description", "A platform app") .Set("app", DictionaryBuilder().Set("background", background.Build()).Build()); if (manifest_version) builder.Set("manifest_version", *manifest_version); return builder.Build(); }; const Manifest::Type kType = Manifest::TYPE_PLATFORM_APP; EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(2), kType, 2)); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(3), kType, 3)); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(4), kType, 4, GetVersionTooHighWarning(3, 4))); // Omitting the key defaults to v2 for platform apps. EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(absl::nullopt), kType, 2)); // Manifest v1 is deprecated, and should not load. EXPECT_TRUE(RunManifestVersionFailure(get_manifest(1))); // '0' and '-1' are invalid values. EXPECT_TRUE(RunManifestVersionFailure(get_manifest(0))); EXPECT_TRUE(RunManifestVersionFailure(get_manifest(-1))); { // Manifest v1 should not load for platform apps, even with the command line // switch. base::test::ScopedCommandLine command_line; command_line.GetProcessCommandLine()->AppendSwitch( switches::kAllowLegacyExtensionManifests); EXPECT_TRUE(RunManifestVersionFailure(get_manifest(1))); } } TEST(ExtensionTest, HostedAppManifestVersions) { auto get_manifest = [](absl::optional manifest_version) { DictionaryBuilder builder; DictionaryBuilder app; app.Set("urls", ListBuilder().Append("http://example.com").Build()); builder.Set("name", "My Hosted App") .Set("version", "0.1") .Set("description", "A hosted app") .Set("app", app.Build()); if (manifest_version) builder.Set("manifest_version", *manifest_version); return builder.Build(); }; const Manifest::Type kType = Manifest::TYPE_HOSTED_APP; EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(2), kType, 2)); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(3), kType, 3)); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(4), kType, 4, GetVersionTooHighWarning(3, 4))); // Manifest v1 is deprecated, but should still load for hosted apps. EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(1), kType, 1)); // Omitting the key defaults to v1 for hosted apps, and v1 is still allowed. EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(absl::nullopt), kType, 1)); // Requiring the modern manifest version should make hosted apps require v2. EXPECT_TRUE(RunManifestVersionFailure( get_manifest(1), Extension::REQUIRE_MODERN_MANIFEST_VERSION)); } TEST(ExtensionTest, UserScriptManifestVersions) { auto get_manifest = [](absl::optional manifest_version) { DictionaryBuilder builder; builder.Set("name", "My Extension") .Set("version", "0.1") .Set("description", "An awesome extension") .Set("converted_from_user_script", true); if (manifest_version) builder.Set("manifest_version", *manifest_version); return builder.Build(); }; const Manifest::Type kType = Manifest::TYPE_USER_SCRIPT; EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(2), kType, 2)); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(3), kType, 3)); EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(4), kType, 4, GetVersionTooHighWarning(3, 4))); // Manifest v1 is deprecated, but should still load for user scripts. EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(1), kType, 1)); // Omitting the key defaults to v1 for user scripts, but v1 is still allowed. EXPECT_TRUE(RunManifestVersionSuccess(get_manifest(absl::nullopt), kType, 1)); // Requiring the modern manifest version should make user scripts require v2. EXPECT_TRUE(RunManifestVersionFailure( get_manifest(1), Extension::REQUIRE_MODERN_MANIFEST_VERSION)); } TEST(ExtensionTest, LoginScreenFlag) { DictionaryBuilder builder; builder.Set("name", "My Extension") .Set("version", "0.1") .Set("description", "An awesome extension") .Set("manifest_version", 2); std::unique_ptr manifest = builder.Build(); EXPECT_TRUE( RunCreationWithFlags(manifest.get(), ManifestLocation::kExternalPolicy, Manifest::TYPE_EXTENSION, Extension::NO_FLAGS)); EXPECT_TRUE(RunCreationWithFlags( manifest.get(), ManifestLocation::kExternalPolicy, Manifest::TYPE_LOGIN_SCREEN_EXTENSION, Extension::FOR_LOGIN_SCREEN)); } } // namespace extensions