blob: 68f069f12efed526089ddde7361ca31a109efe9f [file] [log] [blame]
// Copyright 2025 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/enterprise/data_protection/view_source_navigation_throttle.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/enterprise/connectors/connectors_service.h"
#include "chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_factory.h"
#include "components/safe_browsing/content/browser/safe_browsing_blocking_page.h"
#include "components/safe_browsing/content/browser/safe_browsing_blocking_page_factory.h"
#include "components/safe_browsing/content/browser/ui_manager.h"
#include "components/safe_browsing/core/browser/db/util.h"
#include "components/safe_browsing/core/browser/realtime/policy_engine.h"
#include "components/safe_browsing/core/browser/realtime/url_lookup_service_base.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "components/security_interstitials/core/unsafe_resource.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle_registry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "url/gurl.h"
namespace enterprise_data_protection {
namespace {
GURL GetURLWithViewSourcePrefix(content::NavigationHandle* handle) {
return GURL(content::kViewSourceScheme + std::string(":") +
handle->GetURL().spec());
}
bool IsEnterpriseLookupEnabled(Profile* profile) {
// Some tests return a non-null pointer for the enterprise lookup service,
// so we need to defensively check if enterprise lookup is enabled.
auto* connectors_service =
enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
profile);
bool has_valid_dm_token =
connectors_service &&
connectors_service->GetDMTokenForRealTimeUrlCheck().has_value();
return safe_browsing::RealTimePolicyEngine::CanPerformEnterpriseFullURLLookup(
profile->GetPrefs(), has_valid_dm_token, profile->IsOffTheRecord(),
profile->IsGuestSession());
}
// Helper function to create an UnsafeResource from an RTLookupResponse.
// Returns true if a blockable threat was found and the resource was populated.
// Assumes threat_info in rt_lookup_response is sorted by decreasing severity.
bool CreateUnsafeResourceFromRTLookupResponse(
content::NavigationHandle* handle,
const safe_browsing::RTLookupResponse& rt_lookup_response,
security_interstitials::UnsafeResource& out_resource) {
if (rt_lookup_response.threat_info_size() == 0) {
return false; // No threat info.
}
// Since threats are sorted by severity, we can take the first one
// that translates to a blockable SBThreatType.
const auto& first_threat_info = rt_lookup_response.threat_info(0);
safe_browsing::SBThreatType threat_type = safe_browsing::
RealTimeUrlLookupServiceBase::GetSBThreatTypeForRTThreatType(
first_threat_info.threat_type(), first_threat_info.verdict_type());
// Check if the most severe threat is one we should block on.
// For this throttle, we primarily care about enterprise policies.
if (threat_type ==
safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_BLOCK ||
threat_type ==
safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_WARN) {
out_resource.url = GetURLWithViewSourcePrefix(handle);
out_resource.threat_type = threat_type;
out_resource.threat_source =
safe_browsing::ThreatSource::URL_REAL_TIME_CHECK;
out_resource.rt_lookup_response = rt_lookup_response;
out_resource.navigation_id = handle->GetNavigationId();
out_resource.rfh_locator.frame_tree_node_id =
handle->GetFrameTreeNodeId().value();
return true;
}
return false; // The most severe threat is not one we are blocking for.
}
} // namespace
// static
void ViewSourceNavigationThrottle::MaybeCreateAndAdd(
content::NavigationThrottleRegistry& registry,
safe_browsing::SafeBrowsingUIManager* ui_manager) {
if (!ui_manager) {
return;
}
// Only outer-most main frames show the interstitial through the navigation
// throttle. In other cases, the interstitial is shown via
// BaseUIManager::DisplayBlockingPage.
content::NavigationHandle& handle = registry.GetNavigationHandle();
if (!handle.IsInPrimaryMainFrame() && !handle.IsInPrerenderedMainFrame()) {
return;
}
if (!handle.GetNavigationEntry() ||
!handle.GetNavigationEntry()->IsViewSourceMode()) {
return;
}
registry.AddThrottle(
base::WrapUnique(new ViewSourceNavigationThrottle(registry, ui_manager)));
}
ViewSourceNavigationThrottle::ViewSourceNavigationThrottle(
content::NavigationThrottleRegistry& registry,
safe_browsing::SafeBrowsingUIManager* manager)
: content::NavigationThrottle(registry), manager_(manager) {
content::NavigationHandle* handle = navigation_handle();
content::BrowserContext* browser_context =
handle->GetWebContents()->GetBrowserContext();
Profile* profile = Profile::FromBrowserContext(browser_context);
if (IsEnterpriseLookupEnabled(profile)) {
url_lookup_service_ =
safe_browsing::ChromeEnterpriseRealTimeUrlLookupServiceFactory::
GetForProfile(profile)
->GetWeakPtr();
}
}
inline ViewSourceNavigationThrottle::~ViewSourceNavigationThrottle() = default;
content::NavigationThrottle::ThrottleCheckResult
ViewSourceNavigationThrottle::WillStartRequest() {
return FireRealtimeLookup();
}
content::NavigationThrottle::ThrottleCheckResult
ViewSourceNavigationThrottle::WillRedirectRequest() {
return FireRealtimeLookup();
}
content::NavigationThrottle::ThrottleCheckResult
ViewSourceNavigationThrottle::WillProcessResponse() {
DCHECK(manager_);
content::NavigationHandle* handle = navigation_handle();
security_interstitials::UnsafeResource resource;
auto url = GetURLWithViewSourcePrefix(handle);
safe_browsing::ThreatSeverity severity =
manager_->GetSeverestThreatForRedirectChain(
{url}, handle->GetNavigationId(), resource);
// Unsafe resource will show a blocking page
if (severity != std::numeric_limits<safe_browsing::ThreatSeverity>::max() &&
resource.threat_type !=
safe_browsing::SBThreatType::SB_THREAT_TYPE_SAFE) {
// Subframes and nested frame trees will show an interstitial directly
// from BaseUIManager::DisplayBlockingPage.
DCHECK(handle->IsInPrimaryMainFrame() ||
handle->IsInPrerenderedMainFrame());
security_interstitials::SecurityInterstitialPage* blocking_page =
manager_->CreateBlockingPage(
handle->GetWebContents(), url, {resource},
/*forward_extension_event=*/true,
/*blocked_page_shown_timestamp=*/std::nullopt);
std::string error_page_content = blocking_page->GetHTMLContents();
security_interstitials::SecurityInterstitialTabHelper::
AssociateBlockingPage(handle, base::WrapUnique(blocking_page));
return content::NavigationThrottle::ThrottleCheckResult(
CANCEL, net::ERR_BLOCKED_BY_CLIENT, error_page_content);
}
return content::NavigationThrottle::PROCEED;
}
const char* ViewSourceNavigationThrottle::GetNameForLogging() {
return "ViewSourceNavigationThrottle";
}
content::NavigationThrottle::ThrottleCheckResult
ViewSourceNavigationThrottle::FireRealtimeLookup() {
if (!url_lookup_service_) {
return content::NavigationThrottle::PROCEED;
}
content::NavigationHandle* handle = navigation_handle();
auto url = GetURLWithViewSourcePrefix(handle);
content::WebContents* web_contents = handle->GetWebContents();
url_lookup_service_->StartMaybeCachedLookup(
url,
base::BindOnce(&ViewSourceNavigationThrottle::OnRealTimeLookupComplete,
weak_factory_.GetWeakPtr(), handle),
base::SequencedTaskRunner::GetCurrentDefault(),
sessions::SessionTabHelper::IdForTab(web_contents),
/*referring_app_info=*/std::nullopt, /*use_cache=*/false);
return content::NavigationThrottle::DEFER;
}
void ViewSourceNavigationThrottle::OnRealTimeLookupComplete(
content::NavigationHandle* handle,
bool is_success,
bool is_cached,
std::unique_ptr<safe_browsing::RTLookupResponse> rt_lookup_response) {
if (!is_success || !rt_lookup_response) {
Resume();
return;
}
security_interstitials::UnsafeResource resource;
if (CreateUnsafeResourceFromRTLookupResponse(handle, *rt_lookup_response,
resource)) {
manager_->DisplayBlockingPage(resource);
}
Resume();
}
} // namespace enterprise_data_protection