blob: 023d5a998c0aea2dbea81e8f09425506de2ced50 [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.h"
#include <memory>
#include <utility>
#include "base/auto_reset.h"
#include "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/notimplemented.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/extensions/api/permissions/permissions_api_helpers.h"
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/permissions/permissions_updater.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/permissions.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_api_frame_id_map.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/manifest_handlers/permissions_parser.h"
#include "extensions/common/permissions/permission_message_provider.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/permissions_info.h"
#include "extensions/common/url_pattern.h"
#include "extensions/common/url_pattern_set.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/chrome_extension_function_details.h"
#endif
namespace extensions {
using api::permissions::Permissions;
using permissions_api_helpers::UnpackPermissionSetResult;
namespace {
const char kBlockedByEnterprisePolicy[] =
"Permissions are blocked by enterprise policy.";
const char kCantRemoveRequiredPermissionsError[] =
"You cannot remove required permissions.";
const char kNotInManifestPermissionsError[] =
"Only permissions specified in the manifest may be requested.";
const char kUserGestureRequiredError[] =
"This function must be called during a user gesture";
constexpr char kMustSpecifyDocumentIdOrTabIdError[] =
"Must specify either 'documentId' or 'tabId'.";
constexpr char kTabNotFoundError[] = "No tab with ID '*'.";
constexpr char kInvalidDocumentIdError[] = "No document with ID '*'.";
constexpr char kExtensionHasSiteAccessError[] =
"Extension cannot add a host access request for a host it already has "
"access to.";
constexpr char kExtensionHasNoHostPermissionsError[] =
"Extension cannot add a host access request when it does not have any host "
"permissions.";
constexpr char kExtensionHasNoHostPermissionsForPatternError[] =
"Extension cannot add a host access request with a pattern that does match "
"any of its host permissions.";
constexpr char kExtensionRequestCannotBeRemovedError[] =
"Extension cannot remove a host access request that doesn't exist.";
constexpr char kAddRequestInvalidPatternError[] =
"Extension cannot add a request with an invalid value for 'pattern'.";
constexpr char kRemoveRequestInvalidPatternError[] =
"Extension cannot remove a request with an invalid value for 'pattern'.";
PermissionsRequestFunction::DialogAction g_dialog_action =
PermissionsRequestFunction::DialogAction::kDefault;
PermissionsRequestFunction::ShowDialogCallback* g_show_dialog_callback =
nullptr;
PermissionsRequestFunction* g_pending_request_function = nullptr;
bool ignore_user_gesture_for_tests = false;
// Returns whether `tab_id` is a valid tab. Populates `web_contents` with the
// ones belonging to the tab , and `error` if tab is invalid.
bool ValidateTab(int tab_id,
bool include_incognito_information,
content::BrowserContext* browser_context,
content::WebContents** web_contents,
std::string* error) {
bool is_valid = ExtensionTabUtil::GetTabById(
tab_id, browser_context, include_incognito_information, web_contents);
if (!is_valid) {
*error = ErrorUtils::FormatErrorMessage(kTabNotFoundError,
base::NumberToString(tab_id));
}
return is_valid;
}
// Returns whether `document_id` is a valid document. Populates `web_contents`
// with the ones belonging to the document attached frame, and `error` if
// document is invalid.
bool ValidateDocument(const std::string& document_id,
bool include_incognito_information,
content::BrowserContext* browser_context,
content::WebContents** web_contents,
std::string* error) {
// Document is invalid if its id doesn't exist.
ExtensionApiFrameIdMap::DocumentId frame_document_id =
ExtensionApiFrameIdMap::DocumentIdFromString(document_id);
if (!frame_document_id) {
*error =
ErrorUtils::FormatErrorMessage(kInvalidDocumentIdError, document_id);
return false;
}
// Document is invalid if there it has no frame attached.
content::RenderFrameHost* frame =
ExtensionApiFrameIdMap::Get()->GetRenderFrameHostByDocumentId(
frame_document_id);
if (!frame) {
*error =
ErrorUtils::FormatErrorMessage(kInvalidDocumentIdError, document_id);
return false;
}
// Document is invalid if the web contents doesn't exist in our
// BrowserContext. We check for this since we found the RenderFrameHost
// through a generic lookup.
*web_contents = content::WebContents::FromRenderFrameHost(frame);
if (!ExtensionTabUtil::IsWebContentsInContext(
*web_contents, browser_context, include_incognito_information)) {
*error =
ErrorUtils::FormatErrorMessage(kInvalidDocumentIdError, document_id);
return false;
}
return true;
}
// Returns whether `pattern` was successfully parsed into `parsed_pattern`.
bool ParsePattern(const std::string& pattern, URLPattern& parsed_pattern) {
parsed_pattern.SetValidSchemes(Extension::kValidHostPermissionSchemes);
return parsed_pattern.Parse(pattern) == URLPattern::ParseResult::kSuccess;
}
} // namespace
ExtensionFunction::ResponseAction PermissionsContainsFunction::Run() {
std::optional<api::permissions::Contains::Params> params =
api::permissions::Contains::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
std::string error;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
permissions_api_helpers::UnpackPermissionSet(
params->permissions,
PermissionsParser::GetRequiredPermissions(extension()),
PermissionsParser::GetOptionalPermissions(extension()),
ExtensionPrefs::Get(browser_context())
->AllowFileAccess(extension()->id()),
&error);
if (!unpack_result)
return RespondNow(Error(std::move(error)));
const PermissionSet& active_permissions =
extension()->permissions_data()->active_permissions();
bool has_all_permissions =
// An extension can never have an active permission that wasn't listed in
// the manifest, so we know it won't contain all permissions in
// |unpack_result| if there are any unlisted.
unpack_result->unlisted_apis.empty() &&
unpack_result->unlisted_hosts.is_empty() &&
// Restricted file scheme patterns cannot be active on the extension,
// since it doesn't have file access in that case.
unpack_result->restricted_file_scheme_patterns.is_empty() &&
// Otherwise, check each expected location for whether it contains the
// relevant permissions.
active_permissions.apis().Contains(unpack_result->optional_apis) &&
active_permissions.apis().Contains(unpack_result->required_apis) &&
active_permissions.explicit_hosts().Contains(
unpack_result->optional_explicit_hosts) &&
active_permissions.explicit_hosts().Contains(
unpack_result->required_explicit_hosts) &&
active_permissions.scriptable_hosts().Contains(
unpack_result->required_scriptable_hosts);
return RespondNow(ArgumentList(
api::permissions::Contains::Results::Create(has_all_permissions)));
}
ExtensionFunction::ResponseAction PermissionsGetAllFunction::Run() {
// TODO(devlin): We should filter out file:-scheme patterns if the extension
// doesn't have file access here, so that they don't show up when the
// extension calls getAll(). This can either be solved by filtering here or
// by not adding file:-scheme patterns to |active_permissions| without file
// access (the former is easier, the latter is probably better overall but may
// require some investigation).
std::unique_ptr<Permissions> permissions =
permissions_api_helpers::PackPermissionSet(
extension()->permissions_data()->active_permissions());
return RespondNow(
ArgumentList(api::permissions::GetAll::Results::Create(*permissions)));
}
ExtensionFunction::ResponseAction PermissionsRemoveFunction::Run() {
std::optional<api::permissions::Remove::Params> params =
api::permissions::Remove::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
std::string error;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
permissions_api_helpers::UnpackPermissionSet(
params->permissions,
PermissionsParser::GetRequiredPermissions(extension()),
PermissionsParser::GetOptionalPermissions(extension()),
ExtensionPrefs::Get(browser_context())
->AllowFileAccess(extension_->id()),
&error);
if (!unpack_result)
return RespondNow(Error(std::move(error)));
// We can't remove any permissions that weren't specified in the manifest.
if (!unpack_result->unlisted_apis.empty() ||
!unpack_result->unlisted_hosts.is_empty()) {
return RespondNow(Error(kNotInManifestPermissionsError));
}
// Make sure we only remove optional permissions, and not required
// permissions. Sadly, for some reason we support having a permission be both
// optional and required (and should assume its required), so we need both of
// these checks.
// TODO(devlin): *Why* do we support that? Should be a load error.
// NOTE(devlin): This won't support removal of required permissions that can
// withheld. I don't think that will be a common use case, and so is probably
// fine.
if (!unpack_result->required_apis.empty() ||
!unpack_result->required_explicit_hosts.is_empty() ||
!unpack_result->required_scriptable_hosts.is_empty()) {
return RespondNow(Error(kCantRemoveRequiredPermissionsError));
}
// Note: We don't check |restricted_file_scheme_patterns| here. If there are
// any, it means that the extension didn't have file access, but it also means
// that it doesn't, effectively, currently have that permission granted (i.e.,
// it doesn't actually have access to any file:-scheme URL).
PermissionSet permissions(
std::move(unpack_result->optional_apis), ManifestPermissionSet(),
std::move(unpack_result->optional_explicit_hosts), URLPatternSet());
// Only try and remove those permissions that are active on the extension.
// For backwards compatability with behavior before this check was added, just
// silently remove any that aren't present.
std::unique_ptr<const PermissionSet> permissions_to_revoke =
PermissionSet::CreateIntersection(
permissions, extension()->permissions_data()->active_permissions());
PermissionsUpdater(browser_context())
.RevokeOptionalPermissions(
*extension(), *permissions_to_revoke, PermissionsUpdater::REMOVE_SOFT,
base::BindOnce(
&PermissionsRemoveFunction::Respond, this,
ArgumentList(api::permissions::Remove::Results::Create(true))));
return did_respond() ? AlreadyResponded() : RespondLater();
}
// static
base::AutoReset<PermissionsRequestFunction::DialogAction>
PermissionsRequestFunction::SetDialogActionForTests(
DialogAction dialog_action) {
return base::AutoReset<PermissionsRequestFunction::DialogAction>(
&g_dialog_action, dialog_action);
}
// static
base::AutoReset<PermissionsRequestFunction::ShowDialogCallback*>
PermissionsRequestFunction::SetShowDialogCallbackForTests(
ShowDialogCallback* callback) {
return base::AutoReset<ShowDialogCallback*>(&g_show_dialog_callback,
callback);
}
// static
void PermissionsRequestFunction::ResolvePendingDialogForTests(
bool accept_dialog) {
CHECK(g_pending_request_function);
PermissionsRequestFunction* pending_function = g_pending_request_function;
// Clear out the pending function now. After Release() below, it's unsafe to
// use.
g_pending_request_function = nullptr;
ExtensionInstallPrompt::DoneCallbackPayload result(
accept_dialog ? ExtensionInstallPrompt::Result::ACCEPTED
: ExtensionInstallPrompt::Result::USER_CANCELED);
pending_function->OnInstallPromptDone(result);
pending_function->Release(); // Balanced in Run().
}
// static
void PermissionsRequestFunction::SetIgnoreUserGestureForTests(
bool ignore) {
ignore_user_gesture_for_tests = ignore;
}
PermissionsRequestFunction::PermissionsRequestFunction() = default;
PermissionsRequestFunction::~PermissionsRequestFunction() {
CHECK_NE(g_pending_request_function, this)
<< "Pending request function was never resolved!";
}
ExtensionFunction::ResponseAction PermissionsRequestFunction::Run() {
if (!user_gesture() && !ignore_user_gesture_for_tests &&
extension_->location() != mojom::ManifestLocation::kComponent) {
return RespondNow(Error(kUserGestureRequiredError));
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
gfx::NativeWindow native_window =
ChromeExtensionFunctionDetails(this).GetNativeWindowForUI();
if (!native_window && g_dialog_action == DialogAction::kDefault) {
return RespondNow(Error("Could not find an active window."));
}
#else
// TODO(crbug.com/419057482): Once we have a cross-platform interface for
// browser windows that works on desktop Android, check for an active window.
NOTIMPLEMENTED() << "Skipping active window check";
gfx::NativeWindow native_window = nullptr;
#endif
std::optional<api::permissions::Request::Params> params =
api::permissions::Request::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
std::string error;
std::unique_ptr<UnpackPermissionSetResult> unpack_result =
permissions_api_helpers::UnpackPermissionSet(
params->permissions,
PermissionsParser::GetRequiredPermissions(extension()),
PermissionsParser::GetOptionalPermissions(extension()),
ExtensionPrefs::Get(browser_context())
->AllowFileAccess(extension_->id()),
&error);
if (!unpack_result)
return RespondNow(Error(std::move(error)));
// Don't allow the extension to request any permissions that weren't specified
// in the manifest.
if (!unpack_result->unlisted_apis.empty() ||
!unpack_result->unlisted_hosts.is_empty()) {
return RespondNow(Error(kNotInManifestPermissionsError));
}
if (!unpack_result->restricted_file_scheme_patterns.is_empty()) {
return RespondNow(Error(
"Extension must have file access enabled to request '*'.",
unpack_result->restricted_file_scheme_patterns.begin()->GetAsString()));
}
const PermissionSet& active_permissions =
extension()->permissions_data()->active_permissions();
// Determine which of the requested permissions are optional permissions that
// are "new", i.e. aren't already active on the extension.
requested_optional_ = std::make_unique<const PermissionSet>(
std::move(unpack_result->optional_apis), ManifestPermissionSet(),
std::move(unpack_result->optional_explicit_hosts), URLPatternSet());
requested_optional_ =
PermissionSet::CreateDifference(*requested_optional_, active_permissions);
// Determine which of the requested permissions are withheld host permissions.
// Since hosts are not always exact matches, we cannot take a set difference.
// Thus we only consider requested permissions that are not already active on
// the extension.
URLPatternSet explicit_hosts;
for (const auto& host : unpack_result->required_explicit_hosts) {
if (!active_permissions.explicit_hosts().ContainsPattern(host)) {
explicit_hosts.AddPattern(host);
}
}
URLPatternSet scriptable_hosts;
for (const auto& host : unpack_result->required_scriptable_hosts) {
if (!active_permissions.scriptable_hosts().ContainsPattern(host)) {
scriptable_hosts.AddPattern(host);
}
}
requested_withheld_ = std::make_unique<const PermissionSet>(
APIPermissionSet(), ManifestPermissionSet(), std::move(explicit_hosts),
std::move(scriptable_hosts));
// Determine the total "new" permissions; this is the set of all permissions
// that aren't currently active on the extension.
std::unique_ptr<const PermissionSet> total_new_permissions =
PermissionSet::CreateUnion(*requested_withheld_, *requested_optional_);
// If all permissions are already active, nothing left to do.
if (total_new_permissions->IsEmpty()) {
constexpr bool granted = true;
return RespondNow(WithArguments(granted));
}
// Automatically declines api permissions requests, which are blocked by
// enterprise policy.
if (!ExtensionManagementFactory::GetForBrowserContext(browser_context())
->IsPermissionSetAllowed(extension(), *total_new_permissions)) {
return RespondNow(Error(kBlockedByEnterprisePolicy));
}
// At this point, all permissions in |requested_withheld_| should be within
// the |withheld_permissions| section of the PermissionsData.
DCHECK(extension()->permissions_data()->withheld_permissions().Contains(
*requested_withheld_));
// Prompt the user for any new permissions that aren't contained within the
// already-granted permissions. We don't prompt for already-granted
// permissions since these were either granted to an earlier extension version
// or removed by the extension itself (using the permissions.remove() method).
std::unique_ptr<const PermissionSet> granted_permissions =
ExtensionPrefs::Get(browser_context())
->GetRuntimeGrantedPermissions(extension()->id());
std::unique_ptr<const PermissionSet> already_granted_permissions =
PermissionSet::CreateIntersection(*granted_permissions,
*requested_optional_);
total_new_permissions = PermissionSet::CreateDifference(
*total_new_permissions, *already_granted_permissions);
// We don't need to show the prompt if there are no new warnings, or if
// we're skipping the confirmation UI. COMPONENT extensions are allowed to
// silently increase their permission level.
const PermissionMessageProvider* message_provider =
PermissionMessageProvider::Get();
// TODO(devlin): We should probably use the same logic we do for permissions
// increases here, where we check if there are *new* warnings (e.g., so we
// don't warn about the tabs permission if history is already granted).
bool has_no_warnings =
message_provider
->GetPermissionMessages(message_provider->GetAllPermissionIDs(
*total_new_permissions, extension()->GetType()))
.empty();
if (has_no_warnings ||
extension_->location() == mojom::ManifestLocation::kComponent) {
OnInstallPromptDone(ExtensionInstallPrompt::DoneCallbackPayload(
ExtensionInstallPrompt::Result::ACCEPTED));
return did_respond() ? AlreadyResponded() : RespondLater();
}
// Otherwise, we have to prompt the user (though we might "autoconfirm" for a
// test.
if (g_dialog_action != DialogAction::kDefault) {
prompted_permissions_for_testing_ = total_new_permissions->Clone();
if (g_dialog_action == DialogAction::kAutoConfirm) {
OnInstallPromptDone(ExtensionInstallPrompt::DoneCallbackPayload(
ExtensionInstallPrompt::Result::ACCEPTED));
} else if (g_dialog_action == DialogAction::kAutoReject) {
OnInstallPromptDone(ExtensionInstallPrompt::DoneCallbackPayload(
ExtensionInstallPrompt::Result::USER_CANCELED));
} else {
CHECK_EQ(g_dialog_action, DialogAction::kProgrammatic);
// A test will let us know when to resolve the prompt. Add a reference to
// wait.
AddRef(); // Balanced in ResolvePendingDialogForTests().
if (g_show_dialog_callback) {
g_show_dialog_callback->Run(native_window);
}
g_pending_request_function = this;
}
return did_respond() ? AlreadyResponded() : RespondLater();
}
install_ui_ = std::make_unique<ExtensionInstallPrompt>(
Profile::FromBrowserContext(browser_context()), native_window);
install_ui_->ShowDialog(
base::BindOnce(&PermissionsRequestFunction::OnInstallPromptDone, this),
extension(), nullptr,
std::make_unique<ExtensionInstallPrompt::Prompt>(
ExtensionInstallPrompt::PERMISSIONS_PROMPT),
std::move(total_new_permissions),
ExtensionInstallPrompt::GetDefaultShowDialogCallback());
// ExtensionInstallPrompt::ShowDialog() can call the response synchronously.
return did_respond() ? AlreadyResponded() : RespondLater();
}
bool PermissionsRequestFunction::ShouldKeepWorkerAliveIndefinitely() {
// `permissions.request()` may trigger a user prompt. In this case, we allow
// the extension service worker to be kept alive past the typical 5 minute
// limit per-task, since it may be blocked on user action.
return true;
}
void PermissionsRequestFunction::OnInstallPromptDone(
ExtensionInstallPrompt::DoneCallbackPayload payload) {
// This dialog doesn't support the "withhold permissions" checkbox.
DCHECK_NE(payload.result,
ExtensionInstallPrompt::Result::ACCEPTED_WITH_WITHHELD_PERMISSIONS);
if (payload.result != ExtensionInstallPrompt::Result::ACCEPTED) {
Respond(ArgumentList(api::permissions::Request::Results::Create(false)));
return;
}
PermissionsUpdater permissions_updater(browser_context());
requesting_withheld_permissions_ = !requested_withheld_->IsEmpty();
requesting_optional_permissions_ = !requested_optional_->IsEmpty();
if (requesting_withheld_permissions_) {
permissions_updater.GrantRuntimePermissions(
*extension(), *requested_withheld_,
base::BindOnce(&PermissionsRequestFunction::OnRuntimePermissionsGranted,
this));
}
if (requesting_optional_permissions_) {
permissions_updater.GrantOptionalPermissions(
*extension(), *requested_optional_,
base::BindOnce(
&PermissionsRequestFunction::OnOptionalPermissionsGranted, this));
}
// Grant{Runtime|Optional}Permissions calls above can finish synchronously.
if (!did_respond())
RespondIfRequestsFinished();
}
void PermissionsRequestFunction::OnRuntimePermissionsGranted() {
requesting_withheld_permissions_ = false;
RespondIfRequestsFinished();
}
void PermissionsRequestFunction::OnOptionalPermissionsGranted() {
requesting_optional_permissions_ = false;
RespondIfRequestsFinished();
}
void PermissionsRequestFunction::RespondIfRequestsFinished() {
if (requesting_withheld_permissions_ || requesting_optional_permissions_)
return;
Respond(ArgumentList(api::permissions::Request::Results::Create(true)));
}
std::unique_ptr<const PermissionSet>
PermissionsRequestFunction::TakePromptedPermissionsForTesting() {
return std::move(prompted_permissions_for_testing_);
}
ExtensionFunction::ResponseAction
PermissionsAddHostAccessRequestFunction::Run() {
CHECK(base::FeatureList::IsEnabled(
extensions_features::kApiPermissionsHostAccessRequests));
std::optional<api::permissions::AddHostAccessRequest::Params> params =
api::permissions::AddHostAccessRequest::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
// Validate request has only one of document or tab id, and its value is
// valid.
const std::optional<std::string>& document_id_param =
params->request.document_id;
std::optional<int> tab_id_param = params->request.tab_id;
if ((!document_id_param && !tab_id_param) ||
(document_id_param && tab_id_param)) {
return RespondNow(Error(kMustSpecifyDocumentIdOrTabIdError));
}
content::WebContents* web_contents = nullptr;
int tab_id = -1;
bool is_valid = false;
std::string error;
if (tab_id_param) {
is_valid =
ValidateTab(tab_id_param.value(), include_incognito_information(),
browser_context(), &web_contents, &error);
tab_id = tab_id_param.value();
} else {
// document_id_param.
is_valid = ValidateDocument(document_id_param.value(),
include_incognito_information(),
browser_context(), &web_contents, &error);
tab_id = is_valid ? ExtensionTabUtil::GetTabId(web_contents) : -1;
}
if (!is_valid) {
CHECK(!error.empty());
return RespondNow(Error(error));
}
// Validate request has a valid pattern, if given.
std::optional<std::string> pattern_param = params->request.pattern;
std::optional<URLPattern> pattern;
if (pattern_param) {
URLPattern parsed_pattern;
if (!ParsePattern(*pattern_param, parsed_pattern)) {
return RespondNow(Error(kAddRequestInvalidPatternError));
}
pattern = parsed_pattern;
}
// Verify we properly retrieved the necessary information.
DCHECK(web_contents);
DCHECK_NE(tab_id, -1);
const GURL& url = web_contents->GetLastCommittedURL();
auto* permissions_manager = PermissionsManager::Get(browser_context());
// Request is invalid if extension didn't request any host permissions.
if (!permissions_manager->HasRequestedHostPermissions(*extension())) {
return RespondNow(Error(kExtensionHasNoHostPermissionsError));
}
// Request is invalid if extension has access to the tab's current web
// contents.
PermissionsManager::ExtensionSiteAccess site_access =
permissions_manager->GetSiteAccess(*extension(), url);
if (site_access.has_site_access ||
extension()->permissions_data()->HasTabPermissionsForSecurityOrigin(
tab_id, url)) {
return RespondNow(Error(kExtensionHasSiteAccessError));
}
// Request is invalid if pattern provided does not match the extension's host
// permissions.
if (pattern) {
const PermissionSet& required_permissions =
PermissionsParser::GetRequiredPermissions(extension());
const PermissionSet& optional_permissions =
PermissionsParser::GetOptionalPermissions(extension());
URLPatternSet pattern_list;
pattern_list.AddPattern(*pattern);
if (!required_permissions.effective_hosts().OverlapsWith(pattern_list) &&
!optional_permissions.effective_hosts().OverlapsWith(pattern_list)) {
return RespondNow(Error(kExtensionHasNoHostPermissionsForPatternError));
}
}
permissions_manager->AddHostAccessRequest(web_contents, tab_id, *extension(),
pattern);
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
PermissionsRemoveHostAccessRequestFunction::Run() {
CHECK(base::FeatureList::IsEnabled(
extensions_features::kApiPermissionsHostAccessRequests));
std::optional<api::permissions::RemoveHostAccessRequest::Params> params =
api::permissions::RemoveHostAccessRequest::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
const std::optional<std::string>& document_id_param =
params->request.document_id;
std::optional<int> tab_id_param = params->request.tab_id;
// Removal is invalid if it has both document and tab id.
if ((!document_id_param && !tab_id_param) ||
(document_id_param && tab_id_param)) {
return RespondNow(Error(kMustSpecifyDocumentIdOrTabIdError));
}
content::WebContents* web_contents = nullptr;
int tab_id = -1;
// Removal is invalid if document or tab id are not valid.
bool is_valid = false;
std::string error;
if (tab_id_param) {
is_valid =
ValidateTab(tab_id_param.value(), include_incognito_information(),
browser_context(), &web_contents, &error);
tab_id = tab_id_param.value();
} else {
// document_id_param.
is_valid = ValidateDocument(document_id_param.value(),
include_incognito_information(),
browser_context(), &web_contents, &error);
tab_id = ExtensionTabUtil::GetTabId(web_contents);
}
if (!is_valid) {
CHECK(!error.empty());
return RespondNow(Error(error));
}
// Removal is invalid if pattern provided cannot be parsed.
std::optional<std::string> pattern_param = params->request.pattern;
std::optional<URLPattern> pattern;
if (pattern_param) {
URLPattern parsed_pattern;
if (!ParsePattern(*pattern_param, parsed_pattern)) {
return RespondNow(Error(kRemoveRequestInvalidPatternError));
}
pattern = parsed_pattern;
}
// Verify we properly retrieved the necessary information.
DCHECK(web_contents);
DCHECK_NE(tab_id, -1);
bool is_removed =
PermissionsManager::Get(browser_context())
->RemoveHostAccessRequest(tab_id, extension()->id(), pattern);
if (!is_removed) {
return RespondNow(Error(kExtensionRequestCannotBeRemovedError));
}
return RespondNow(NoArguments());
}
} // namespace extensions