blob: b80b732df4024cc9aa619903ea6c803666c3d00d [file] [log] [blame]
// Copyright 2022 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/privacy_sandbox/privacy_sandbox_settings_delegate.h"
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/feature_list.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/privacy_sandbox/privacy_sandbox_notice_confirmation.h"
#include "chrome/browser/privacy_sandbox/tracking_protection_onboarding_factory.h"
#include "chrome/browser/privacy_sandbox/tracking_protection_settings_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/tpcd/experiment/experiment_manager.h"
#include "chrome/browser/tpcd/experiment/tpcd_experiment_features.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/privacy_sandbox/privacy_sandbox_prefs.h"
#include "components/privacy_sandbox/tpcd_experiment_eligibility.h"
#include "components/privacy_sandbox/tracking_protection_onboarding.h"
#include "components/privacy_sandbox/tracking_protection_settings.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/tribool.h"
#include "content/public/common/content_features.h"
#include "net/cookies/cookie_util.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/android/webapps/webapp_registry.h"
#endif
namespace {
using TpcdExperimentEligibility = privacy_sandbox::TpcdExperimentEligibility;
signin::Tribool GetPrivacySandboxRestrictedByAccountCapability(
signin::IdentityManager* identity_manager) {
const auto core_account_info =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
const AccountInfo account_info =
identity_manager->FindExtendedAccountInfo(core_account_info);
return account_info.capabilities.can_run_chrome_privacy_sandbox_trials();
}
const base::FeatureParam<bool> kCookieDeprecationUseProfileFiltering{
&features::kCookieDeprecationFacilitatedTesting, "use_profile_filtering",
false};
} // namespace
PrivacySandboxSettingsDelegate::PrivacySandboxSettingsDelegate(
Profile* profile,
tpcd::experiment::ExperimentManager* experiment_manager,
PrivacySandboxCountries* privacy_sandbox_countries)
: profile_(profile),
experiment_manager_(experiment_manager),
privacy_sandbox_countries_(privacy_sandbox_countries)
#if BUILDFLAG(IS_ANDROID)
,
webapp_registry_(std::make_unique<WebappRegistry>())
#endif
{
}
PrivacySandboxSettingsDelegate::~PrivacySandboxSettingsDelegate() = default;
bool PrivacySandboxSettingsDelegate::IsRestrictedNoticeEnabled() const {
return privacy_sandbox::IsRestrictedNoticeRequired(
privacy_sandbox_countries_);
}
bool PrivacySandboxSettingsDelegate::IsPrivacySandboxRestricted() const {
// If the Sandbox was ever reported as restricted, it is always restricted.
// TODO (crbug.com/1428546): Adjust when we have a graduation flow.
bool was_ever_reported_as_restricted =
profile_->GetPrefs()->GetBoolean(prefs::kPrivacySandboxM1Restricted);
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile_);
if (!identity_manager ||
!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
// The user isn't signed in so we can't apply any capabilties-based
// restrictions.
return was_ever_reported_as_restricted;
}
auto restricted_by_capability =
GetPrivacySandboxRestrictedByAccountCapability(identity_manager);
// The Privacy Sandbox is not considered restricted unless the
// capability has a definitive false signal.
bool is_restricted = restricted_by_capability == signin::Tribool::kFalse;
// If the capability is restricting the Sandbox, "latch", so the sandbox is
// always restricted.
if (is_restricted) {
profile_->GetPrefs()->SetBoolean(prefs::kPrivacySandboxM1Restricted, true);
}
return was_ever_reported_as_restricted || is_restricted;
}
bool PrivacySandboxSettingsDelegate::IsPrivacySandboxCurrentlyUnrestricted()
const {
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile_);
if (!identity_manager ||
!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
// The user isn't signed in so we can't apply any capabilties-based
// restrictions.
return false;
}
const AccountInfo account_info =
identity_manager->FindExtendedPrimaryAccountInfo(
signin::ConsentLevel::kSignin);
auto capability =
account_info.capabilities.can_run_chrome_privacy_sandbox_trials();
return capability == signin::Tribool::kTrue;
}
bool PrivacySandboxSettingsDelegate::IsSubjectToM1NoticeRestricted() const {
// If the feature is deactivated, the notice shouldn't be shown.
if (!privacy_sandbox::IsRestrictedNoticeRequired(
privacy_sandbox_countries_)) {
return false;
}
return PrivacySandboxRestrictedNoticeRequired();
}
bool PrivacySandboxSettingsDelegate::IsIncognitoProfile() const {
return profile_->IsIncognitoProfile();
}
bool PrivacySandboxSettingsDelegate::HasAppropriateTopicsConsent() const {
// If the profile doesn't require a release 4 consent, then it always has
// an appropriate (i.e. not required) Topics consent.
if (!privacy_sandbox::IsConsentRequired(privacy_sandbox_countries_)) {
return true;
}
// Ideally we could consult the PrivacySandboxService, and centralise this
// logic. However, that service depends on PrivacySandboxSettings, which will
// own this delegate, and so including it here would create a circular
// dependency.
return profile_->GetPrefs()->GetBoolean(
prefs::kPrivacySandboxTopicsConsentGiven);
}
bool PrivacySandboxSettingsDelegate::PrivacySandboxRestrictedNoticeRequired()
const {
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile_);
if (!identity_manager ||
!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
// The user isn't signed in so we can't apply any capabilties-based
// restrictions.
return false;
}
const AccountInfo account_info =
identity_manager->FindExtendedPrimaryAccountInfo(
signin::ConsentLevel::kSignin);
auto capability =
account_info.capabilities
.is_subject_to_chrome_privacy_sandbox_restricted_measurement_notice();
return capability == signin::Tribool::kTrue;
}
bool PrivacySandboxSettingsDelegate::IsCookieDeprecationExperimentEligible()
const {
if (!base::FeatureList::IsEnabled(
features::kCookieDeprecationFacilitatedTesting)) {
return false;
}
if (!features::kCookieDeprecationFacilitatedTestingEnableOTRProfiles.Get() &&
(profile_->IsOffTheRecord() || profile_->IsGuestSession())) {
return false;
}
// Uses per-profile filtering if enabled.
if (kCookieDeprecationUseProfileFiltering.Get()) {
// The 3PCD experiment eligibility persists for the browser session.
if (!is_cookie_deprecation_experiment_eligible_.has_value()) {
is_cookie_deprecation_experiment_eligible_ =
GetCookieDeprecationExperimentCurrentEligibility().is_eligible();
}
return *is_cookie_deprecation_experiment_eligible_;
}
if (experiment_manager_) {
return experiment_manager_->IsClientEligible().value_or(false);
}
return false;
}
TpcdExperimentEligibility PrivacySandboxSettingsDelegate::
GetCookieDeprecationExperimentCurrentEligibility() const {
if (tpcd::experiment::kForceEligibleForTesting.Get()) {
return TpcdExperimentEligibility(
TpcdExperimentEligibility::Reason::kForcedEligible);
}
// Whether third-party cookies are blocked.
if (tpcd::experiment::kExclude3PCBlocked.Get()) {
const auto cookie_controls_mode =
static_cast<content_settings::CookieControlsMode>(
profile_->GetPrefs()->GetInteger(prefs::kCookieControlsMode));
if (cookie_controls_mode ==
content_settings::CookieControlsMode::kBlockThirdParty) {
return TpcdExperimentEligibility(
TpcdExperimentEligibility::Reason::k3pCookiesBlocked);
}
scoped_refptr<content_settings::CookieSettings> cookie_settings =
CookieSettingsFactory::GetForProfile(profile_);
DCHECK(cookie_settings);
if (cookie_settings->GetDefaultCookieSetting() ==
ContentSetting::CONTENT_SETTING_BLOCK) {
return TpcdExperimentEligibility(
TpcdExperimentEligibility::Reason::k3pCookiesBlocked);
}
}
// Whether the privacy sandbox Ads APIs notice has been seen.
//
// TODO(linnan): Consider checking whether the restricted notice has been
// acknowledged (`prefs::kPrivacySandboxM1RestrictedNoticeAcknowledged`) as
// well.
if (tpcd::experiment::kExcludeNotSeenAdsAPIsNotice.Get()) {
const bool row_notice_acknowledged = profile_->GetPrefs()->GetBoolean(
prefs::kPrivacySandboxM1RowNoticeAcknowledged);
const bool eaa_notice_acknowledged = profile_->GetPrefs()->GetBoolean(
prefs::kPrivacySandboxM1EEANoticeAcknowledged);
if (!row_notice_acknowledged && !eaa_notice_acknowledged) {
return TpcdExperimentEligibility(
TpcdExperimentEligibility::Reason::kHasNotSeenNotice);
}
}
// Whether it's a dasher account.
if (tpcd::experiment::kExcludeDasherAccount.Get() &&
IsSubjectToEnterpriseFeatures()) {
return TpcdExperimentEligibility(
TpcdExperimentEligibility::Reason::kEnterpriseUser);
}
// TODO(linnan): Consider moving the following client-level filtering to
// `ExperimentManager`.
// Whether it's a new client.
if (tpcd::experiment::kExcludeNewUser.Get()) {
base::Time install_date =
base::Time::FromTimeT(g_browser_process->local_state()->GetInt64(
metrics::prefs::kInstallDate));
if (install_date.is_null() ||
base::Time::Now() - install_date <
tpcd::experiment::kInstallTimeForNewUser.Get()) {
return TpcdExperimentEligibility(
TpcdExperimentEligibility::Reason::kNewUser);
}
}
// Whether PWA or TWA has been installed on Android.
#if BUILDFLAG(IS_ANDROID)
if (tpcd::experiment::kExcludePwaOrTwaInstalled.Get() &&
!webapp_registry_->GetOriginsWithInstalledApp().empty()) {
return TpcdExperimentEligibility(
TpcdExperimentEligibility::Reason::kPwaOrTwaInstalled);
}
#endif
return TpcdExperimentEligibility(
TpcdExperimentEligibility::Reason::kEligible);
}
bool PrivacySandboxSettingsDelegate::IsSubjectToEnterpriseFeatures() const {
auto* identity_manager = IdentityManagerFactory::GetForProfile(profile_);
if (!identity_manager ||
!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
// The user isn't signed in so we can't apply any capabilties-based
// restrictions.
return false;
}
const AccountInfo account_info =
identity_manager->FindExtendedPrimaryAccountInfo(
signin::ConsentLevel::kSignin);
auto capability =
account_info.capabilities.is_subject_to_enterprise_features();
return capability == signin::Tribool::kTrue;
}
#if BUILDFLAG(IS_ANDROID)
void PrivacySandboxSettingsDelegate::OverrideWebappRegistryForTesting(
std::unique_ptr<WebappRegistry> webapp_registry) {
DCHECK(webapp_registry);
webapp_registry_ = std::move(webapp_registry);
}
#endif
bool PrivacySandboxSettingsDelegate::IsCookieDeprecationLabelAllowed() const {
if (!IsCookieDeprecationExperimentEligible()) {
return false;
}
auto* tracking_protection_onboarding =
TrackingProtectionOnboardingFactory::GetForProfile(profile_);
if (!tracking_protection_onboarding) {
return false;
}
if (tpcd::experiment::kDisable3PCookies.Get()) {
switch (tracking_protection_onboarding->GetOnboardingStatus()) {
case privacy_sandbox::TrackingProtectionOnboarding::OnboardingStatus::
kIneligible:
return false;
case privacy_sandbox::TrackingProtectionOnboarding::OnboardingStatus::
kEligible:
return !tpcd::experiment::kNeedOnboardingForLabel.Get();
case privacy_sandbox::TrackingProtectionOnboarding::OnboardingStatus::
kOnboarded:
return true;
}
} else if (tpcd::experiment::kEnableSilentOnboarding.Get()) {
switch (tracking_protection_onboarding->GetSilentOnboardingStatus()) {
case privacy_sandbox::TrackingProtectionOnboarding::
SilentOnboardingStatus::kIneligible:
return false;
case privacy_sandbox::TrackingProtectionOnboarding::
SilentOnboardingStatus::kEligible:
return !tpcd::experiment::kNeedOnboardingForLabel.Get();
case privacy_sandbox::TrackingProtectionOnboarding::
SilentOnboardingStatus::kOnboarded:
return true;
}
} else {
return true;
}
}
bool PrivacySandboxSettingsDelegate::
AreThirdPartyCookiesBlockedByCookieDeprecationExperiment() const {
if (net::cookie_util::IsForceThirdPartyCookieBlockingEnabled()) {
return false;
}
if (!IsCookieDeprecationExperimentEligible()) {
return false;
}
if (!tpcd::experiment::kDisable3PCookies.Get()) {
return false;
}
auto* tracking_protection_onboarding =
TrackingProtectionOnboardingFactory::GetForProfile(profile_);
if (!tracking_protection_onboarding) {
return false;
}
// Third-party cookies are not disabled until the profile gets onboarded.
switch (tracking_protection_onboarding->GetOnboardingStatus()) {
case privacy_sandbox::TrackingProtectionOnboarding::OnboardingStatus::
kIneligible:
case privacy_sandbox::TrackingProtectionOnboarding::OnboardingStatus::
kEligible:
return false;
case privacy_sandbox::TrackingProtectionOnboarding::OnboardingStatus::
kOnboarded:
break;
}
// Respect user preferences.
auto* tracking_protection_settings =
TrackingProtectionSettingsFactory::GetForProfile(profile_);
if (tracking_protection_settings &&
tracking_protection_settings->AreAllThirdPartyCookiesBlocked()) {
return false;
}
const auto cookie_controls_mode =
static_cast<content_settings::CookieControlsMode>(
profile_->GetPrefs()->GetInteger(prefs::kCookieControlsMode));
if (cookie_controls_mode ==
content_settings::CookieControlsMode::kBlockThirdParty) {
return false;
}
return true;
}