blob: 394a298134c30b37d44296a9c1c23cf0e32bd2f1 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/manifest_handlers/permissions_parser.h"
#include "base/containers/contains.h"
#include "base/strings/stringprintf.h"
#include "base/test/values_test_util.h"
#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/mojom/api_permission_id.mojom.h"
#include "extensions/common/permissions/permissions_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
using PermissionsParserTest = ChromeManifestTest;
TEST_F(PermissionsParserTest, RemoveOverlappingAPIPermissions) {
scoped_refptr<Extension> extension(LoadAndExpectWarning(
"permissions_overlapping_api_permissions.json",
ErrorUtils::FormatErrorMessage(
manifest_errors::kPermissionMarkedOptionalAndRequired, "tabs")));
std::set<std::string> required_api_names =
PermissionsParser::GetRequiredPermissions(extension.get())
.GetAPIsAsStrings();
std::set<std::string> optional_api_names =
PermissionsParser::GetOptionalPermissions(extension.get())
.GetAPIsAsStrings();
// Check that required api permissions have not changed while "tabs" is
// removed from optional permissions as it is specified as required.
EXPECT_THAT(required_api_names,
testing::UnorderedElementsAre("tabs", "storage"));
EXPECT_THAT(optional_api_names, testing::UnorderedElementsAre("bookmarks"));
}
TEST_F(PermissionsParserTest, RemoveOverlappingHostPermissions) {
scoped_refptr<Extension> extension(LoadAndExpectWarning(
"permissions_overlapping_host_permissions.json",
ErrorUtils::FormatErrorMessage(
manifest_errors::kPermissionMarkedOptionalAndRequired,
"https://google.com/*")));
const URLPatternSet& required_hosts =
PermissionsParser::GetRequiredPermissions(extension.get())
.explicit_hosts();
const URLPatternSet& optional_hosts =
PermissionsParser::GetOptionalPermissions(extension.get())
.explicit_hosts();
// Check that required hosts have not changed at all while
// "https://google.com/maps" is removed from optional hosts as it is a strict
// subset of hosts specified as required.
EXPECT_THAT(required_hosts.ToStringVector(),
testing::UnorderedElementsAre("https://example.com/*",
"https://*.google.com/*"));
EXPECT_THAT(optional_hosts.ToStringVector(),
testing::UnorderedElementsAre("*://chromium.org/*"));
}
// Same as the above test, except host permissions are specified in
// `host_permissions` and `optional_host_permissions` as the extension is
// running Manifest V3.
TEST_F(PermissionsParserTest, RemoveOverlappingHostPermissions_ManifestV3) {
scoped_refptr<Extension> extension(LoadAndExpectWarning(
"permissions_overlapping_host_permissions_mv3.json",
ErrorUtils::FormatErrorMessage(
manifest_errors::kPermissionMarkedOptionalAndRequired,
"https://google.com/*")));
const URLPatternSet& required_hosts =
PermissionsParser::GetRequiredPermissions(extension.get())
.explicit_hosts();
const URLPatternSet& optional_hosts =
PermissionsParser::GetOptionalPermissions(extension.get())
.explicit_hosts();
// Check that required hosts have not changed at all while
// "https://google.com/maps" is removed from optional hosts as it is a strict
// subset of hosts specified as required.
EXPECT_THAT(required_hosts.ToStringVector(),
testing::UnorderedElementsAre("https://example.com/*",
"https://*.google.com/*"));
EXPECT_THAT(optional_hosts.ToStringVector(),
testing::UnorderedElementsAre("*://chromium.org/*"));
}
TEST_F(PermissionsParserTest, RequiredHostPermissionsAllURLs) {
scoped_refptr<Extension> extension(LoadAndExpectWarning(
"permissions_all_urls_host_permissions.json",
ErrorUtils::FormatErrorMessage(
manifest_errors::kPermissionMarkedOptionalAndRequired,
"http://*/*")));
const URLPatternSet& optional_hosts =
PermissionsParser::GetOptionalPermissions(extension.get())
.explicit_hosts();
// Since we specified <all_urls> as a required host permission,
// there should be no optional host permissions.
EXPECT_THAT(optional_hosts.ToStringVector(), testing::IsEmpty());
}
TEST_F(PermissionsParserTest, OptionalHostPermissionsAllURLs) {
scoped_refptr<Extension> extension(
LoadAndExpectSuccess("permissions_optional_all_urls_permissions.json"));
const URLPatternSet& required_hosts =
PermissionsParser::GetRequiredPermissions(extension.get())
.explicit_hosts();
const URLPatternSet& optional_hosts =
PermissionsParser::GetOptionalPermissions(extension.get())
.explicit_hosts();
// This time, required permissions are a subset of optional permissions
// so we make sure that permissions remain the same
// as what's specified in the manifest.
EXPECT_THAT(required_hosts.ToStringVector(),
testing::UnorderedElementsAre("https://*.google.com/*"));
EXPECT_THAT(optional_hosts.ToStringVector(),
testing::UnorderedElementsAre("*://*/*"));
}
TEST_F(PermissionsParserTest, OptionalHostPermissionsInvalidScheme) {
std::vector<std::string> expected_warnings;
expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
manifest_errors::kInvalidPermissionScheme,
manifest_keys::kOptionalPermissions, "chrome://extensions/"));
scoped_refptr<Extension> extension(LoadAndExpectWarnings(
"optional_permissions_invalid_scheme.json", expected_warnings));
}
TEST_F(PermissionsParserTest, HostPermissionsKey) {
std::vector<std::string> expected_warnings;
expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
manifest_errors::kPermissionUnknown, "https://google.com/*"));
expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
manifest_errors::kPermissionUnknown, "http://chromium.org/*"));
scoped_refptr<Extension> extension(
LoadAndExpectWarnings("host_permissions_key.json", expected_warnings));
// Expect that the host specified in |host_permissions| is parsed.
const URLPatternSet& required_hosts =
PermissionsParser::GetRequiredPermissions(extension.get())
.explicit_hosts();
EXPECT_THAT(required_hosts.ToStringVector(),
testing::UnorderedElementsAre("https://example.com/*"));
// Expect that the host specified in |optional_host_permissions| is parsed.
const URLPatternSet& optional_hosts =
PermissionsParser::GetOptionalPermissions(extension.get())
.explicit_hosts();
EXPECT_THAT(optional_hosts.ToStringVector(),
testing::UnorderedElementsAre("https://optional.com/*"));
}
TEST_F(PermissionsParserTest, HostPermissionsKeyInvalidHosts) {
std::vector<std::string> expected_warnings;
expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
manifest_errors::kPatternMalformed, "malformed_host"));
expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
manifest_errors::kPatternMalformed, "optional_malformed_host"));
scoped_refptr<Extension> extension(LoadAndExpectWarnings(
"host_permissions_key_invalid_hosts.json", expected_warnings));
}
TEST_F(PermissionsParserTest, HostPermissionsKeyInvalidScheme) {
std::vector<std::string> expected_warnings;
expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
manifest_errors::kInvalidPermissionScheme,
manifest_keys::kHostPermissions, "chrome://extensions/"));
expected_warnings.push_back(ErrorUtils::FormatErrorMessage(
manifest_errors::kInvalidPermissionScheme,
manifest_keys::kOptionalHostPermissions, "chrome://settings/"));
scoped_refptr<Extension> extension(LoadAndExpectWarnings(
"host_permissions_key_invalid_scheme.json", expected_warnings));
}
// Tests that listing a permissions as optional when that permission cannot be
// optional produces a warning and doesn't add the permission.
TEST_F(PermissionsParserTest, UnsupportedOptionalPermissionWarning) {
scoped_refptr<Extension> extension(LoadAndExpectWarning(
"unsupported_optional_api_permission.json",
ErrorUtils::FormatErrorMessage(
manifest_errors::kPermissionCannotBeOptional, "geolocation")));
// Check that the geolocation was not included in the optional permissions as
// it is not allowed to be optional.
std::set<std::string> optional_api_names =
PermissionsParser::GetOptionalPermissions(extension.get())
.GetAPIsAsStrings();
EXPECT_THAT(optional_api_names, testing::UnorderedElementsAre("tabs"));
}
// Test that chrome://favicon is a supported permission in MV2, but not MV3.
TEST_F(PermissionsParserTest, ChromeFavicon) {
auto get_manifest_data = [](int manifest_version, const char* permission) {
constexpr char kManifestStub[] =
R"({
"name": "Test",
"version": "0.1",
"manifest_version": %d,
"%s": ["%s"]
})";
const char* permissions_key = manifest_version > 2
? manifest_keys::kHostPermissions
: manifest_keys::kPermissions;
base::Value manifest_value = base::test::ParseJson(base::StringPrintf(
kManifestStub, manifest_version, permissions_key, permission));
EXPECT_TRUE(manifest_value.is_dict());
return ManifestData(std::move(manifest_value).TakeDict(), permission);
};
static constexpr char kFaviconPattern[] = "chrome://favicon/*";
// <all_urls> implicitly includes chrome://favicon, if it's supported.
constexpr char kAllUrls[] = "<all_urls>";
auto has_favicon_access = [](const Extension& extension) {
const GURL favicon_url("chrome://favicon");
return extension.permissions_data()->HasHostPermission(favicon_url);
};
auto has_install_warning = [](const Extension& extension) {
const char* permissions_key = extension.manifest_version() > 2
? manifest_keys::kHostPermissions
: manifest_keys::kPermissions;
InstallWarning expected_warning(ErrorUtils::FormatErrorMessage(
manifest_errors::kInvalidPermissionScheme, permissions_key,
kFaviconPattern));
return base::Contains(extension.install_warnings(), expected_warning);
};
{
scoped_refptr<const Extension> extension =
LoadAndExpectSuccess(get_manifest_data(2, kFaviconPattern));
ASSERT_TRUE(extension);
EXPECT_TRUE(has_favicon_access(*extension));
EXPECT_FALSE(has_install_warning(*extension));
}
{
scoped_refptr<const Extension> extension =
LoadAndExpectSuccess(get_manifest_data(2, kAllUrls));
ASSERT_TRUE(extension);
EXPECT_TRUE(has_favicon_access(*extension));
EXPECT_FALSE(has_install_warning(*extension));
}
{
scoped_refptr<const Extension> extension =
LoadAndExpectSuccess(get_manifest_data(3, kFaviconPattern));
ASSERT_TRUE(extension);
EXPECT_FALSE(has_favicon_access(*extension));
// Since chrome://favicon is not a valid permission in MV3, we expect a
// warning to be thrown.
EXPECT_TRUE(has_install_warning(*extension));
}
{
scoped_refptr<const Extension> extension =
LoadAndExpectSuccess(get_manifest_data(3, kAllUrls));
ASSERT_TRUE(extension);
EXPECT_FALSE(has_favicon_access(*extension));
// NOTE: We don't expect an install warning here, because the <all_urls>
// permission is still supported. It just doesn't grant favicon access.
EXPECT_FALSE(has_install_warning(*extension));
}
}
TEST_F(PermissionsParserTest, InternalPermissionsAreNotAllowedInTheManifest) {
static constexpr char kManifest[] =
R"({
"name": "My Extension",
"manifest_version": 3,
"version": "0.1",
"permissions": ["searchProvider"]
})";
scoped_refptr<const Extension> extension =
LoadAndExpectWarning(ManifestData::FromJSON(kManifest),
"Permission 'searchProvider' is unknown.");
ASSERT_TRUE(extension);
EXPECT_FALSE(extension->permissions_data()->HasAPIPermission(
mojom::APIPermissionID::kSearchProvider));
}
} // namespace extensions