| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/host_access_request_helper.h" |
| |
| #include <sys/types.h> |
| |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/permissions_manager.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/url_pattern.h" |
| |
| namespace extensions { |
| |
| HostAccessRequestsHelper::HostAccessRequestsHelper( |
| PassKey pass_key, |
| PermissionsManager* permissions_manager, |
| content::WebContents* web_contents, |
| int tab_id) |
| : content::WebContentsObserver(web_contents), |
| permissions_manager_(permissions_manager), |
| web_contents_(web_contents), |
| tab_id_(tab_id) { |
| extension_registry_observation_.Observe( |
| ExtensionRegistry::Get(web_contents->GetBrowserContext())); |
| } |
| |
| HostAccessRequestsHelper::~HostAccessRequestsHelper() = default; |
| |
| void HostAccessRequestsHelper::AddRequest( |
| const Extension& extension, |
| const std::optional<URLPattern>& filter) { |
| // Extension must not have granted access to the current site. |
| auto site_access = permissions_manager_->GetSiteAccess( |
| extension, web_contents_->GetLastCommittedURL()); |
| CHECK(!site_access.has_site_access && !site_access.has_all_sites_access); |
| |
| extensions_with_requests_.insert({extension.id(), filter}); |
| } |
| |
| void HostAccessRequestsHelper::UpdateRequest( |
| const Extension& extension, |
| const std::optional<URLPattern>& filter) { |
| // We can only update a request if there is an existent one. |
| CHECK(HasRequest(extension.id())); |
| |
| // Extension must not have granted access to the current site. |
| auto site_access = permissions_manager_->GetSiteAccess( |
| extension, web_contents_->GetLastCommittedURL()); |
| CHECK(!site_access.has_site_access && !site_access.has_all_sites_access); |
| |
| extensions_with_requests_.at(extension.id()) = filter; |
| } |
| |
| bool HostAccessRequestsHelper::RemoveRequest( |
| const ExtensionId& extension_id, |
| const std::optional<URLPattern>& filter) { |
| auto requests_iter = extensions_with_requests_.find(extension_id); |
| if (requests_iter == extensions_with_requests_.end()) { |
| return false; |
| } |
| |
| // Remove request iff it matches the parameter when given. Otherwise, always |
| // remove the request. |
| if (!filter || requests_iter->second == filter) { |
| extensions_with_requests_.erase(extension_id); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool HostAccessRequestsHelper::RemoveRequestIfGrantedAccess( |
| const Extension& extension) { |
| // Request is removed iff extension has access to the current site. |
| const GURL& url = web_contents_->GetLastCommittedURL(); |
| PermissionsManager::ExtensionSiteAccess site_access = |
| permissions_manager_->GetSiteAccess(extension, url); |
| if (!site_access.has_site_access && !site_access.has_all_sites_access && |
| !permissions_manager_->HasActiveTabAndCanAccess(extension, url)) { |
| return false; |
| } |
| |
| return RemoveRequest(extension.id(), /*filter=*/std::nullopt); |
| } |
| |
| void HostAccessRequestsHelper::UserDismissedRequest( |
| const ExtensionId& extension_id) { |
| CHECK(extensions_with_requests_.contains(extension_id)); |
| extensions_with_requests_dismissed_.insert(extension_id); |
| } |
| |
| bool HostAccessRequestsHelper::HasRequest( |
| const ExtensionId& extension_id) const { |
| return extensions_with_requests_.contains(extension_id); |
| } |
| |
| bool HostAccessRequestsHelper::HasActiveRequest( |
| const ExtensionId& extension_id) const { |
| if (!extensions_with_requests_.contains(extension_id)) { |
| return false; |
| } |
| |
| if (extensions_with_requests_dismissed_.contains(extension_id)) { |
| return false; |
| } |
| |
| // Web contents must match the filter if provided, otherwise request is always |
| // active. |
| std::optional<URLPattern> filter = extensions_with_requests_.at(extension_id); |
| return !filter || |
| filter.value().MatchesURL(web_contents_->GetLastCommittedURL()); |
| } |
| |
| bool HostAccessRequestsHelper::HasRequests() { |
| return !extensions_with_requests_.empty(); |
| } |
| |
| void HostAccessRequestsHelper::OnExtensionUnloaded( |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| UnloadedExtensionReason reason) { |
| RemoveRequest(extension->id(), /*filter=*/std::nullopt); |
| |
| if (!HasRequests()) { |
| permissions_manager_->DeleteHostAccessRequestHelperFor(tab_id_); |
| // IMPORTANT: This object is now deleted and is unsafe to use. |
| } |
| } |
| |
| void HostAccessRequestsHelper::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| // Sub-frames don't get specific requests. |
| if (!navigation_handle->IsInPrimaryMainFrame() || |
| !navigation_handle->HasCommitted() || |
| navigation_handle->IsSameDocument()) { |
| return; |
| } |
| |
| // Only clear requests for cross-origin navigations. |
| if (navigation_handle->IsSameOrigin()) { |
| return; |
| } |
| |
| extensions_with_requests_.clear(); |
| extensions_with_requests_dismissed_.clear(); |
| |
| permissions_manager_->NotifyHostAccessRequestsCleared(tab_id_); |
| permissions_manager_->DeleteHostAccessRequestHelperFor(tab_id_); |
| // IMPORTANT: This object is now deleted and is unsafe to use. |
| } |
| |
| void HostAccessRequestsHelper::WebContentsDestroyed() { |
| // Delete web contents pointer so it's not dangling at helper's destruction. |
| web_contents_ = nullptr; |
| |
| extensions_with_requests_.clear(); |
| extensions_with_requests_dismissed_.clear(); |
| |
| permissions_manager_->DeleteHostAccessRequestHelperFor(tab_id_); |
| // IMPORTANT: This object is now deleted and is unsafe to use. |
| } |
| |
| } // namespace extensions |