blob: 8ceecfd2704ec1565f42186ff663a1444174c7f1 [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.
#include "chrome/browser/supervised_user/supervised_user_verification_page.h"
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/signin/signin_promo.h"
#include "chrome/browser/ui/tabs/public/tab_interface.h"
#include "components/grit/components_resources.h"
#include "components/security_interstitials/content/security_interstitial_controller_client.h"
#include "components/security_interstitials/core/common_string_util.h"
#include "components/strings/grit/components_strings.h"
#include "components/supervised_user/core/common/features.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/referrer.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "ui/base/l10n/l10n_util.h"
using security_interstitials::MetricsHelper;
namespace {
constexpr char kBlockedSiteVerifyItsYouInterstitialStateHistogramName[] =
"FamilyLinkUser.BlockedSiteVerifyItsYouInterstitialState";
} // namespace
// static
const security_interstitials::SecurityInterstitialPage::TypeID
SupervisedUserVerificationPage::kTypeForTesting =
&SupervisedUserVerificationPage::kTypeForTesting;
// static
bool SupervisedUserVerificationPage::ShouldShowPage(
const supervised_user::ChildAccountService& child_account_service) {
switch (child_account_service.GetGoogleAuthState()) {
case supervised_user::ChildAccountService::AuthState::NOT_AUTHENTICATED:
case supervised_user::ChildAccountService::AuthState::AUTHENTICATED:
// The user is fully signed out or fully signed in. Don't show the
// interstitial.
return false;
case supervised_user::ChildAccountService::AuthState::PENDING:
case supervised_user::ChildAccountService::AuthState::
TRANSIENT_MOVING_TO_AUTHENTICATED:
// The user is in a stable pending state, or a transient state. Show the
// interstitial, as a parent approval request or YouTube visit would not
// be successful with the correct behavior.
//
// In the transient case, an update to AUTHENTICATED state may shortly
// follow, which will trigger this interstitial to be refreshed.
return true;
}
}
SupervisedUserVerificationPage::SupervisedUserVerificationPage(
content::WebContents* web_contents,
const std::string& email_to_reauth,
const GURL& request_url,
VerificationPurpose verification_purpose,
supervised_user::ChildAccountService* child_account_service,
ukm::SourceId source_id,
std::unique_ptr<
security_interstitials::SecurityInterstitialControllerClient>
controller_client,
bool is_main_frame,
bool has_second_custodian)
: security_interstitials::SecurityInterstitialPage(
web_contents,
request_url,
std::move(controller_client)),
email_to_reauth_(email_to_reauth),
request_url_(request_url),
verification_purpose_(verification_purpose),
child_account_service_(child_account_service),
source_id_(source_id),
is_main_frame_(is_main_frame),
has_second_custodian_(has_second_custodian) {
if (child_account_service_) {
// Reloads the interstitial to continue navigation once the supervised user
// is authenticated. Also closes the sign-in tabs opened by this
// interstitial.
google_auth_state_subscription_ =
child_account_service_->ObserveGoogleAuthState(base::BindRepeating(
&SupervisedUserVerificationPage::OnGoogleAuthStateUpdate,
weak_factory_.GetWeakPtr()));
RecordReauthStatusMetrics(Status::SHOWN);
}
}
SupervisedUserVerificationPage::~SupervisedUserVerificationPage() = default;
security_interstitials::SecurityInterstitialPage::TypeID
SupervisedUserVerificationPage::GetTypeForTesting() {
return SupervisedUserVerificationPage::kTypeForTesting;
}
void SupervisedUserVerificationPage::CloseSignInTabs() {
while (!signin_tabs_handle_id_list_.empty()) {
auto tab_handle_id = signin_tabs_handle_id_list_.front();
// Obtains the tab associated with the unique tab handle id. A tab pointer
// is only returned if the tab is still valid.
auto* tab_interface = tabs::TabInterface::MaybeGetFromHandle(tab_handle_id);
if (tab_interface) {
tab_interface->Close();
}
signin_tabs_handle_id_list_.pop_front();
}
// TODO(b/364546097): Ideally focus the last visited tab (before the sign-in
// page), before closing the sign-in tabs.
}
void SupervisedUserVerificationPage::OnGoogleAuthStateUpdate() {
// This callback doesn't guarantee that the state has changed, or that it has
// transitioned to fully signed in.
// If we're still in a state where we should be showing this interstitial,
// drop out.
CHECK(child_account_service_);
if (ShouldShowPage(*child_account_service_)) {
return;
}
RecordReauthStatusMetrics(Status::REAUTH_COMPLETED);
if (base::FeatureList::IsEnabled(
supervised_user::kCloseSignTabsFromReauthenticationInterstitial)) {
CloseSignInTabs();
}
controller()->Reload();
}
void SupervisedUserVerificationPage::PopulateInterstitialStrings(
base::Value::Dict& load_time_data) {
PopulateStringsForSharedHTML(load_time_data);
load_time_data.Set("optInLink", l10n_util::GetStringUTF16(
IDS_SAFE_BROWSING_SCOUT_REPORTING_AGREE));
load_time_data.Set(
"enhancedProtectionMessage",
l10n_util::GetStringUTF16(IDS_SAFE_BROWSING_ENHANCED_PROTECTION_MESSAGE));
switch (verification_purpose_) {
case VerificationPurpose::REAUTH_REQUIRED_SITE:
load_time_data.Set(
"tabTitle",
l10n_util::GetStringUTF16(IDS_SUPERVISED_USER_VERIFY_PAGE_TAB_TITLE));
load_time_data.Set(
"heading",
is_main_frame_
? l10n_util::GetStringUTF16(
IDS_SUPERVISED_USER_VERIFY_PAGE_PRIMARY_HEADING)
: l10n_util::GetStringUTF16(
IDS_SUPERVISED_USER_VERIFY_PAGE_SUBFRAME_YOUTUBE_HEADING));
load_time_data.Set(
"primaryParagraph",
l10n_util::GetStringUTF16(
IDS_SUPERVISED_USER_VERIFY_PAGE_PRIMARY_PARAGRAPH));
break;
case VerificationPurpose::DEFAULT_BLOCKED_SITE:
case VerificationPurpose::SAFE_SITES_BLOCKED_SITE:
case VerificationPurpose::MANUAL_BLOCKED_SITE:
load_time_data.Set(
"tabTitle", l10n_util::GetStringUTF16(IDS_BLOCK_INTERSTITIAL_TITLE));
load_time_data.Set(
"heading",
is_main_frame_
? l10n_util::GetStringUTF16(IDS_CHILD_BLOCK_INTERSTITIAL_HEADER)
: l10n_util::GetStringUTF16(
IDS_SUPERVISED_USER_VERIFY_PAGE_SUBFRAME_BLOCKED_SITE_HEADING));
load_time_data.Set(
"primaryParagraph",
l10n_util::GetStringUTF16(
IDS_CHILD_BLOCK_INTERSTITIAL_MESSAGE_NOT_SIGNED_IN));
load_time_data.Set("show_blocked_site_message", true);
load_time_data.Set(
"blockedSiteMessageHeader",
l10n_util::GetStringUTF8(IDS_GENERIC_SITE_BLOCK_HEADER));
load_time_data.Set("blockedSiteMessageReason",
l10n_util::GetStringUTF8(GetBlockMessageReasonId()));
break;
default:
NOTREACHED_NORETURN();
}
load_time_data.Set("primaryButtonText",
l10n_util::GetStringUTF16(
IDS_SUPERVISED_USER_VERIFY_PAGE_PRIMARY_BUTTON));
}
void SupervisedUserVerificationPage::OnInterstitialClosing() {}
int SupervisedUserVerificationPage::GetHTMLTemplateId() {
return IDR_SECURITY_INTERSTITIAL_HTML;
}
void SupervisedUserVerificationPage::PopulateStringsForSharedHTML(
base::Value::Dict& load_time_data) {
load_time_data.Set("overridable", false);
load_time_data.Set("hide_primary_button", false);
load_time_data.Set("show_recurrent_error_paragraph", false);
load_time_data.Set("recurrentErrorParagraph", "");
load_time_data.Set("openDetails", "");
load_time_data.Set("explanationParagraph", "");
load_time_data.Set("finalParagraph", "");
if (is_main_frame_) {
load_time_data.Set("type", "SUPERVISED_USER_VERIFY");
} else {
load_time_data.Set("type", "SUPERVISED_USER_VERIFY_SUBFRAME");
}
}
void SupervisedUserVerificationPage::RecordReauthStatusMetrics(Status status) {
if (!is_main_frame_) {
// Do not record metrics for subframe interstitials.
return;
}
switch (verification_purpose_) {
case VerificationPurpose::REAUTH_REQUIRED_SITE:
RecordYouTubeReauthStatusUkm(status);
break;
case VerificationPurpose::DEFAULT_BLOCKED_SITE:
case VerificationPurpose::SAFE_SITES_BLOCKED_SITE:
case VerificationPurpose::MANUAL_BLOCKED_SITE:
RecordBlockedUrlReauthStatusUma(status);
break;
default:
NOTREACHED();
}
}
void SupervisedUserVerificationPage::RecordYouTubeReauthStatusUkm(
Status status) {
CHECK_EQ(verification_purpose_, VerificationPurpose::REAUTH_REQUIRED_SITE);
auto builder =
ukm::builders::FamilyLinkUser_ReauthenticationInterstitial(source_id_);
switch (status) {
case Status::SHOWN:
builder.SetInterstitialShown(true);
break;
case Status::REAUTH_STARTED:
builder.SetReauthenticationStarted(true);
break;
case Status::REAUTH_COMPLETED:
builder.SetReauthenticationCompleted(true);
break;
default:
NOTREACHED();
}
builder.Record(ukm::UkmRecorder::Get());
}
void SupervisedUserVerificationPage::RecordBlockedUrlReauthStatusUma(
Status status) {
CHECK_NE(verification_purpose_, VerificationPurpose::REAUTH_REQUIRED_SITE);
auto state =
FamilyLinkUserReauthenticationInterstitialState::kInterstitialShown;
switch (status) {
case Status::SHOWN:
break;
case Status::REAUTH_STARTED:
state = FamilyLinkUserReauthenticationInterstitialState::
kReauthenticationStarted;
break;
case Status::REAUTH_COMPLETED:
state = FamilyLinkUserReauthenticationInterstitialState::
kReauthenticationCompleted;
break;
default:
NOTREACHED();
}
base::UmaHistogramEnumeration(
kBlockedSiteVerifyItsYouInterstitialStateHistogramName, state);
}
int SupervisedUserVerificationPage::GetBlockMessageReasonId() {
switch (verification_purpose_) {
case VerificationPurpose::DEFAULT_BLOCKED_SITE:
return has_second_custodian_
? IDS_CHILD_BLOCK_MESSAGE_DEFAULT_MULTI_PARENT
: IDS_CHILD_BLOCK_MESSAGE_DEFAULT_SINGLE_PARENT;
case VerificationPurpose::SAFE_SITES_BLOCKED_SITE:
return IDS_SUPERVISED_USER_BLOCK_MESSAGE_SAFE_SITES;
case VerificationPurpose::MANUAL_BLOCKED_SITE:
return has_second_custodian_
? IDS_CHILD_BLOCK_MESSAGE_MANUAL_MULTI_PARENT
: IDS_CHILD_BLOCK_MESSAGE_MANUAL_SINGLE_PARENT;
default:
NOTREACHED_NORETURN();
}
}
void SupervisedUserVerificationPage::CommandReceived(
const std::string& command) {
if (command == "\"pageLoadComplete\"") {
// content::WaitForRenderFrameReady sends this message when the page
// load completes. Ignore it.
return;
}
int cmd = 0;
bool retval = base::StringToInt(command, &cmd);
DCHECK(retval);
switch (cmd) {
case security_interstitials::CMD_OPEN_LOGIN: {
RecordReauthStatusMetrics(Status::REAUTH_STARTED);
content::OpenURLParams params(
signin::GetChromeReauthURL({.email = email_to_reauth_}),
content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_LINK, false);
auto* signin_web_contents =
SecurityInterstitialPage::web_contents()->OpenURL(
params, /*navigation_handle_callback=*/{});
if (base::FeatureList::IsEnabled(
supervised_user::
kCloseSignTabsFromReauthenticationInterstitial) &&
signin_web_contents) {
tabs::TabInterface* tab_interface =
tabs::TabInterface::GetFromContents(signin_web_contents);
signin_tabs_handle_id_list_.emplace_back(tab_interface->GetTabHandle());
}
break;
}
case security_interstitials::CMD_DONT_PROCEED:
case security_interstitials::CMD_OPEN_HELP_CENTER:
case security_interstitials::CMD_PROCEED:
case security_interstitials::CMD_DO_REPORT:
case security_interstitials::CMD_DONT_REPORT:
case security_interstitials::CMD_SHOW_MORE_SECTION:
case security_interstitials::CMD_OPEN_DATE_SETTINGS:
case security_interstitials::CMD_OPEN_REPORTING_PRIVACY:
case security_interstitials::CMD_OPEN_WHITEPAPER:
case security_interstitials::CMD_RELOAD:
case security_interstitials::CMD_OPEN_DIAGNOSTIC:
case security_interstitials::CMD_REPORT_PHISHING_ERROR:
// Not supported by the verification page.
NOTREACHED_IN_MIGRATION() << "Unsupported command: " << command;
break;
case security_interstitials::CMD_ERROR:
case security_interstitials::CMD_TEXT_FOUND:
case security_interstitials::CMD_TEXT_NOT_FOUND:
// Commands are for testing.
break;
default:
NOTREACHED();
}
}