blob: 8258ea6ff67d8c7ad87137a7fcce7f0aa01f689c [file] [log] [blame]
// Copyright 2016 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.h"
#include <memory>
#include <optional>
#include <string>
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/extension_service_test_with_install.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/permissions/active_tab_permission_granter.h"
#include "chrome/browser/extensions/permissions/permissions_test_util.h"
#include "chrome/browser/extensions/permissions/permissions_updater.h"
#include "chrome/browser/extensions/permissions/scripting_permissions_modifier.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/manifest_handlers/permissions_parser.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/test/test_extension_dir.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/test_browser_window.h"
#endif
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
namespace extensions {
namespace {
constexpr char kNotInManifestError[] =
"Only permissions specified in the manifest may be requested.";
using permissions_test_util::GetPatternsAsStrings;
scoped_refptr<const Extension> CreateExtensionWithPermissions(
base::Value::List permissions,
const std::string& name,
bool allow_file_access) {
int creation_flags = Extension::NO_FLAGS;
if (allow_file_access)
creation_flags |= Extension::ALLOW_FILE_ACCESS;
return ExtensionBuilder()
.SetLocation(mojom::ManifestLocation::kInternal)
.SetManifest(base::Value::Dict()
.Set("name", name)
.Set("description", "foo")
.Set("manifest_version", 2)
.Set("version", "0.1.2.3")
.Set("permissions", std::move(permissions)))
.AddFlags(creation_flags)
.SetID(crx_file::id_util::GenerateId(name))
.Build();
}
// Runs permissions.request() with the provided |args|, and returns the result
// of the API call. Expects the function to succeed.
// Populates |did_prompt_user| with whether the user would be prompted for the
// new permissions.
bool RunRequestFunction(
const Extension& extension,
content::BrowserContext* browser_context,
const char* args,
std::unique_ptr<const PermissionSet>* prompted_permissions_out) {
auto function = base::MakeRefCounted<PermissionsRequestFunction>();
function->set_user_gesture(true);
function->set_extension(&extension);
std::optional<base::Value> result =
api_test_utils::RunFunctionAndReturnSingleResult(
function.get(), args, browser_context,
api_test_utils::FunctionMode::kNone);
if (!function->GetError().empty()) {
ADD_FAILURE() << "Unexpected function error: " << function->GetError();
return false;
}
if (!result || !result->is_bool()) {
ADD_FAILURE() << "Unexpected function result.";
return false;
}
*prompted_permissions_out = function->TakePromptedPermissionsForTesting();
return result->GetBool();
}
} // namespace
class PermissionsAPIUnitTest : public ExtensionServiceTestWithInstall {
public:
PermissionsAPIUnitTest() = default;
PermissionsAPIUnitTest(const PermissionsAPIUnitTest&) = delete;
PermissionsAPIUnitTest& operator=(const PermissionsAPIUnitTest&) = delete;
~PermissionsAPIUnitTest() override = default;
// Runs chrome.permissions.contains(|json_query|).
bool RunContainsFunction(const std::string& manifest_permission,
const std::string& args_string,
bool allow_file_access) {
SCOPED_TRACE(args_string);
scoped_refptr<const Extension> extension = CreateExtensionWithPermissions(
base::Value::List().Append(manifest_permission), "My Extension",
allow_file_access);
ExtensionPrefs::Get(profile())->SetAllowFileAccess(extension->id(),
allow_file_access);
scoped_refptr<PermissionsContainsFunction> function(
new PermissionsContainsFunction());
function->set_extension(extension.get());
bool run_result =
api_test_utils::RunFunction(function.get(), args_string, profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_TRUE(run_result) << function->GetError();
const auto& args_list = *function->GetResultListForTest();
if (args_list.empty()) {
ADD_FAILURE() << "Result unexpectedly empty.";
return false;
}
if (!args_list[0].is_bool()) {
ADD_FAILURE() << "Result is not a boolean.";
return false;
}
return args_list[0].GetBool();
}
// Adds the extension to the ExtensionService, and grants any initial
// permissions.
void AddExtensionAndGrantPermissions(const Extension& extension) {
PermissionsUpdater updater(profile());
updater.InitializePermissions(&extension);
updater.GrantActivePermissions(&extension);
registrar()->AddExtension(&extension);
}
// Adds the extension to the ExtensionService, and withheld any initial
// permissions.
void AddExtensionAndWithheldPermissions(const Extension& extension) {
PermissionsUpdater updater(profile());
updater.InitializePermissions(&extension);
ScriptingPermissionsModifier(profile(), &extension)
.SetWithholdHostPermissions(true);
registrar()->AddExtension(&extension);
}
protected:
// ExtensionServiceTestBase:
void SetUp() override {
ExtensionServiceTestWithInstall::SetUp();
dialog_action_ = PermissionsRequestFunction::SetDialogActionForTests(
PermissionsRequestFunction::DialogAction::kAutoConfirm);
InitializeEmptyExtensionService();
}
// ExtensionServiceTestBase:
void TearDown() override {
dialog_action_.reset();
ExtensionServiceTestWithInstall::TearDown();
}
private:
std::optional<base::AutoReset<PermissionsRequestFunction::DialogAction>>
dialog_action_;
};
TEST_F(PermissionsAPIUnitTest, Contains) {
// 1. Since the extension does not have file:// origin access, expect it
// to return false;
bool expected_has_permission = false;
bool has_permission = RunContainsFunction(
"tabs", "[{\"origins\":[\"file://*\"]}]", false /* allow_file_access */);
EXPECT_EQ(expected_has_permission, has_permission);
// 2. Extension has tabs permission, expect to return true.
expected_has_permission = true;
has_permission = RunContainsFunction("tabs", "[{\"permissions\":[\"tabs\"]}]",
false /* allow_file_access */);
EXPECT_EQ(expected_has_permission, has_permission);
// 3. Extension has file permission, but not active. Expect to return false.
expected_has_permission = false;
has_permission =
RunContainsFunction("file://*", "[{\"origins\":[\"file://*\"]}]",
false /* allow_file_access */);
EXPECT_EQ(expected_has_permission, has_permission);
// 4. Same as #3, but this time with file access allowed.
expected_has_permission = true;
has_permission =
RunContainsFunction("file:///*", "[{\"origins\":[\"file:///*\"]}]",
true /* allow_file_access */);
EXPECT_EQ(expected_has_permission, has_permission);
// Tests calling contains() with <all_urls> with and without file access.
// Regression test for https://crbug.com/931816.
EXPECT_TRUE(RunContainsFunction("<all_urls>",
R"([{"origins": ["<all_urls>"]}])",
false /* allow file access */));
EXPECT_TRUE(RunContainsFunction("<all_urls>",
R"([{"origins": ["<all_urls>"]}])",
true /* allow file access */));
}
TEST_F(PermissionsAPIUnitTest, ContainsAndGetAllWithRuntimeHostPermissions) {
constexpr char kExampleCom[] = "https://example.com/*";
constexpr char kContentScriptCom[] = "https://contentscript.com/*";
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddHostPermission(kExampleCom)
.AddContentScript("foo.js", {kContentScriptCom, kExampleCom})
.Build();
AddExtensionAndGrantPermissions(*extension);
PermissionsUpdater updater(profile());
updater.InitializePermissions(extension.get());
updater.GrantActivePermissions(extension.get());
registrar()->AddExtension(extension.get());
auto contains_origin = [this, &extension](const char* origin) {
SCOPED_TRACE(origin);
auto function = base::MakeRefCounted<PermissionsContainsFunction>();
function->set_extension(extension.get());
if (!api_test_utils::RunFunction(
function.get(),
base::StringPrintf(R"([{"origins": ["%s"]}])", origin), profile(),
api_test_utils::FunctionMode::kNone)) {
ADD_FAILURE() << "Running function failed: " << function->GetError();
}
return (*function->GetResultListForTest())[0].GetBool();
};
auto get_all = [this, &extension]() {
auto function = base::MakeRefCounted<PermissionsGetAllFunction>();
function->set_extension(extension.get());
std::vector<std::string> origins;
if (!api_test_utils::RunFunction(function.get(), "[]", profile(),
api_test_utils::FunctionMode::kNone)) {
ADD_FAILURE() << "Running function failed: " << function->GetError();
return origins;
}
const base::Value::List* results = function->GetResultListForTest();
if (results->size() != 1u || !(*results)[0].is_dict()) {
ADD_FAILURE() << "Invalid result value";
return origins;
}
const base::Value::List* origins_value =
(*results)[0].GetDict().FindList("origins");
for (const auto& value : *origins_value) {
origins.push_back(value.GetString());
}
return origins;
};
// Currently, the extension should have access to example.com and
// contentscript.com (since permissions are not withheld).
EXPECT_TRUE(contains_origin(kExampleCom));
EXPECT_TRUE(contains_origin(kContentScriptCom));
EXPECT_THAT(get_all(),
testing::UnorderedElementsAre(kExampleCom, kContentScriptCom));
ScriptingPermissionsModifier modifier(profile(), extension);
modifier.SetWithholdHostPermissions(true);
// Once we withhold the permission, the contains function should correctly
// report the value.
EXPECT_FALSE(contains_origin(kExampleCom));
EXPECT_FALSE(contains_origin(kContentScriptCom));
EXPECT_THAT(get_all(), testing::IsEmpty());
constexpr char kChromiumOrg[] = "https://chromium.org/";
modifier.GrantHostPermission(GURL(kChromiumOrg));
// The permissions API only reports active permissions, rather than granted
// permissions. This means it will not report values for permissions that
// aren't requested. This is probably good, because the extension wouldn't be
// able to use them anyway (since they aren't active).
EXPECT_FALSE(contains_origin(kChromiumOrg));
EXPECT_THAT(get_all(), testing::IsEmpty());
// Fun edge case: example.com is requested as both a scriptable and an
// explicit host. It is technically possible that it may be granted *only* as
// one of the two (e.g., only explicit granted).
{
URLPatternSet explicit_hosts(
{URLPattern(Extension::kValidHostPermissionSchemes, kExampleCom)});
permissions_test_util::GrantRuntimePermissionsAndWaitForCompletion(
profile(), *extension,
PermissionSet(APIPermissionSet(), ManifestPermissionSet(),
std::move(explicit_hosts), URLPatternSet()));
const GURL example_url("https://example.com");
const PermissionSet& active_permissions =
extension->permissions_data()->active_permissions();
EXPECT_TRUE(active_permissions.explicit_hosts().MatchesURL(example_url));
EXPECT_FALSE(active_permissions.scriptable_hosts().MatchesURL(example_url));
}
// In this case, contains() should return *false* (because not all the
// permissions are active, but getAll() should include example.com (because it
// has been [partially] granted). In practice, this case should be
// exceptionally rare, and we're mostly just making sure that there's some
// sane behavior.
EXPECT_FALSE(contains_origin(kExampleCom));
EXPECT_THAT(get_all(), testing::ElementsAre(kExampleCom));
}
// Tests requesting permissions that are already granted with the
// permissions.request() API.
TEST_F(PermissionsAPIUnitTest, RequestingGrantedPermissions) {
// Create an extension with requires all urls, and grant the permission.
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension").AddHostPermission("<all_urls>").Build();
AddExtensionAndGrantPermissions(*extension);
// Request access to any host permissions. No permissions should be prompted,
// since permissions that are already granted are not taken into account.
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_TRUE(RunRequestFunction(*extension, profile(),
R"([{"origins": ["https://*/*"]}])",
&prompted_permissions));
EXPECT_EQ(prompted_permissions, nullptr);
}
// Tests requesting withheld permissions with the permissions.request() API.
TEST_F(PermissionsAPIUnitTest, RequestingWithheldPermissions) {
// Create an extension with required host permissions, and withhold those
// permissions.
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddHostPermissions({"https://example.com/*", "https://google.com/*"})
.Build();
AddExtensionAndGrantPermissions(*extension);
ScriptingPermissionsModifier(profile(), extension)
.SetWithholdHostPermissions(true);
const GURL kExampleCom("https://example.com");
const GURL kGoogleCom("https://google.com");
const PermissionsData* permissions_data = extension->permissions_data();
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().is_empty());
// Request one of the withheld permissions.
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_TRUE(RunRequestFunction(*extension, profile(),
R"([{"origins": ["https://example.com/*"]}])",
&prompted_permissions));
ASSERT_TRUE(prompted_permissions);
EXPECT_THAT(GetPatternsAsStrings(prompted_permissions->effective_hosts()),
testing::UnorderedElementsAre("https://example.com/*"));
// The withheld permission should be granted.
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kExampleCom));
EXPECT_FALSE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kGoogleCom));
}
// Tests requesting withheld content script permissions with the
// permissions.request() API.
TEST_F(PermissionsAPIUnitTest, RequestingWithheldContentScriptPermissions) {
constexpr char kContentScriptPattern[] = "https://contentscript.com/*";
// Create an extension with required host permissions, and withhold those
// permissions.
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddContentScript("foo.js", {kContentScriptPattern})
.Build();
AddExtensionAndGrantPermissions(*extension);
ScriptingPermissionsModifier(profile(), extension)
.SetWithholdHostPermissions(true);
const GURL kContentScriptCom("https://contentscript.com");
const PermissionsData* permissions_data = extension->permissions_data();
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().is_empty());
// Request one of the withheld permissions.
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_TRUE(
RunRequestFunction(*extension, profile(),
R"([{"origins": ["https://contentscript.com/*"]}])",
&prompted_permissions));
ASSERT_TRUE(prompted_permissions);
EXPECT_THAT(GetPatternsAsStrings(prompted_permissions->effective_hosts()),
testing::UnorderedElementsAre(kContentScriptPattern));
// The withheld permission should be granted.
EXPECT_THAT(GetPatternsAsStrings(
permissions_data->active_permissions().effective_hosts()),
testing::UnorderedElementsAre(kContentScriptPattern));
EXPECT_TRUE(
permissions_data->withheld_permissions().effective_hosts().is_empty());
}
// Tests requesting a withheld host permission that is both an explicit and a
// scriptable host with the permissions.request() API.
TEST_F(PermissionsAPIUnitTest,
RequestingWithheldExplicitAndScriptablePermissionsInTheSameCall) {
constexpr char kContentScriptPattern[] = "https://example.com/*";
// Create an extension with required host permissions, and withhold those
// permissions.
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddHostPermission("https://example.com/*")
.AddContentScript("foo.js", {kContentScriptPattern})
.Build();
AddExtensionAndGrantPermissions(*extension);
ScriptingPermissionsModifier(profile(), extension)
.SetWithholdHostPermissions(true);
const GURL kExampleCom("https://example.com");
const PermissionsData* permissions_data = extension->permissions_data();
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().is_empty());
// Request one of the withheld permissions.
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_TRUE(RunRequestFunction(*extension, profile(),
R"([{"origins": ["https://example.com/*"]}])",
&prompted_permissions));
ASSERT_TRUE(prompted_permissions);
EXPECT_THAT(GetPatternsAsStrings(prompted_permissions->effective_hosts()),
testing::UnorderedElementsAre(kContentScriptPattern));
// The withheld permission should be granted to both explicit and scriptable
// hosts.
EXPECT_TRUE(
permissions_data->active_permissions().explicit_hosts().MatchesURL(
kExampleCom));
EXPECT_TRUE(
permissions_data->active_permissions().scriptable_hosts().MatchesURL(
kExampleCom));
}
// Tests an extension re-requesting an optional host after the user removes it.
TEST_F(PermissionsAPIUnitTest, ReRequestingWithheldOptionalPermissions) {
// Create an extension an optional host permissions, and withhold those
// permissions.
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddOptionalHostPermission("https://chromium.org/*")
.Build();
AddExtensionAndGrantPermissions(*extension);
const GURL kChromiumOrg("https://chromium.org");
const PermissionsData* permissions_data = extension->permissions_data();
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().is_empty());
{
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_TRUE(RunRequestFunction(
*extension, profile(), R"([{"origins": ["https://chromium.org/*"]}])",
&prompted_permissions));
ASSERT_TRUE(prompted_permissions);
EXPECT_THAT(GetPatternsAsStrings(prompted_permissions->effective_hosts()),
testing::UnorderedElementsAre("https://chromium.org/*"));
}
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kChromiumOrg));
{
URLPattern chromium_org_pattern(Extension::kValidHostPermissionSchemes,
"https://chromium.org/*");
PermissionSet permissions(APIPermissionSet(), ManifestPermissionSet(),
URLPatternSet({chromium_org_pattern}),
URLPatternSet());
permissions_test_util::RevokeRuntimePermissionsAndWaitForCompletion(
profile(), *extension, permissions);
}
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().is_empty());
auto dialog_action_reset =
PermissionsRequestFunction::SetDialogActionForTests(
PermissionsRequestFunction::DialogAction::kAutoReject);
{
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_FALSE(RunRequestFunction(
*extension, profile(), R"([{"origins": ["https://chromium.org/*"]}])",
&prompted_permissions));
ASSERT_TRUE(prompted_permissions);
EXPECT_THAT(GetPatternsAsStrings(prompted_permissions->effective_hosts()),
testing::UnorderedElementsAre("https://chromium.org/*"));
}
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().is_empty());
}
// Tests requesting both optional and withheld permissions in the same call to
// permissions.request().
TEST_F(PermissionsAPIUnitTest, RequestingWithheldAndOptionalPermissions) {
// Create an extension with required and optional host permissions, and
// withhold the required permissions.
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddHostPermissions({"https://example.com/*", "https://google.com/*"})
.AddOptionalHostPermission("https://chromium.org/*")
.Build();
AddExtensionAndGrantPermissions(*extension);
ScriptingPermissionsModifier(profile(), extension)
.SetWithholdHostPermissions(true);
const GURL kExampleCom("https://example.com");
const GURL kGoogleCom("https://google.com");
const GURL kChromiumOrg("https://chromium.org");
const PermissionsData* permissions_data = extension->permissions_data();
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().is_empty());
// Request one of the withheld host permissions and an optional host
// permission in the same call.
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_TRUE(RunRequestFunction(
*extension, profile(),
R"([{"origins": ["https://example.com/*", "https://chromium.org/*"]}])",
&prompted_permissions));
ASSERT_TRUE(prompted_permissions);
EXPECT_THAT(GetPatternsAsStrings(prompted_permissions->effective_hosts()),
testing::UnorderedElementsAre("https://chromium.org/*",
"https://example.com/*"));
// The requested permissions should be added.
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kExampleCom));
EXPECT_FALSE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kGoogleCom));
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kChromiumOrg));
}
// Tests requesting permissions that weren't specified in the manifest (either
// in optional permissions or in required permissions).
TEST_F(PermissionsAPIUnitTest, RequestingPermissionsNotSpecifiedInManifest) {
// Create an extension with required and optional host permissions, and
// withhold the required permissions.
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddHostPermission("https://example.com/*")
.AddOptionalHostPermission("https://chromium.org/*")
.Build();
AddExtensionAndGrantPermissions(*extension);
ScriptingPermissionsModifier(profile(), extension)
.SetWithholdHostPermissions(true);
const GURL kExampleCom("https://example.com");
const GURL kGoogleCom("https://google.com");
const GURL kChromiumOrg("https://chromium.org");
// Request permission for an optional and required permission, as well as a
// permission that wasn't specified in the manifest. The call should fail.
// Note: Not using RunRequestFunction(), since that expects function success.
auto function = base::MakeRefCounted<PermissionsRequestFunction>();
function->set_user_gesture(true);
function->set_extension(extension.get());
EXPECT_EQ(kNotInManifestError,
api_test_utils::RunFunctionAndReturnError(
function.get(),
R"([{
"origins": [
"https://example.com/*",
"https://chromium.org/*",
"https://google.com/*"
]
}])",
profile(), api_test_utils::FunctionMode::kNone));
}
// Tests requesting withheld permissions that have already been granted.
TEST_F(PermissionsAPIUnitTest, RequestingAlreadyGrantedWithheldPermissions) {
// Create an extension with required host permissions, withhold host
// permissions, and then grant one of the hosts.
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddHostPermissions({"https://example.com/*", "https://google.com/*"})
.Build();
AddExtensionAndGrantPermissions(*extension);
ScriptingPermissionsModifier modifier(profile(), extension);
modifier.SetWithholdHostPermissions(true);
const GURL kExampleCom("https://example.com");
const GURL kGoogleCom("https://google.com");
modifier.GrantHostPermission(kExampleCom);
const PermissionsData* permissions_data = extension->permissions_data();
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kExampleCom));
EXPECT_FALSE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kGoogleCom));
// Request the already-granted host permission. The function should succeed
// (without even prompting the user), and the permission should (still) be
// granted.
auto dialog_action_reset =
PermissionsRequestFunction::SetDialogActionForTests(
PermissionsRequestFunction::DialogAction::kAutoReject);
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_TRUE(RunRequestFunction(*extension, profile(),
R"([{"origins": ["https://example.com/*"]}])",
&prompted_permissions));
ASSERT_FALSE(prompted_permissions);
// The withheld permission should be granted.
EXPECT_TRUE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kExampleCom));
EXPECT_FALSE(
permissions_data->active_permissions().effective_hosts().MatchesURL(
kGoogleCom));
}
// Test that requesting chrome:-scheme URLs is disallowed in the permissions
// API.
TEST_F(PermissionsAPIUnitTest, RequestingChromeURLs) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddOptionalHostPermission("<all_urls>")
.Build();
AddExtensionAndGrantPermissions(*extension);
const GURL chrome_url("chrome://settings");
// By default, the extension should not have access to chrome://settings.
EXPECT_FALSE(extension->permissions_data()->HasHostPermission(chrome_url));
// The optional permissions should also omit the chrome:-scheme for the
// <all_urls> pattern.
EXPECT_FALSE(PermissionsParser::GetOptionalPermissions(extension.get())
.explicit_hosts()
.MatchesURL(chrome_url));
{
// Trying to request "chrome://settings/*" should fail, since it's not in
// the optional permissions.
auto function = base::MakeRefCounted<PermissionsRequestFunction>();
function->set_user_gesture(true);
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), R"([{"origins": ["chrome://settings/*"]}])", profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_EQ(kNotInManifestError, error);
}
// chrome://settings should still be restricted.
EXPECT_FALSE(extension->permissions_data()->HasHostPermission(chrome_url));
// The extension can request <all_urls>, but it should not grant access to the
// chrome:-scheme.
std::unique_ptr<const PermissionSet> prompted_permissions;
RunRequestFunction(*extension, profile(), R"([{"origins": ["<all_urls>"]}])",
&prompted_permissions);
EXPECT_THAT(GetPatternsAsStrings(prompted_permissions->effective_hosts()),
testing::UnorderedElementsAre("<all_urls>"));
EXPECT_FALSE(extension->permissions_data()->HasHostPermission(chrome_url));
EXPECT_TRUE(extension->permissions_data()->HasHostPermission(
GURL("https://example.com")));
}
// Tests requesting the a file:-scheme pattern with and without file
// access granted. Regression test for https://crbug.com/932703.
TEST_F(PermissionsAPIUnitTest, RequestingFilePermissions) {
// We need a "real" extension here, since toggling file access requires
// reloading the extension to re-initialize permissions.
TestExtensionDir test_dir;
test_dir.WriteManifest(
R"({
"name": "Extension",
"manifest_version": 2,
"version": "0.1",
"optional_permissions": ["file:///*"]
})");
ChromeTestExtensionLoader loader(profile());
loader.set_allow_file_access(false);
scoped_refptr<const Extension> extension =
loader.LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
EXPECT_FALSE(util::AllowFileAccess(extension->id(), profile()));
const GURL file_url("file:///foo");
EXPECT_FALSE(extension->permissions_data()->HasHostPermission(file_url));
{
auto function = base::MakeRefCounted<PermissionsRequestFunction>();
function->set_user_gesture(true);
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), R"([{"origins": ["file:///*"]}])", profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_EQ("Extension must have file access enabled to request 'file:///*'.",
error);
EXPECT_FALSE(extension->permissions_data()->HasHostPermission(file_url));
}
{
TestExtensionRegistryObserver observer(registry(), extension->id());
// This will reload the extension, so we need to reset the extension
// pointer.
util::SetAllowFileAccess(extension->id(), profile(), true);
extension = observer.WaitForExtensionLoaded();
ASSERT_TRUE(extension);
}
std::unique_ptr<const PermissionSet> prompted_permissions;
EXPECT_TRUE(RunRequestFunction(*extension, profile(),
R"([{"origins": ["file:///*"]}])",
&prompted_permissions));
// Note: There are no permission warnings associated with requesting file
// URLs (probably because there's a separate toggle to control it already);
// they are filtered out of the permission ID set when we get permission
// messages.
EXPECT_FALSE(prompted_permissions);
EXPECT_TRUE(extension->permissions_data()->HasHostPermission(file_url));
}
// TODO(crbug.com/419057482): Once we have a cross-platform interface for
// browser windows, port this to desktop Android.
#if BUILDFLAG(ENABLE_EXTENSIONS)
class PermissionsAPIHostAccessRequestsUnitTest : public PermissionsAPIUnitTest {
public:
PermissionsAPIHostAccessRequestsUnitTest() {
scoped_feature_list_.InitAndEnableFeature(
extensions_features::kApiPermissionsHostAccessRequests);
}
~PermissionsAPIHostAccessRequestsUnitTest() override = default;
PermissionsAPIHostAccessRequestsUnitTest(
const PermissionsAPIHostAccessRequestsUnitTest&) = delete;
PermissionsAPIHostAccessRequestsUnitTest& operator=(
const PermissionsAPIHostAccessRequestsUnitTest&) = delete;
// Navigate to `url` in the current web contents.
void NavigateTo(const std::string& url) {
web_contents_tester_->NavigateAndCommit(GURL(url));
}
// Returns the function params for permissions.add|removeHostAccessRequest for
// a tab.
std::string GetFunctionParams(int tab_id,
const std::string& pattern = std::string()) {
if (pattern.empty()) {
return base::StringPrintf(R"([{"tabId": %s}])",
base::NumberToString(tab_id).c_str());
}
return base::StringPrintf(R"([{"tabId": %s, "pattern": "%s"}])",
base::NumberToString(tab_id).c_str(),
pattern.c_str());
}
protected:
// PermissionsAPIUnitTest:
void SetUp() override {
PermissionsAPIUnitTest::SetUp();
auto browser_window = std::make_unique<TestBrowserWindow>();
Browser::CreateParams params(profile(), true);
params.type = Browser::TYPE_NORMAL;
params.window = browser_window.release();
browser_ = Browser::DeprecatedCreateOwnedForTesting(params);
std::unique_ptr<content::WebContents> web_contents =
content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
content::WebContents* raw_web_contents = web_contents.get();
browser()->tab_strip_model()->AppendWebContents(std::move(web_contents),
true);
web_contents_tester_ = content::WebContentsTester::For(raw_web_contents);
}
void TearDown() override {
// Detach the web contents.
web_contents_tester_ = nullptr;
browser()->tab_strip_model()->DetachAndDeleteWebContentsAt(/*index=*/0);
browser_.reset();
PermissionsAPIUnitTest::TearDown();
}
Browser* browser() { return browser_.get(); }
private:
std::unique_ptr<Browser> browser_;
base::test::ScopedFeatureList scoped_feature_list_;
raw_ptr<content::WebContentsTester> web_contents_tester_;
};
// Test extension can add a host access request for a site it has host
// permissions for and has withheld host access.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_RequestedSite) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.AddHostPermission("*://*.requested.com/*")
.Build();
AddExtensionAndGrantPermissions(*extension);
// Open tab on a url requested by the extension.
NavigateTo("http://www.requested.com");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
auto* permissions_manager = PermissionsManager::Get(profile());
// Add host access request when extension has granted host access.
{
// Function should fail since extension already has host access.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), GetFunctionParams(tab_id), profile());
EXPECT_EQ(
"Extension cannot add a host access request for a host it already has "
"access to.",
error);
// Verify host access request was not added.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Add host access request when extension has withheld host access.
ScriptingPermissionsModifier(profile(), extension)
.SetWithholdHostPermissions(true);
{
// Function should succeed since extension can be granted access.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request is active.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
}
// Test extension can add a host access request with a pattern for a host it has
// host permissions for and has withheld host access. Request is only valid if
// pattern matches the extension's host permissions.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequestWithPattern_RequestedSite) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.AddHostPermission("*://*.requested.com/*")
.Build();
AddExtensionAndWithheldPermissions(*extension);
// Open tab on a url requested by the extension.
NavigateTo("http://www.requested.com");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
auto* permissions_manager = PermissionsManager::Get(profile());
// Add host access request for a pattern that does not match the extension's
// host permissions.
{
// Function should fail since pattern doesn't match the extension's host
// permissions.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(),
GetFunctionParams(tab_id, "*://www.not-requested.com/*"), profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_EQ(
"Extension cannot add a host access request with a pattern that does "
"match any of its host permissions.",
error);
// Verify host access request was not added.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Add host access request for a pattern that matches the extension's host
// permissions and the current host.
{
// Function should succeed since pattern matches the extension's host
// permissions.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), GetFunctionParams(tab_id, "*://www.requested.com/*"),
profile(), api_test_utils::FunctionMode::kNone));
// Verify host access request was not added.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Add host access request for a pattern that matches the extension's host
// permissions and does not match the current host but will match on a
// cross-origin navigation.
{
// Function should succeed since extension can be granted access.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), GetFunctionParams(tab_id, "*://*/path"), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request was not added. Note that new requests will
// overridden any existent ones.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
// Verify host access request was added when navigating to the same-origin
// url that matches the pattern.
NavigateTo("http://www.requested.com/path");
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
}
// Test extension can add a host access request for a host it doesn't have host
// permissions for, but request is not active.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_NonRequestedSite) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.AddHostPermission("*://*.requested.com/*")
.AddAPIPermission("activeTab")
.Build();
AddExtensionAndGrantPermissions(*extension);
// Open tab on a url not requested by the extension.
NavigateTo("http://www.not-requested.com");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
auto* permissions_manager = PermissionsManager::Get(profile());
// Add host access request.
{
// Function should succeed since we don't want to reveal information
// about the current host to the extension, but request is not added.
// Even though extension could have access via activeTab, extension can only
// add access requests for hosts it has previously requested host
// permissions for.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request was not added.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Add host access request with a pattern that matches the current host but
// doesn't match the extension's host permissions.
{
// Function should fail since pattern doesn't match the extension's host
// permissions.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(),
GetFunctionParams(tab_id, "*://www.not-requested.com/*"), profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_EQ(
"Extension cannot add a host access request with a pattern that does "
"match any of its host permissions.",
error);
// Verify host access request was not added.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
}
// Test extension cannot add a host access request when it doesn't have any
// host permissions.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_NoHostPermissions) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension").AddAPIPermission("activeTab").Build();
registrar()->AddExtension(extension.get());
// Open tab on any url.
NavigateTo("http://www.example.com");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
auto* permissions_manager = PermissionsManager::Get(profile());
// Add host access request. Function should fail since extension doesn't have
// any host permissions.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), GetFunctionParams(tab_id), profile());
EXPECT_EQ(
"Extension cannot add a host access request when it does not have any "
"host permissions.",
error);
// Verify host access request was not added.
EXPECT_FALSE(
permissions_manager->HasActiveHostAccessRequest(tab_id, extension->id()));
}
// Test extension can add a host access request for a restricted host, but
// request is not active.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_TabId_RestrictedSite) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.AddHostPermission("*://*.requested.com/*")
.Build();
AddExtensionAndGrantPermissions(*extension);
// Open tab on a url not requested by the extension.
NavigateTo("chrome://extensions");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
auto* permissions_manager = PermissionsManager::Get(profile());
// Add host access request.
{
// Function should succeed since we don't want to reveal information
// about the current host to the extension, but request is not added.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request was not added.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// TODO(crbug.com/330588494): Add tests with `pattern` once parameter is
// added.
}
// Tests extension can add a host access request for a host where it has
// optional host permissions.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_OptionalHostPermissions) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.SetManifestKey("optional_host_permissions",
base::Value::List().Append("*://*.optional.com/*"))
.Build();
registrar()->AddExtension(extension.get());
auto* permissions_manager = PermissionsManager::Get(profile());
// Navigate to url requested by the extension via optional host permissions.
// Verify there is no host access request.
NavigateTo("http://www.optional.com");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
EXPECT_FALSE(
permissions_manager->HasActiveHostAccessRequest(tab_id, extension->id()));
// Add host access request for tab with optional.com. Function should
// succeed since extension can be granted access.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(function.get(),
GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request was added.
EXPECT_TRUE(
permissions_manager->HasActiveHostAccessRequest(tab_id, extension->id()));
}
// Tests extension can add a host access request for a host where it wants to
// inject a content script.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_ContentScriptMatches) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.AddContentScript("script.js", {"*://*.contentscript.com/*"})
.Build();
AddExtensionAndWithheldPermissions(*extension);
auto* permissions_manager = PermissionsManager::Get(profile());
// Navigate to url requested by the extension via the content script. Verify
// there is no host access request.
NavigateTo("http://www.contentscript.com");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
EXPECT_FALSE(
permissions_manager->HasActiveHostAccessRequest(tab_id, extension->id()));
// Add host access request for tab with contentscript.com. Function should
// succeed since extension can be granted access.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(function.get(),
GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request was added.
EXPECT_TRUE(
permissions_manager->HasActiveHostAccessRequest(tab_id, extension->id()));
}
// Tests extension can add a host access request for a host with access
// withheld, even if the host was blocked by the user. Having a valid request
// doesn't mean it will be signaled to the user.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_UserBlockedSite) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.SetManifestKey("host_permissions",
base::Value::List().Append("*://*.requested.com/*"))
.Build();
AddExtensionAndGrantPermissions(*extension);
// Navigate to url requested by the extension.
NavigateTo("http://www.requested.com");
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int tab_id = ExtensionTabUtil::GetTabId(web_contents);
// Block all extensions on requested.com.
auto* permissions_manager = PermissionsManager::Get(profile());
permissions_manager->UpdateUserSiteSetting(
url::Origin::Create(web_contents->GetLastCommittedURL()),
PermissionsManager::UserSiteSetting::kBlockAllExtensions);
// Add host access request for tab with requested.com. Request is invalid
// because extension has granted host access, even though it can't access the
// host since user blocked access for all extensions.
{
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_EQ(
"Extension cannot add a host access request for a host it already has "
"access to.",
error);
}
// Withheld extension's host access.
ScriptingPermissionsModifier(profile(), extension.get())
.SetWithholdHostPermissions(true);
// Add host access request for tab with requested.com. Request is valid
// because extension wants host access, and host access was withheld. It
// doesn't matter that extensions are blocked on the host, since that is a
// user setting.
{
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
}
// Verify host access request was added.
EXPECT_TRUE(
permissions_manager->HasActiveHostAccessRequest(tab_id, extension->id()));
}
// An extension with granted tab permission (via granting activeTab or running
// an extension set on click) can't add a host request.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_OneTimeGrantedAccess) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.SetManifestKey("host_permissions",
base::Value::List().Append("*://*.requested.com/*"))
.Build();
AddExtensionAndWithheldPermissions(*extension);
// Navigate to url requested by the extension.
NavigateTo("http://www.requested.com");
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int tab_id = ExtensionTabUtil::GetTabId(web_contents);
// Grant one-time host access.
ActiveTabPermissionGranter::FromWebContents(web_contents)
->GrantIfRequested(extension.get());
// Add host access request for requested.com. Request is invalid because
// extension already has host access (even if it's just one-time).
{
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_EQ(
"Extension cannot add a host access request for a host it already has "
"access to.",
error);
}
}
// Test extension can add a host access request for a host it has host
// permissions for and has withheld host access by providing a document id.
// Note: Document id is converted to a tab id by the API after parsing. Thus,
// it's sufficient to test only some bases cases to make sure the documentId is
// properly parsed. Other scenarios are extensively tested using a tab id.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
AddHostAccessRequest_DocumentId) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.AddHostPermission("*://*.requested.com/*")
.Build();
AddExtensionAndGrantPermissions(*extension);
// Open tab on a url requested by the extension.
NavigateTo("http://www.requested.com");
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int tab_id = ExtensionTabUtil::GetTabId(web_contents);
std::string document_id =
ExtensionApiFrameIdMap::GetDocumentId(web_contents->GetPrimaryMainFrame())
.ToString();
auto* permissions_manager = PermissionsManager::Get(profile());
auto function_params = [](const std::string& document_id) {
return base::StringPrintf(R"([{"documentId": "%s"}])", document_id.c_str());
};
// Add host access request when extension has granted host access.
{
// Function should fail since extension already has host access.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), function_params(document_id), profile());
EXPECT_EQ(
"Extension cannot add a host access request for a host it already has "
"access to.",
error);
// Verify host access request was not added.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Add host access request when extension has withheld host access.
ScriptingPermissionsModifier(profile(), extension)
.SetWithholdHostPermissions(true);
{
// Function should succeed since extension can be granted access.
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), function_params(document_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request is active.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
}
// Tests extension cannot remove a host access request that doesn't exist.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
RemoveHostAccessRequest_TabId_Invalid) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.SetManifestKey("host_permissions",
base::Value::List().Append("*://*.requested.com/*"))
.Build();
AddExtensionAndWithheldPermissions(*extension);
// Open tab on a url requested by the extension.
NavigateTo("http://www.requested.com");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
auto* permissions_manager = PermissionsManager::Get(profile());
// Extension cannot remove a request when there is no current request.
{
auto remove_function =
base::MakeRefCounted<PermissionsRemoveHostAccessRequestFunction>();
remove_function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
remove_function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_EQ(
"Extension cannot remove a host access request that doesn't exist.",
error);
// Verify there is no request.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Extension cannot remove a host access request with a pattern when it
// doesn't match the active request (that matches all patterns).
{
// Add a host access request without a pattern. Not specifying a pattern
// means request will be shown for all patterns.
auto add_function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
add_function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
add_function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request was added.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
// Remove a host access request with 'requested.com' pattern. Even though
// existent request matches all patterns, the removal must exactly match
// request. We do this because we don't support "all urls but <x>".
auto remove_function =
base::MakeRefCounted<PermissionsRemoveHostAccessRequestFunction>();
remove_function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
remove_function.get(),
GetFunctionParams(tab_id, /*pattern=*/"*://*.requested.com/*"),
profile(), api_test_utils::FunctionMode::kNone);
EXPECT_EQ(
"Extension cannot remove a host access request that doesn't exist.",
error);
// Verify request wasn't removed.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Extension cannot remove a host access request with a pattern when it
// doesn't match the active request (with a different pattern specified).
{
// Add a host access request with 'requested.com' pattern. Adding a new
// request overrides existent request.
auto add_function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
add_function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
add_function.get(), GetFunctionParams(tab_id, "*://*.requested.com/*"),
profile(), api_test_utils::FunctionMode::kNone));
// Verify host access request was added.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
// Remove a host access request with a 'other.com' pattern. Function is
// invalid because 'other.com' doesn't match with the current request for
// 'requested.com'.
auto remove_function =
base::MakeRefCounted<PermissionsRemoveHostAccessRequestFunction>();
remove_function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
remove_function.get(), GetFunctionParams(tab_id, "*://*.other.com/*"),
profile(), api_test_utils::FunctionMode::kNone);
EXPECT_EQ(
"Extension cannot remove a host access request that doesn't exist.",
error);
// Verify request wasn't removed.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
}
// Tests extension can remove a host access request that matches an existent
// request.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
RemoveHostAccessRequest_TabId_Valid) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.SetManifestKey("host_permissions",
base::Value::List().Append("*://*.requested.com/*"))
.Build();
AddExtensionAndWithheldPermissions(*extension);
// Open tab on a url requested by the extension.
NavigateTo("http://www.requested.com");
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
auto* permissions_manager = PermissionsManager::Get(profile());
// Extension can remove a host access request that matches all patterns (by
// not specifying one) when it matches the active request (that matches all
// patterns).
{
// Add a host access request without a pattern.
auto add_function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
add_function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
add_function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request was added.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
// Remove a host access request without a pattern.
auto remove_function =
base::MakeRefCounted<PermissionsRemoveHostAccessRequestFunction>();
remove_function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
remove_function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify request was removed.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Extension can remove a host access request with a pattern when it matches
// the current request (with the same pattern).
{
// Add a host access request with 'requested.com' pattern.
auto add_function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
add_function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
add_function.get(),
GetFunctionParams(tab_id, /*pattern=*/"*://*.requested.com/*"),
profile(), api_test_utils::FunctionMode::kNone));
// Verify host access request was added.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
// Remove a host access request with 'requested.com' pattern.
auto remove_function =
base::MakeRefCounted<PermissionsRemoveHostAccessRequestFunction>();
remove_function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
remove_function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify request was removed.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Extension can remove a host access request without a pattern when it
// matches the active request (with a pattern).
{
// Add a host access request with 'requested.com' pattern.
auto add_function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
add_function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
add_function.get(),
GetFunctionParams(tab_id, /*pattern=*/"*://*.requested.com/*"),
profile(), api_test_utils::FunctionMode::kNone));
// Verify host access request was added.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
// Remove a host access request without specifying pattern (which matches to
// all patterns). Function is valid because it matches the current request
// ('all patterns' which matches current request on 'requested.com').
auto remove_function =
base::MakeRefCounted<PermissionsRemoveHostAccessRequestFunction>();
remove_function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
remove_function.get(), GetFunctionParams(tab_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify request was removed.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
}
// Tests extension can remove a host access request for a document, if request
// is existent.
// Note: Document id is converted to tab id. Thus, here we only need to test the
// base cases since we have extensive testing for removing requests with tab id.
TEST_F(PermissionsAPIHostAccessRequestsUnitTest,
RemoveHostAccessRequest_DocumentId) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("Extension")
.SetManifestKey("host_permissions",
base::Value::List().Append("*://*.requested.com/*"))
.Build();
AddExtensionAndWithheldPermissions(*extension);
// Open tab on a url requested by the extension.
NavigateTo("http://www.requested.com");
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int tab_id = ExtensionTabUtil::GetTabId(web_contents);
std::string document_id =
ExtensionApiFrameIdMap::GetDocumentId(web_contents->GetPrimaryMainFrame())
.ToString();
auto* permissions_manager = PermissionsManager::Get(profile());
auto function_params = [](const std::string& document_id) {
return base::StringPrintf(R"([{"documentId": "%s"}])", document_id.c_str());
};
// Remove host access request for document, when it has no active requests.
{
auto function =
base::MakeRefCounted<PermissionsRemoveHostAccessRequestFunction>();
function->set_extension(extension.get());
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), function_params(document_id), profile(),
api_test_utils::FunctionMode::kNone);
EXPECT_EQ(
"Extension cannot remove a host access request that doesn't exist.",
error);
// Verify there is no request.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Add host access request for document.
{
auto function =
base::MakeRefCounted<PermissionsAddHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), function_params(document_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify host access request was added.
EXPECT_TRUE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
// Remove host access request for document, when it has an active requests.
{
auto function =
base::MakeRefCounted<PermissionsRemoveHostAccessRequestFunction>();
function->set_extension(extension.get());
EXPECT_TRUE(api_test_utils::RunFunction(
function.get(), function_params(document_id), profile(),
api_test_utils::FunctionMode::kNone));
// Verify request was removed.
EXPECT_FALSE(permissions_manager->HasActiveHostAccessRequest(
tab_id, extension->id()));
}
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
} // namespace extensions