blob: 43d73ad69bb82383e151fb785e1179c66d2883a3 [file] [log] [blame]
// 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.
#import "ios/chrome/browser/enterprise/connectors/ios_enterprise_interstitial.h"
#import "components/application_locale_storage/application_locale_storage.h"
#import "components/grit/components_resources.h"
#import "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#import "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
#import "components/safe_browsing/core/common/proto/realtimeapi.pb.h"
#import "components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h"
#import "components/security_interstitials/core/urls.h"
#import "ios/chrome/browser/enterprise/connectors/reporting/reporting_util.h"
#import "ios/chrome/browser/safe_browsing/model/safe_browsing_metrics_collector_factory.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/components/security_interstitials/ios_blocking_page_metrics_helper.h"
#import "ui/base/resource/resource_bundle.h"
#import "ui/base/webui/web_ui_util.h"
namespace enterprise_connectors {
namespace {
// Creates a metrics helper for `resource`.
std::unique_ptr<security_interstitials::IOSBlockingPageMetricsHelper>
CreateMetricsHelper(const security_interstitials::UnsafeResource& resource) {
security_interstitials::MetricsHelper::ReportDetails reporting_info;
switch (resource.threat_type) {
case safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_WARN:
reporting_info.metric_prefix = "enterprise_warn";
break;
case safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_BLOCK:
reporting_info.metric_prefix = "enterprise_block";
break;
default:
NOTREACHED();
}
return std::make_unique<security_interstitials::IOSBlockingPageMetricsHelper>(
resource.weak_web_state.get(), resource.url, reporting_info);
}
class IOSEnterpriseBlockInterstitial : public IOSEnterpriseInterstitial {
public:
IOSEnterpriseBlockInterstitial(
const security_interstitials::UnsafeResource& resource,
std::unique_ptr<EnterprisePageControllerClient> client)
: IOSEnterpriseInterstitial(resource, std::move(client)) {
DCHECK_EQ(resource.threat_type,
safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_BLOCK);
ReportEnterpriseUrlFilteringEvent(UrlFilteringEventType::kBlockedSeen,
request_url(), web_state());
}
// EnterpriseInterstitialBase:
EnterpriseInterstitialBase::Type type() const override {
return EnterpriseInterstitialBase::Type::kBlock;
}
};
class IOSEnterpriseWarnInterstitial : public IOSEnterpriseInterstitial {
public:
IOSEnterpriseWarnInterstitial(
const security_interstitials::UnsafeResource& resource,
std::unique_ptr<EnterprisePageControllerClient> client)
: IOSEnterpriseInterstitial(resource, std::move(client)) {
DCHECK_EQ(resource.threat_type,
safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_WARN);
ReportEnterpriseUrlFilteringEvent(UrlFilteringEventType::kWarnedSeen,
request_url(), web_state());
}
// EnterpriseInterstitialBase:
EnterpriseInterstitialBase::Type type() const override {
return EnterpriseInterstitialBase::Type::kWarn;
}
};
} // namespace
#pragma mark - IOSEnterpriseInterstitial
// static
std::unique_ptr<IOSEnterpriseInterstitial>
IOSEnterpriseInterstitial::CreateBlockingPage(
const security_interstitials::UnsafeResource& resource) {
return std::make_unique<IOSEnterpriseBlockInterstitial>(
resource, std::make_unique<EnterprisePageControllerClient>(resource));
}
// static
std::unique_ptr<IOSEnterpriseInterstitial>
IOSEnterpriseInterstitial::CreateWarningPage(
const security_interstitials::UnsafeResource& resource) {
return std::make_unique<IOSEnterpriseWarnInterstitial>(
resource, std::make_unique<EnterprisePageControllerClient>(resource));
}
IOSEnterpriseInterstitial::IOSEnterpriseInterstitial(
const security_interstitials::UnsafeResource& resource,
std::unique_ptr<IOSEnterpriseInterstitial::EnterprisePageControllerClient>
client)
: IOSSecurityInterstitialPage(resource.weak_web_state.get(),
resource.url,
client.get()),
unsafe_resources_({resource}),
// Always do the `client` std::move after it's been passed to the
// `IOSSecurityInterstitialPage` constructor or other members.
client_(std::move(client)) {
if (web_state()) {
// The EnterpriseWarnPage requires the allow list to be instantiated. The
// allow list is not intantiated when opening the interstitial directly
// through the chrome://interstitials WebUI page.
SafeBrowsingUrlAllowList::CreateForWebState(web_state());
}
client_->metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::SHOW);
client_->metrics_helper()->RecordUserInteraction(
security_interstitials::MetricsHelper::TOTAL_VISITS);
}
IOSEnterpriseInterstitial::~IOSEnterpriseInterstitial() = default;
std::string IOSEnterpriseInterstitial::GetHtmlContents() const {
base::Value::Dict load_time_data;
PopulateInterstitialStrings(load_time_data);
webui::SetLoadTimeDataDefaults(client_->GetApplicationLocale(),
&load_time_data);
std::string html =
ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
IDR_SECURITY_INTERSTITIAL_HTML);
webui::AppendWebUiCssTextDefaults(&html);
return webui::GetLocalizedHtml(html, load_time_data);
}
void IOSEnterpriseInterstitial::HandleCommand(
security_interstitials::SecurityInterstitialCommand command) {
// Delegate the primary command handling logic to the client.
client_->HandleCommand(command);
}
bool IOSEnterpriseInterstitial::ShouldCreateNewNavigation() const {
return true;
}
void IOSEnterpriseInterstitial::WasDismissed() {
// Record do not proceed when tab is closed but not via page commands. For
// example: tapping the back button or closing the tab.
client_->metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::DONT_PROCEED);
}
void IOSEnterpriseInterstitial::PopulateInterstitialStrings(
base::Value::Dict& load_time_data) const {
PopulateStrings(load_time_data);
}
const std::vector<security_interstitials::UnsafeResource>&
IOSEnterpriseInterstitial::unsafe_resources() const {
return unsafe_resources_;
}
GURL IOSEnterpriseInterstitial::request_url() const {
return security_interstitials::IOSSecurityInterstitialPage::request_url();
}
#pragma mark - IOSEnterpriseInterstitial::EnterprisePageControllerClient
IOSEnterpriseInterstitial::EnterprisePageControllerClient::
EnterprisePageControllerClient(
const security_interstitials::UnsafeResource& resource)
: IOSBlockingPageControllerClient(
resource.weak_web_state.get(),
CreateMetricsHelper(resource),
GetApplicationContext()->GetApplicationLocaleStorage()->Get()),
request_url_(resource.url),
threat_type_(resource.threat_type),
threat_source_(resource.threat_source) {}
IOSEnterpriseInterstitial::EnterprisePageControllerClient::
~EnterprisePageControllerClient() {
RemovePendingUnsafeNavigationDecisionsFromAllowList();
}
void IOSEnterpriseInterstitial::EnterprisePageControllerClient::HandleCommand(
security_interstitials::SecurityInterstitialCommand command) {
switch (command) {
case security_interstitials::CMD_DONT_PROCEED:
metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::DONT_PROCEED);
RemovePendingUnsafeNavigationDecisionsFromAllowList();
GoBack();
break;
case security_interstitials::CMD_OPEN_HELP_CENTER:
metrics_helper()->RecordUserInteraction(
security_interstitials::MetricsHelper::SHOW_LEARN_MORE);
OpenUrlInNewForegroundTab(
GURL(security_interstitials::kEnterpriseInterstitialHelpLink));
break;
case security_interstitials::CMD_PROCEED:
// Proceed is only valid for Warning pages.
CHECK_EQ(threat_type_,
safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_WARN);
if (!web_state()) {
return;
}
// Report that the user bypassed the warning.
ReportEnterpriseUrlFilteringEvent(UrlFilteringEventType::kBypassed,
request_url_, web_state());
// Add the URL to the allowlist for this specific threat type.
if (SafeBrowsingUrlAllowList* allow_list =
SafeBrowsingUrlAllowList::FromWebState(web_state())) {
allow_list->AllowUnsafeNavigations(request_url_, threat_type_);
}
// Record bypass metrics.
if (ProfileIOS* profile =
ProfileIOS::FromBrowserState(web_state()->GetBrowserState())) {
if (safe_browsing::SafeBrowsingMetricsCollector* metrics_collector =
SafeBrowsingMetricsCollectorFactory::GetForProfile(profile)) {
metrics_collector->AddBypassEventToPref(threat_source_);
}
}
metrics_helper()->RecordUserDecision(
security_interstitials::MetricsHelper::PROCEED);
// Trigger the actual navigation/reload.
Proceed();
break;
default:
NOTREACHED() << "Unsupported command received: " << command;
}
}
void IOSEnterpriseInterstitial::EnterprisePageControllerClient::Proceed() {
Reload();
}
void IOSEnterpriseInterstitial::EnterprisePageControllerClient::GoBack() {
security_interstitials::IOSBlockingPageControllerClient::GoBack();
}
void IOSEnterpriseInterstitial::EnterprisePageControllerClient::
GoBackAfterNavigationCommitted() {
GoBack();
}
void IOSEnterpriseInterstitial::EnterprisePageControllerClient::
RemovePendingUnsafeNavigationDecisionsFromAllowList() {
if (web_state()) {
SafeBrowsingUrlAllowList::FromWebState(web_state())
->RemovePendingUnsafeNavigationDecisions(request_url_);
}
}
} // namespace enterprise_connectors