blob: 9f5239613f759843b81d29821c2199164cb80761 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/permissions/permissions_api_helpers.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "base/json/json_reader.h"
#include "base/values.h"
#include "chrome/browser/extensions/permissions/permissions_test_util.h"
#include "chrome/common/extensions/api/permissions.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_info.h"
#include "extensions/common/permissions/usb_device_permission.h"
#include "extensions/common/url_pattern_set.h"
#include "extensions/common/user_script.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
using extensions::api::permissions::Permissions;
using extensions::mojom::APIPermissionID;
using extensions::permissions_api_helpers::PackPermissionSet;
using extensions::permissions_api_helpers::UnpackPermissionSet;
using extensions::permissions_api_helpers::UnpackPermissionSetResult;
using extensions::permissions_test_util::GetPatternsAsStrings;
namespace extensions {
// Tests that we can convert PermissionSets to the generated types.
TEST(PermissionsApiHelpersTest, Pack) {
APIPermissionSet apis;
apis.insert(APIPermissionID::kTab);
URLPatternSet explicit_hosts(
{URLPattern(Extension::kValidHostPermissionSchemes, "http://a.com/*"),
URLPattern(Extension::kValidHostPermissionSchemes, "http://b.com/*")});
URLPatternSet scriptable_hosts(
{URLPattern(UserScript::ValidUserScriptSchemes(), "http://c.com/*"),
URLPattern(UserScript::ValidUserScriptSchemes(), "http://d.com/*")});
// Pack the permission set to value and verify its contents.
std::unique_ptr<Permissions> pack_result(PackPermissionSet(
PermissionSet(std::move(apis), ManifestPermissionSet(),
std::move(explicit_hosts), std::move(scriptable_hosts))));
ASSERT_TRUE(pack_result);
ASSERT_TRUE(pack_result->permissions);
EXPECT_THAT(*pack_result->permissions, testing::UnorderedElementsAre("tabs"));
ASSERT_TRUE(pack_result->origins);
EXPECT_THAT(*pack_result->origins, testing::UnorderedElementsAre(
"http://a.com/*", "http://b.com/*",
"http://c.com/*", "http://d.com/*"));
}
// Tests various error conditions and edge cases when unpacking Dicts
// into PermissionSets.
TEST(PermissionsApiHelpersTest, Unpack_Basic) {
base::Value::List apis;
apis.Append("tabs");
base::Value::List origins;
origins.Append("http://a.com/*");
std::unique_ptr<const PermissionSet> permissions;
std::string error;
APIPermissionSet optional_apis;
optional_apis.insert(APIPermissionID::kTab);
URLPatternSet optional_explicit_hosts(
{URLPattern(Extension::kValidHostPermissionSchemes, "http://a.com/*")});
PermissionSet optional_permissions(
std::move(optional_apis), ManifestPermissionSet(),
std::move(optional_explicit_hosts), URLPatternSet());
// Origins shouldn't have to be present.
{
base::Value::Dict dict;
dict.Set("permissions", apis.Clone());
auto permissions_object = Permissions::FromValue(dict);
EXPECT_TRUE(permissions_object);
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(*permissions_object, PermissionSet(),
optional_permissions, true, &error);
ASSERT_TRUE(unpack_result);
EXPECT_TRUE(error.empty());
EXPECT_EQ(1u, unpack_result->optional_apis.size());
EXPECT_TRUE(
unpack_result->optional_apis.count(mojom::APIPermissionID::kTab));
}
// The api permissions don't need to be present either.
{
base::Value::Dict dict;
dict.Set("origins", origins.Clone());
auto permissions_object = Permissions::FromValue(dict);
EXPECT_TRUE(permissions_object);
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(*permissions_object, PermissionSet(),
optional_permissions, true, &error);
ASSERT_TRUE(unpack_result);
EXPECT_TRUE(error.empty());
EXPECT_THAT(permissions_test_util::GetPatternsAsStrings(
unpack_result->optional_explicit_hosts),
testing::UnorderedElementsAre("http://a.com/*"));
}
// Throw errors for non-string API permissions.
{
base::Value::Dict dict;
base::Value::List invalid_apis = apis.Clone();
invalid_apis.Append(3);
dict.Set("permissions", std::move(invalid_apis));
auto permissions_object = Permissions::FromValue(dict);
EXPECT_FALSE(permissions_object);
}
// Throw errors for non-string origins.
{
base::Value::Dict dict;
base::Value::List invalid_origins = origins.Clone();
invalid_origins.Append(3);
dict.Set("origins", std::move(invalid_origins));
auto permissions_object = Permissions::FromValue(dict);
EXPECT_FALSE(permissions_object);
}
// Throw errors when "origins" or "permissions" are not list values.
{
base::Value::Dict dict;
dict.Set("origins", 2);
auto permissions_object = Permissions::FromValue(dict);
EXPECT_FALSE(permissions_object);
}
{
base::Value::Dict dict;
dict.Set("permissions", 2);
auto permissions_object = Permissions::FromValue(dict);
EXPECT_FALSE(permissions_object);
}
// Additional fields should be allowed.
{
base::Value::Dict dict;
dict.Set("origins", origins.Clone());
dict.Set("random", 3);
auto permissions_object = Permissions::FromValue(dict);
EXPECT_TRUE(permissions_object);
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(*permissions_object, PermissionSet(),
optional_permissions, true, &error);
ASSERT_TRUE(unpack_result);
EXPECT_TRUE(error.empty());
EXPECT_THAT(permissions_test_util::GetPatternsAsStrings(
unpack_result->optional_explicit_hosts),
testing::UnorderedElementsAre("http://a.com/*"));
}
// Unknown permissions should throw an error.
{
base::Value::Dict dict;
base::Value::List invalid_apis = apis.Clone();
invalid_apis.Append("unknown_permission");
dict.Set("permissions", std::move(invalid_apis));
auto permissions_object = Permissions::FromValue(dict);
EXPECT_TRUE(permissions_object);
EXPECT_FALSE(UnpackPermissionSet(*permissions_object, PermissionSet(),
optional_permissions, true, &error));
EXPECT_EQ(error, "'unknown_permission' is not a recognized permission.");
}
}
// Tests that host permissions are properly partitioned according to the
// required/optional permission sets.
TEST(PermissionsApiHelpersTest, Unpack_HostSeparation) {
auto explicit_url_pattern = [](const char* pattern) {
return URLPattern(Extension::kValidHostPermissionSchemes, pattern);
};
auto scriptable_url_pattern = [](const char* pattern) {
return URLPattern(UserScript::ValidUserScriptSchemes(), pattern);
};
constexpr char kRequiredExplicit1[] = "https://required_explicit1.com/*";
constexpr char kRequiredExplicit2[] = "https://required_explicit2.com/*";
constexpr char kOptionalExplicit1[] = "https://optional_explicit1.com/*";
constexpr char kOptionalExplicit2[] = "https://optional_explicit2.com/*";
constexpr char kRequiredScriptable1[] = "https://required_scriptable1.com/*";
constexpr char kRequiredScriptable2[] = "https://required_scriptable2.com/*";
constexpr char kRequiredExplicitAndScriptable1[] =
"https://required_explicit_and_scriptable1.com/*";
constexpr char kRequiredExplicitAndScriptable2[] =
"https://required_explicit_and_scriptable2.com/*";
constexpr char kOptionalExplicitAndRequiredScriptable1[] =
"https://optional_explicit_and_scriptable1.com/*";
constexpr char kOptionalExplicitAndRequiredScriptable2[] =
"https://optional_explicit_and_scriptable2.com/*";
constexpr char kUnlisted1[] = "https://unlisted1.com/*";
URLPatternSet required_explicit_hosts({
explicit_url_pattern(kRequiredExplicit1),
explicit_url_pattern(kRequiredExplicit2),
explicit_url_pattern(kRequiredExplicitAndScriptable1),
explicit_url_pattern(kRequiredExplicitAndScriptable2),
});
URLPatternSet required_scriptable_hosts({
scriptable_url_pattern(kRequiredScriptable1),
scriptable_url_pattern(kRequiredScriptable2),
scriptable_url_pattern(kRequiredExplicitAndScriptable1),
scriptable_url_pattern(kRequiredExplicitAndScriptable2),
scriptable_url_pattern(kOptionalExplicitAndRequiredScriptable1),
scriptable_url_pattern(kOptionalExplicitAndRequiredScriptable2),
});
URLPatternSet optional_explicit_hosts({
explicit_url_pattern(kOptionalExplicit1),
explicit_url_pattern(kOptionalExplicit2),
explicit_url_pattern(kOptionalExplicitAndRequiredScriptable1),
explicit_url_pattern(kOptionalExplicitAndRequiredScriptable2),
});
PermissionSet required_permissions(
APIPermissionSet(), ManifestPermissionSet(),
std::move(required_explicit_hosts), std::move(required_scriptable_hosts));
PermissionSet optional_permissions(
APIPermissionSet(), ManifestPermissionSet(),
std::move(optional_explicit_hosts), URLPatternSet());
Permissions permissions_object;
permissions_object.origins = std::vector<std::string>(
{kRequiredExplicit1, kOptionalExplicit1, kRequiredScriptable1,
kRequiredExplicitAndScriptable1, kOptionalExplicitAndRequiredScriptable1,
kUnlisted1});
std::string error;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(permissions_object, required_permissions,
optional_permissions, true, &error);
ASSERT_TRUE(unpack_result);
EXPECT_TRUE(error.empty()) << error;
EXPECT_THAT(GetPatternsAsStrings(unpack_result->required_explicit_hosts),
testing::UnorderedElementsAre(kRequiredExplicit1,
kRequiredExplicitAndScriptable1));
EXPECT_THAT(GetPatternsAsStrings(unpack_result->optional_explicit_hosts),
testing::UnorderedElementsAre(
kOptionalExplicit1, kOptionalExplicitAndRequiredScriptable1));
EXPECT_THAT(GetPatternsAsStrings(unpack_result->required_scriptable_hosts),
testing::UnorderedElementsAre(
kRequiredScriptable1, kRequiredExplicitAndScriptable1,
kOptionalExplicitAndRequiredScriptable1));
EXPECT_THAT(GetPatternsAsStrings(unpack_result->unlisted_hosts),
testing::UnorderedElementsAre(kUnlisted1));
}
// Tests that host permissions are properly partitioned according to the
// required/optional permission sets.
TEST(PermissionsApiHelpersTest, Unpack_APISeparation) {
constexpr APIPermissionID kRequired1 = APIPermissionID::kTab;
constexpr APIPermissionID kRequired2 = APIPermissionID::kStorage;
constexpr APIPermissionID kOptional1 = APIPermissionID::kCookie;
constexpr APIPermissionID kOptional2 = APIPermissionID::kAlarms;
constexpr APIPermissionID kUnlisted1 = APIPermissionID::kIdle;
APIPermissionSet required_apis;
required_apis.insert(kRequired1);
required_apis.insert(kRequired2);
APIPermissionSet optional_apis;
optional_apis.insert(kOptional1);
optional_apis.insert(kOptional2);
PermissionSet required_permissions(std::move(required_apis),
ManifestPermissionSet(), URLPatternSet(),
URLPatternSet());
PermissionSet optional_permissions(std::move(optional_apis),
ManifestPermissionSet(), URLPatternSet(),
URLPatternSet());
Permissions permissions_object;
permissions_object.permissions =
std::vector<std::string>({"tabs", "cookies", "idle"});
std::string error;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(permissions_object, required_permissions,
optional_permissions, true, &error);
ASSERT_TRUE(unpack_result);
EXPECT_TRUE(error.empty()) << error;
EXPECT_EQ(1u, unpack_result->required_apis.size());
EXPECT_TRUE(unpack_result->required_apis.count(kRequired1));
EXPECT_EQ(1u, unpack_result->optional_apis.size());
EXPECT_TRUE(unpack_result->optional_apis.count(kOptional1));
EXPECT_EQ(1u, unpack_result->unlisted_apis.size());
EXPECT_TRUE(unpack_result->unlisted_apis.count(kUnlisted1));
}
// Tests that unpacking works correctly with wildcard schemes (which are
// interesting, because they only match http | https, and not all schemes).
TEST(PermissionsApiHelpersTest, Unpack_WildcardSchemes) {
constexpr char kWildcardSchemePattern[] = "*://*/*";
PermissionSet optional_permissions(
APIPermissionSet(), ManifestPermissionSet(),
URLPatternSet({URLPattern(Extension::kValidHostPermissionSchemes,
kWildcardSchemePattern)}),
URLPatternSet());
Permissions permissions_object;
permissions_object.origins =
std::vector<std::string>({kWildcardSchemePattern});
std::string error;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(permissions_object, PermissionSet(),
optional_permissions, true, &error);
ASSERT_TRUE(unpack_result) << error;
EXPECT_THAT(GetPatternsAsStrings(unpack_result->optional_explicit_hosts),
testing::ElementsAre(kWildcardSchemePattern));
}
// Tests that unpacking <all_urls> correctly includes or omits the file:-scheme.
TEST(PermissionsApiHelpersTest, Unpack_FileSchemes_AllUrls) {
// Without file access, <all_urls> should be parsed, but the resulting pattern
// should not include file:-scheme access.
{
const int kNonFileSchemes =
Extension::kValidHostPermissionSchemes & ~URLPattern::SCHEME_FILE;
URLPattern all_urls(kNonFileSchemes, URLPattern::kAllUrlsPattern);
PermissionSet required_permissions(
APIPermissionSet(), ManifestPermissionSet(), URLPatternSet({all_urls}),
URLPatternSet());
Permissions permissions_object;
permissions_object.origins =
std::vector<std::string>({URLPattern::kAllUrlsPattern});
constexpr bool kHasFileAccess = false;
std::string error;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(permissions_object, required_permissions,
PermissionSet(), kHasFileAccess, &error);
ASSERT_TRUE(unpack_result) << error;
EXPECT_THAT(GetPatternsAsStrings(unpack_result->required_explicit_hosts),
testing::ElementsAre(URLPattern::kAllUrlsPattern));
EXPECT_FALSE(
unpack_result->required_explicit_hosts.begin()->valid_schemes() &
URLPattern::SCHEME_FILE);
EXPECT_THAT(
GetPatternsAsStrings(unpack_result->restricted_file_scheme_patterns),
testing::IsEmpty());
}
// With file access, <all_urls> should be parsed and include file:-scheme
// access.
{
URLPattern all_urls(Extension::kValidHostPermissionSchemes,
URLPattern::kAllUrlsPattern);
PermissionSet required_permissions(
APIPermissionSet(), ManifestPermissionSet(), URLPatternSet({all_urls}),
URLPatternSet());
Permissions permissions_object;
permissions_object.origins =
std::vector<std::string>({URLPattern::kAllUrlsPattern});
std::string error;
constexpr bool kHasFileAccess = true;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(permissions_object, required_permissions,
PermissionSet(), kHasFileAccess, &error);
ASSERT_TRUE(unpack_result) << error;
EXPECT_THAT(GetPatternsAsStrings(unpack_result->required_explicit_hosts),
testing::ElementsAre(URLPattern::kAllUrlsPattern));
EXPECT_TRUE(
unpack_result->required_explicit_hosts.begin()->valid_schemes() &
URLPattern::SCHEME_FILE);
EXPECT_THAT(
GetPatternsAsStrings(unpack_result->restricted_file_scheme_patterns),
testing::IsEmpty());
}
}
// Tests that unpacking a pattern that explicitly specifies the file:-scheme is
// properly placed into the |restricted_file_scheme_patterns| set.
TEST(PermissionsApiHelpersTest, Unpack_FileSchemes_Specific) {
constexpr char kFilePattern[] = "file:///*";
// Without file access, the file:-scheme pattern should be populated into
// |restricted_file_scheme_patterns|.
{
URLPattern file_pattern(Extension::kValidHostPermissionSchemes,
kFilePattern);
PermissionSet required_permissions(
APIPermissionSet(), ManifestPermissionSet(),
URLPatternSet({file_pattern}), URLPatternSet());
Permissions permissions_object;
permissions_object.origins = std::vector<std::string>({kFilePattern});
std::string error;
constexpr bool kHasFileAccess = false;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(permissions_object, required_permissions,
PermissionSet(), kHasFileAccess, &error);
ASSERT_TRUE(unpack_result) << error;
EXPECT_THAT(GetPatternsAsStrings(unpack_result->required_explicit_hosts),
testing::IsEmpty());
EXPECT_THAT(
GetPatternsAsStrings(unpack_result->restricted_file_scheme_patterns),
testing::ElementsAre(kFilePattern));
EXPECT_TRUE(unpack_result->restricted_file_scheme_patterns.begin()
->valid_schemes() &
URLPattern::SCHEME_FILE);
}
// With file access, the file:-scheme pattern should be populated into
// |required_explicit_hosts| (since it's not restricted).
{
URLPattern file_pattern(Extension::kValidHostPermissionSchemes,
kFilePattern);
PermissionSet required_permissions(
APIPermissionSet(), ManifestPermissionSet(),
URLPatternSet({file_pattern}), URLPatternSet());
Permissions permissions_object;
permissions_object.origins = std::vector<std::string>({kFilePattern});
std::string error;
constexpr bool kHasFileAccess = true;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(permissions_object, required_permissions,
PermissionSet(), kHasFileAccess, &error);
ASSERT_TRUE(unpack_result) << error;
EXPECT_THAT(GetPatternsAsStrings(unpack_result->required_explicit_hosts),
testing::ElementsAre(kFilePattern));
EXPECT_TRUE(
unpack_result->required_explicit_hosts.begin()->valid_schemes() &
URLPattern::SCHEME_FILE);
EXPECT_THAT(
GetPatternsAsStrings(unpack_result->restricted_file_scheme_patterns),
testing::IsEmpty());
}
}
// Tests that unpacking a UsbDevicePermission with a list of USB device IDs
// preserves the device list in the result object.
TEST(PermissionsApiHelpersTest, Unpack_UsbDevicePermission) {
constexpr char kDeviceListJson[] = R"([{"productId":2,"vendorId":1}])";
constexpr char kUsbDevicesPermissionJson[] =
R"(usbDevices|[{"productId":2,"vendorId":1}])";
auto device_list = base::JSONReader::Read(
kDeviceListJson, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
ASSERT_TRUE(device_list) << "Failed to parse device list JSON.";
auto usb_device_permission = std::make_unique<UsbDevicePermission>(
PermissionsInfo::GetInstance()->GetByID(
mojom::APIPermissionID::kUsbDevice));
std::string error;
std::vector<std::string> unhandled_permissions;
bool from_value_result = usb_device_permission->FromValue(
&device_list.value(), &error, &unhandled_permissions);
ASSERT_TRUE(from_value_result);
EXPECT_TRUE(unhandled_permissions.empty());
APIPermissionSet api_permission_set;
api_permission_set.insert(usb_device_permission->Clone());
PermissionSet optional_permissions(std::move(api_permission_set),
ManifestPermissionSet(), URLPatternSet(),
URLPatternSet());
Permissions permissions_object;
permissions_object.permissions =
std::vector<std::string>({kUsbDevicesPermissionJson});
constexpr bool kHasFileAccess = false;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
UnpackPermissionSet(permissions_object, PermissionSet(),
optional_permissions, kHasFileAccess, &error);
ASSERT_TRUE(unpack_result) << error;
ASSERT_EQ(1U, unpack_result->optional_apis.size());
EXPECT_EQ(mojom::APIPermissionID::kUsbDevice,
unpack_result->optional_apis.begin()->id());
EXPECT_TRUE(unpack_result->optional_apis.begin()->Contains(
usb_device_permission.get()));
}
} // namespace extensions