blob: 622503a0a0e8c9886e5d6e6ab5c4da62284ce385 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/privacy_sandbox/privacy_sandbox_settings_impl.h"
#include <algorithm>
#include <cstddef>
#include <vector>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/json/values_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/browsing_topics/common/common_types.h"
#include "components/browsing_topics/common/semantic_tree.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/privacy_sandbox/canonical_topic.h"
#include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/privacy_sandbox/privacy_sandbox_prefs.h"
#include "components/privacy_sandbox/privacy_sandbox_settings.h"
#include "components/privacy_sandbox/tpcd_experiment_eligibility.h"
#include "components/privacy_sandbox/tracking_protection_settings.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_features.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/base/schemeful_site.h"
#include "net/cookies/site_for_cookies.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace privacy_sandbox {
namespace {
using ::content_settings::CookieControlsMode;
constexpr char kBlockedTopicsTopicKey[] = "topic";
constexpr char kBlockedTopicsBlockTimeKey[] = "blockedOn";
constexpr char kIsTopicsAllowedHistogram[] = "PrivacySandbox.IsTopicsAllowed";
constexpr char kIsTopicsAllowedForContextHistogram[] =
"PrivacySandbox.IsTopicsAllowedForContext";
constexpr char kIsAttributionReportingEverAllowedHistogram[] =
"PrivacySandbox.IsAttributionReportingEverAllowed";
constexpr char kIsAttributionReportingAllowedHistogram[] =
"PrivacySandbox.IsAttributionReportingAllowed";
constexpr char kMaySendAttributionReportHistogram[] =
"PrivacySandbox.MaySendAttributionReport";
constexpr char kIsFledgeJoinAllowedHistogram[] =
"PrivacySandbox.IsFledgeJoinAllowed";
constexpr char kIsFledgeLeaveAllowedHistogram[] =
"PrivacySandbox.IsFledgeLeaveAllowed";
constexpr char kIsFledgeUpdateAllowedHistogram[] =
"PrivacySandbox.IsFledgeUpdateAllowed";
constexpr char kIsFledgeSellAllowedHistogram[] =
"PrivacySandbox.IsFledgeSellAllowed";
constexpr char kIsFledgeBuyAllowedHistogram[] =
"PrivacySandbox.IsFledgeBuyAllowed";
constexpr char kIsFledgeReadAllowedHistogram[] =
"PrivacySandbox.IsFledgeReadAllowed";
constexpr char kIsPrivacySandboxReportingDestinationAttestedHistogram[] =
"PrivacySandbox.IsPrivacySandboxReportingDestinationAttested";
constexpr char kIsSharedStorageAllowedHistogram[] =
"PrivacySandbox.IsSharedStorageAllowed";
constexpr char kIsSharedStorageSelectURLAllowedHistogram[] =
"PrivacySandbox.IsSharedStorageSelectURLAllowed";
constexpr char kIsFencedStorageReadAllowedHistogram[] =
"PrivacySandbox.IsFencedStorageReadAllowed";
constexpr char kIsPrivateAggregationAllowedHistogram[] =
"PrivacySandbox.IsPrivateAggregationAllowed";
bool IsCookiesClearOnExitEnabled(HostContentSettingsMap* map) {
return map->GetDefaultContentSetting(ContentSettingsType::COOKIES) ==
ContentSetting::CONTENT_SETTING_SESSION_ONLY;
}
// Convert a stored FLEDGE block eTLD+1 into applicable content settings
// patterns. This ensures that if Public Suffix List membership changes, the
// stored item continues to match as when it was set. Multiple patterns are set
// to support IP address fallbacks, which do not support [*.] prefixes.
// TODO (crbug.com/1287153): This is somewhat hacky and can be removed when
// FLEDGE is controlled by a content setting directly.
std::vector<ContentSettingsPattern> FledgeBlockToContentSettingsPatterns(
const std::string& entry) {
return {ContentSettingsPattern::FromString("[*.]" + entry),
ContentSettingsPattern::FromString(entry)};
}
// Returns a base::Value::Dict for storage in prefs that represents |topic|
// blocked at the current time.
base::Value::Dict CreateBlockedTopicEntry(const CanonicalTopic& topic) {
return base::Value::Dict()
.Set(kBlockedTopicsTopicKey, topic.ToValue())
.Set(kBlockedTopicsBlockTimeKey, base::TimeToValue(base::Time::Now()));
}
std::set<browsing_topics::Topic> GetTopicsSetFromString(
std::string topics_string) {
if (topics_string.empty()) {
return {};
}
std::set<browsing_topics::Topic> result;
std::vector<std::string> tokens = base::SplitString(
topics_string, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
for (const std::string& token : tokens) {
int topic_id;
CHECK(base::StringToInt(token, &topic_id));
result.emplace(topic_id);
}
return result;
}
} // namespace
// static
bool PrivacySandboxSettingsImpl::IsAllowed(Status status) {
return status == Status::kAllowed;
}
// static
void PrivacySandboxSettingsImpl::JoinHistogram(const char* name,
Status status) {
base::UmaHistogramEnumeration(name, status);
}
// static
void PrivacySandboxSettingsImpl::JoinFledgeHistogram(
content::InterestGroupApiOperation interest_group_api_operation,
Status status) {
switch (interest_group_api_operation) {
case content::InterestGroupApiOperation::kJoin:
JoinHistogram(kIsFledgeJoinAllowedHistogram, status);
break;
case content::InterestGroupApiOperation::kLeave:
JoinHistogram(kIsFledgeLeaveAllowedHistogram, status);
break;
case content::InterestGroupApiOperation::kUpdate:
JoinHistogram(kIsFledgeUpdateAllowedHistogram, status);
break;
case content::InterestGroupApiOperation::kSell:
JoinHistogram(kIsFledgeSellAllowedHistogram, status);
break;
case content::InterestGroupApiOperation::kBuy:
JoinHistogram(kIsFledgeBuyAllowedHistogram, status);
break;
case content::InterestGroupApiOperation::kRead:
JoinHistogram(kIsFledgeReadAllowedHistogram, status);
break;
}
}
PrivacySandboxSettingsImpl::PrivacySandboxSettingsImpl(
std::unique_ptr<Delegate> delegate,
HostContentSettingsMap* host_content_settings_map,
scoped_refptr<content_settings::CookieSettings> cookie_settings,
TrackingProtectionSettings* tracking_protection_settings,
PrefService* pref_service)
: delegate_(std::move(delegate)),
host_content_settings_map_(host_content_settings_map),
cookie_settings_(cookie_settings),
tracking_protection_settings_(tracking_protection_settings),
pref_service_(pref_service) {
CHECK(pref_service_);
CHECK(host_content_settings_map_);
CHECK(cookie_settings_);
CHECK(tracking_protection_settings_);
// "Clear on exit" causes a cookie deletion on shutdown. But for practical
// purposes, we're notifying the observers on startup (which should be
// equivalent, as no cookie operations could have happened while the profile
// was shut down).
if (IsCookiesClearOnExitEnabled(host_content_settings_map_)) {
OnCookiesCleared();
}
tracking_protection_settings_observation_.Observe(
tracking_protection_settings_);
pref_change_registrar_.Init(pref_service_);
pref_change_registrar_.Add(
prefs::kPrivacySandboxRelatedWebsiteSetsEnabled,
base::BindRepeating(
&PrivacySandboxSettingsImpl::OnRelatedWebsiteSetsEnabledPrefChanged,
base::Unretained(this)));
pref_change_registrar_.Add(
prefs::kCookieControlsMode,
base::BindRepeating(
&PrivacySandboxSettingsImpl::OnCookieControlsModePrefChanged,
base::Unretained(this)));
}
PrivacySandboxSettingsImpl::~PrivacySandboxSettingsImpl() = default;
void PrivacySandboxSettingsImpl::Shutdown() {
observers_.Clear();
delegate_.reset();
host_content_settings_map_ = nullptr;
cookie_settings_.reset();
tracking_protection_settings_ = nullptr;
pref_service_ = nullptr;
pref_change_registrar_.Reset();
tracking_protection_settings_observation_.Reset();
}
PrivacySandboxSettingsImpl::Status
PrivacySandboxSettingsImpl::GetM1TopicAllowedStatus() const {
auto control_status = GetM1PrivacySandboxApiEnabledStatus(
prefs::kPrivacySandboxM1TopicsEnabled);
auto has_appropriate_consent = delegate_->HasAppropriateTopicsConsent();
// If `control_status` indicates that Topics is allowed, then
// `has_appropriate_consent` should be true, as there is no pathway for a user
// to enable Topics controls without also granting consent. The inverse does
// not hold, as an extension or policy may disable Topics, without necessarily
// revoking user consent.
if (control_status == Status::kAllowed && !has_appropriate_consent) {
// This status will be recorded via UMA, and is indicative of an error.
return Status::kMismatchedConsent;
}
return control_status;
}
const std::set<browsing_topics::Topic>&
PrivacySandboxSettingsImpl::GetFinchDisabledTopics() {
if (finch_disabled_topics_.size() > 0) {
return finch_disabled_topics_;
}
std::string disabled_topics_string =
blink::features::kBrowsingTopicsDisabledTopicsList.Get();
finch_disabled_topics_ = GetTopicsSetFromString(disabled_topics_string);
return finch_disabled_topics_;
}
const std::set<browsing_topics::Topic>&
PrivacySandboxSettingsImpl::GetFinchPrioritizedTopics() {
if (finch_prioritized_topics_.size() > 0) {
return finch_prioritized_topics_;
}
std::string prioritized_topics_string =
blink::features::kBrowsingTopicsPrioritizedTopicsList.Get();
finch_prioritized_topics_ = GetTopicsSetFromString(prioritized_topics_string);
return finch_prioritized_topics_;
}
bool PrivacySandboxSettingsImpl::IsTopicsAllowed() const {
Status status = GetM1TopicAllowedStatus();
JoinHistogram(kIsTopicsAllowedHistogram, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::IsTopicsAllowedForContext(
const url::Origin& top_frame_origin,
const GURL& url,
content::RenderFrameHost* console_frame) const {
// Check for attestation on the calling context's site.
Status attestation_status =
PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
net::SchemefulSite(url), PrivacySandboxAttestationsGatedAPI::kTopics);
if (!IsAllowed(attestation_status)) {
JoinHistogram(kIsTopicsAllowedForContextHistogram, attestation_status);
if (console_frame) {
console_frame->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Attestation check for Topics on " + url.spec() + " failed.");
}
return false;
}
Status status = GetM1TopicAllowedStatus();
if (IsAllowed(status)) {
status = GetSiteAccessAllowedStatus(top_frame_origin, url);
}
JoinHistogram(kIsTopicsAllowedForContextHistogram, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::IsTopicAllowed(const CanonicalTopic& topic) {
const auto& blocked_topics =
pref_service_->GetList(prefs::kPrivacySandboxBlockedTopics);
std::vector<browsing_topics::Topic> ancestor_topics =
browsing_topics::SemanticTree().GetAncestorTopics(topic.topic_id());
for (const base::Value& item : blocked_topics) {
auto blocked_topic =
CanonicalTopic::FromValue(*item.GetDict().Find(kBlockedTopicsTopicKey));
if (!blocked_topic) {
continue;
}
if ((topic.topic_id() == blocked_topic->topic_id()) ||
(base::Contains(ancestor_topics, blocked_topic->topic_id()))) {
return false;
}
}
for (browsing_topics::Topic blocked_topic_id : GetFinchDisabledTopics()) {
if ((topic.topic_id() == blocked_topic_id) ||
(base::Contains(ancestor_topics, blocked_topic_id))) {
return false;
}
}
return true;
}
void PrivacySandboxSettingsImpl::SetTopicAllowed(const CanonicalTopic& topic,
bool allowed) {
ScopedListPrefUpdate scoped_pref_update(pref_service_,
prefs::kPrivacySandboxBlockedTopics);
// Presence in the preference list indicates that a topic is blocked, as
// there is no concept of explicitly allowed topics. Thus, allowing a topic
// is the same as removing it, if it exists, from the blocklist. Blocking
// a topic is the same as adding it to the blocklist, but as duplicate entries
// are undesireable, removing any existing reference first is desireable.
// Thus, regardless of |allowed|, removing any existing reference is the
// first step.
scoped_pref_update->EraseIf([&](const base::Value& value) {
auto* blocked_topic_value = value.GetDict().Find(kBlockedTopicsTopicKey);
auto converted_topic = CanonicalTopic::FromValue(*blocked_topic_value);
return converted_topic && *converted_topic == topic;
});
// If the topic is being blocked, it can be (re)added to the blocklist. If the
// topic was removed from the blocklist above, this is equivalent to updating
// the modified time associated with the entry to the current time. As data
// deletions are typically from the current time backwards, this makes it
// more likely to be removed - a privacy improvement.
if (!allowed) {
scoped_pref_update->Append(CreateBlockedTopicEntry(topic));
}
}
bool PrivacySandboxSettingsImpl::IsTopicPrioritized(
const CanonicalTopic& topic) {
const std::set<browsing_topics::Topic>& prioritized_topics =
GetFinchPrioritizedTopics();
if (prioritized_topics.contains(topic.topic_id())) {
return true;
}
for (const browsing_topics::Topic& ancestor_topic :
browsing_topics::SemanticTree().GetAncestorTopics(topic.topic_id())) {
if (prioritized_topics.contains(ancestor_topic)) {
return true;
}
}
return false;
}
void PrivacySandboxSettingsImpl::ClearTopicSettings(base::Time start_time,
base::Time end_time) {
ScopedListPrefUpdate scoped_pref_update(pref_service_,
prefs::kPrivacySandboxBlockedTopics);
// Shortcut for maximum time range deletion.
if (start_time == base::Time() && end_time == base::Time::Max()) {
scoped_pref_update->clear();
return;
}
scoped_pref_update->EraseIf([&](const base::Value& value) {
auto blocked_time =
base::ValueToTime(value.GetDict().Find(kBlockedTopicsBlockTimeKey));
return start_time <= blocked_time && blocked_time <= end_time;
});
}
base::Time PrivacySandboxSettingsImpl::TopicsDataAccessibleSince() const {
return pref_service_->GetTime(
prefs::kPrivacySandboxTopicsDataAccessibleSince);
}
PrivacySandboxSettingsImpl::Status
PrivacySandboxSettingsImpl::GetM1AdMeasurementAllowedStatus(
const url::Origin& top_frame_origin,
const url::Origin& reporting_origin) const {
Status status = GetM1PrivacySandboxApiEnabledStatus(
prefs::kPrivacySandboxM1AdMeasurementEnabled);
if (!IsAllowed(status)) {
return status;
}
return GetSiteAccessAllowedStatus(top_frame_origin,
reporting_origin.GetURL());
}
bool PrivacySandboxSettingsImpl::IsAttributionReportingEverAllowed() const {
Status status = GetM1PrivacySandboxApiEnabledStatus(
prefs::kPrivacySandboxM1AdMeasurementEnabled);
JoinHistogram(kIsAttributionReportingEverAllowedHistogram, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::IsAttributionReportingAllowed(
const url::Origin& top_frame_origin,
const url::Origin& reporting_origin,
content::RenderFrameHost* console_frame) const {
// Check for attestation on the reporting origin.
Status attestation_status =
PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
net::SchemefulSite(reporting_origin),
PrivacySandboxAttestationsGatedAPI::kAttributionReporting);
if (!IsAllowed(attestation_status)) {
JoinHistogram(kIsAttributionReportingAllowedHistogram, attestation_status);
if (console_frame) {
console_frame->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Attestation check for Attribution Reporting on " +
reporting_origin.Serialize() + " failed.");
}
return false;
}
Status status =
GetM1AdMeasurementAllowedStatus(top_frame_origin, reporting_origin);
JoinHistogram(kIsAttributionReportingAllowedHistogram, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::MaySendAttributionReport(
const url::Origin& source_origin,
const url::Origin& destination_origin,
const url::Origin& reporting_origin,
content::RenderFrameHost* console_frame) const {
// Check for attestation on the reporting origin.
Status attestation_status =
PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
net::SchemefulSite(reporting_origin),
PrivacySandboxAttestationsGatedAPI::kAttributionReporting,
AttestationsDefaultBehavior::kAllow);
if (!IsAllowed(attestation_status)) {
JoinHistogram(kMaySendAttributionReportHistogram, attestation_status);
if (console_frame) {
console_frame->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Attestation check for Attribution Reporting on " +
reporting_origin.Serialize() + " failed.");
}
return false;
}
Status status = GetM1AdMeasurementAllowedStatus(
/*top_frame_origin=*/source_origin,
/*reporting_origin=*/reporting_origin);
if (IsAllowed(status)) {
status = GetM1AdMeasurementAllowedStatus(
/*top_frame_origin=*/destination_origin,
/*reporting_origin=*/reporting_origin);
}
JoinHistogram(kMaySendAttributionReportHistogram, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::
IsAttributionReportingTransitionalDebuggingAllowed(
const url::Origin& top_frame_origin,
const url::Origin& reporting_origin,
bool& can_bypass) const {
content_settings::CookieSettingsBase::CookieSettingWithMetadata
cookie_setting_with_metadata;
// Third party cookies must also be available for this context. An empty site
// for cookies is provided so the context is always treated as a third party.
bool allowed = cookie_settings_->IsFullCookieAccessAllowed(
reporting_origin.GetURL(), net::SiteForCookies(), top_frame_origin,
net::CookieSettingOverrides(), &cookie_setting_with_metadata);
if (base::FeatureList::IsEnabled(
kAttributionDebugReportingCookieDeprecationTesting)) {
can_bypass =
cookie_setting_with_metadata.BlockedByThirdPartyCookieBlocking() &&
delegate_->AreThirdPartyCookiesBlockedByCookieDeprecationExperiment();
} else {
can_bypass = false;
}
return allowed;
}
void PrivacySandboxSettingsImpl::SetFledgeJoiningAllowed(
const std::string& top_frame_etld_plus1,
bool allowed) {
ScopedDictPrefUpdate scoped_pref_update(
pref_service_, prefs::kPrivacySandboxFledgeJoinBlocked);
// Ensure that the provided etld_plus1 actually is an etld+1.
auto effective_top_frame_etld_plus1 =
net::registry_controlled_domains::GetDomainAndRegistry(
top_frame_etld_plus1,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
// Hosts are also accepted as a fallback. This may occur if the private
// registry has changed, and what the caller may be assuming is an eTLD+1 no
// longer is. Simply ignoring non-eTLD+1's may thus result in unexpected
// access.
if (effective_top_frame_etld_plus1 != top_frame_etld_plus1) {
// Add a dummy scheme and use GURL to confirm the provided string is a valid
// host.
const GURL url("https://" + top_frame_etld_plus1);
effective_top_frame_etld_plus1 = url.host();
}
// Ignore attempts to configure an empty etld+1. This will also catch the
// case where the eTLD+1 was not even a host, as GURL will have canonicalised
// it to empty.
if (effective_top_frame_etld_plus1.length() == 0) {
NOTREACHED() << "Cannot control FLEDGE joining for empty eTLD+1";
}
if (allowed) {
// Existence of the key implies blocking, so simply removing the key is
// sufficient. If the key wasn't already present, the following is a no-op.
scoped_pref_update->Remove(effective_top_frame_etld_plus1);
} else {
// Overriding the creation date for keys which already exist is acceptable.
// Time range based deletions are typically started from the current time,
// and so this will be more aggressively removed. This decreases the chance
// a potentially sensitive website remains in preferences.
scoped_pref_update->Set(effective_top_frame_etld_plus1,
base::TimeToValue(base::Time::Now()));
}
}
void PrivacySandboxSettingsImpl::ClearFledgeJoiningAllowedSettings(
base::Time start_time,
base::Time end_time) {
ScopedDictPrefUpdate scoped_pref_update(
pref_service_, prefs::kPrivacySandboxFledgeJoinBlocked);
auto& pref_data = scoped_pref_update.Get();
// Shortcut for maximum time range deletion
if (start_time == base::Time() && end_time == base::Time::Max()) {
pref_data.clear();
return;
}
std::vector<std::string> keys_to_remove;
for (auto entry : pref_data) {
std::optional<base::Time> created_time = base::ValueToTime(entry.second);
if (created_time.has_value() && start_time <= created_time &&
created_time <= end_time) {
keys_to_remove.push_back(entry.first);
}
}
for (const auto& key : keys_to_remove) {
pref_data.Remove(key);
}
}
bool PrivacySandboxSettingsImpl::IsFledgeJoiningAllowed(
const url::Origin& top_frame_origin) const {
ScopedDictPrefUpdate scoped_pref_update(
pref_service_, prefs::kPrivacySandboxFledgeJoinBlocked);
auto& pref_data = scoped_pref_update.Get();
for (auto entry : pref_data) {
if (std::ranges::any_of(FledgeBlockToContentSettingsPatterns(entry.first),
[&](const auto& pattern) {
return pattern.Matches(top_frame_origin.GetURL());
})) {
return false;
}
}
return true;
}
PrivacySandboxSettingsImpl::Status
PrivacySandboxSettingsImpl::GetM1FledgeAllowedStatus(
const url::Origin& top_frame_origin,
const url::Origin& auction_party) const {
Status status = GetM1PrivacySandboxApiEnabledStatus(
prefs::kPrivacySandboxM1FledgeEnabled);
if (!IsAllowed(status)) {
return status;
}
return GetSiteAccessAllowedStatus(top_frame_origin, auction_party.GetURL());
}
PrivacySandboxSettingsImpl::Status
PrivacySandboxSettingsImpl::GetFencedStorageReadEnabledStatus() const {
// User has turned on the setting to block all third party cookies.
if (cookie_settings_->ShouldBlockThirdPartyCookies() &&
!cookie_settings_->AreThirdPartyCookiesLimited()) {
return Status::kApisDisabled;
}
// This feature is default enabled when 3PCs are not blocked.
return Status::kAllowed;
}
bool PrivacySandboxSettingsImpl::IsEventReportingDestinationAttested(
const url::Origin& destination_origin,
privacy_sandbox::PrivacySandboxAttestationsGatedAPI invoking_api) const {
// Check for attestation on the event recipient's site with whichever API
// created the frame that invoked the event reporting.
Status attestation_status =
PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
net::SchemefulSite(destination_origin), invoking_api);
JoinHistogram(kIsPrivacySandboxReportingDestinationAttestedHistogram,
attestation_status);
return IsAllowed(attestation_status);
}
bool PrivacySandboxSettingsImpl::IsFledgeAllowed(
const url::Origin& top_frame_origin,
const url::Origin& auction_party,
content::InterestGroupApiOperation interest_group_api_operation,
content::RenderFrameHost* console_frame) const {
// Check for attestation on the auction party's site. The auction party is a
// variety of entities during the auction, all of which need to be attested.
Status attestation_status =
PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
net::SchemefulSite(auction_party),
PrivacySandboxAttestationsGatedAPI::kProtectedAudience);
if (!IsAllowed(attestation_status)) {
JoinFledgeHistogram(interest_group_api_operation, attestation_status);
if (console_frame) {
console_frame->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Attestation check for Protected Audience on " +
auction_party.Serialize() + " failed.");
}
return false;
}
if (interest_group_api_operation ==
content::InterestGroupApiOperation::kJoin &&
!IsFledgeJoiningAllowed(top_frame_origin)) {
JoinFledgeHistogram(interest_group_api_operation,
Status::kJoiningTopFrameBlocked);
return false;
}
Status status = GetM1FledgeAllowedStatus(top_frame_origin, auction_party);
JoinFledgeHistogram(interest_group_api_operation, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::IsSharedStorageAllowed(
const url::Origin& top_frame_origin,
const url::Origin& accessing_origin,
std::string* out_debug_message,
content::RenderFrameHost* console_frame,
bool* out_block_is_site_setting_specific) const {
// Check for attestation on the caller's site.
Status attestation_status =
PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
net::SchemefulSite(accessing_origin),
PrivacySandboxAttestationsGatedAPI::kSharedStorage);
SetOutBlockIsSiteSettingSpecificFromStatus(
attestation_status, out_block_is_site_setting_specific);
if (!IsAllowed(attestation_status)) {
JoinHistogram(kIsSharedStorageAllowedHistogram, attestation_status);
std::string error_message =
base::StrCat({"Attestation check for Shared Storage on ",
accessing_origin.Serialize(), " failed."});
if (out_debug_message) {
*out_debug_message = base::StrCat(
{error_message, "\nReturned status ",
base::NumberToString(int(attestation_status)),
"; see `PrivacySandboxSettingsImpl::Status` at ",
"https://chromium.googlesource.com/chromium/src/+/refs/heads/main/",
"components/privacy_sandbox/privacy_sandbox_settings_impl.h."});
}
if (console_frame) {
console_frame->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError, error_message);
}
return false;
}
Status status = GetPrivacySandboxAllowedStatus();
SetOutBlockIsSiteSettingSpecificFromStatus(
status, out_block_is_site_setting_specific);
if (IsAllowed(status)) {
status =
GetSiteAccessAllowedStatus(top_frame_origin, accessing_origin.GetURL());
SetOutBlockIsSiteSettingSpecificFromStatus(
status, out_block_is_site_setting_specific);
if (out_debug_message) {
*out_debug_message = base::StrCat(
{"Site access settings returned status ",
base::NumberToString(int(status)), " for accessing origin ",
accessing_origin.Serialize(), " and top-frame origin ",
top_frame_origin.Serialize(),
"; see `PrivacySandboxSettingsImpl::Status` at ",
"https://chromium.googlesource.com/chromium/src/+/refs/heads/main/",
"components/privacy_sandbox/privacy_sandbox_settings_impl.h."});
}
} else if (out_debug_message) {
*out_debug_message = base::StrCat(
{"Privacy Sandbox settings returned status ",
base::NumberToString(int(status)),
"; see `PrivacySandboxSettingsImpl::Status` at ",
"https://chromium.googlesource.com/chromium/src/+/refs/heads/main/",
"components/privacy_sandbox/privacy_sandbox_settings_impl.h."});
}
JoinHistogram(kIsSharedStorageAllowedHistogram, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::IsSharedStorageSelectURLAllowed(
const url::Origin& top_frame_origin,
const url::Origin& accessing_origin,
std::string* out_debug_message,
bool* out_block_is_site_setting_specific) const {
Status status = GetM1FledgeAllowedStatus(top_frame_origin, accessing_origin);
SetOutBlockIsSiteSettingSpecificFromStatus(
status, out_block_is_site_setting_specific);
JoinHistogram(kIsSharedStorageSelectURLAllowedHistogram, status);
if (out_debug_message) {
*out_debug_message = base::StrCat(
{"M1 measurement settings returned status ",
base::NumberToString(int(status)), " for accessing origin ",
accessing_origin.Serialize(), " and top-frame origin ",
top_frame_origin.Serialize(),
"; see `PrivacySandboxSettingsImpl::Status` at ",
"https://chromium.googlesource.com/chromium/src/+/refs/heads/main/",
"components/privacy_sandbox/privacy_sandbox_settings_impl.h."});
}
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::IsFencedStorageReadAllowed(
const url::Origin& top_frame_origin,
const url::Origin& accessing_origin,
content::RenderFrameHost* console_frame) const {
if (Status status = GetFencedStorageReadEnabledStatus(); !IsAllowed(status)) {
JoinHistogram(kIsFencedStorageReadAllowedHistogram, status);
if (console_frame) {
console_frame->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Fenced storage read is disabled because all third-party cookies are "
"blocked.");
}
return false;
}
Status attestation_status =
PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
net::SchemefulSite(accessing_origin),
PrivacySandboxAttestationsGatedAPI::kFencedStorageRead);
if (!IsAllowed(attestation_status)) {
JoinHistogram(kIsFencedStorageReadAllowedHistogram, attestation_status);
if (console_frame) {
console_frame->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
base::StrCat({"Attestation check for fenced storage read on ",
accessing_origin.Serialize(), " failed."}));
}
return false;
}
Status status = GetPrivacySandboxAllowedStatus();
if (IsAllowed(status)) {
status =
GetSiteAccessAllowedStatus(top_frame_origin, accessing_origin.GetURL());
}
JoinHistogram(kIsFencedStorageReadAllowedHistogram, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::IsPrivateAggregationAllowed(
const url::Origin& top_frame_origin,
const url::Origin& reporting_origin,
bool* out_block_is_site_setting_specific) const {
// Check for attestation on the worklet's site.
Status attestation_status =
PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
net::SchemefulSite(reporting_origin),
PrivacySandboxAttestationsGatedAPI::kPrivateAggregation);
SetOutBlockIsSiteSettingSpecificFromStatus(
attestation_status, out_block_is_site_setting_specific);
if (!IsAllowed(attestation_status)) {
JoinHistogram(kIsPrivateAggregationAllowedHistogram, attestation_status);
return false;
}
Status status =
GetM1AdMeasurementAllowedStatus(top_frame_origin, reporting_origin);
SetOutBlockIsSiteSettingSpecificFromStatus(
status, out_block_is_site_setting_specific);
JoinHistogram(kIsPrivateAggregationAllowedHistogram, status);
return IsAllowed(status);
}
bool PrivacySandboxSettingsImpl::IsPrivateAggregationDebugModeAllowed(
const url::Origin& top_frame_origin,
const url::Origin& reporting_origin) const {
if (!IsPrivateAggregationAllowed(
top_frame_origin, reporting_origin,
/*out_block_is_site_setting_specific=*/nullptr)) {
return false;
}
// If this feature is disabled, provide a top-frame origin anyway to match
// previous behavior.
std::optional<url::Origin> top_frame_origin_to_query;
if (!base::FeatureList::IsEnabled(
kPrivateAggregationDebugReportingIgnoreSiteExceptions)) {
top_frame_origin_to_query = top_frame_origin;
}
// Third party cookies must also be available for this context. An empty site
// for cookies and empty top-frame origin is provided so the context is always
// treated as a third party. That is, we ignore any top-level site cookie
// exceptions (see crbug.com/364318217).
content_settings::CookieSettingsBase::CookieSettingWithMetadata
cookie_setting_with_metadata;
if (cookie_settings_->IsFullCookieAccessAllowed(
reporting_origin.GetURL(), net::SiteForCookies(),
top_frame_origin_to_query, net::CookieSettingOverrides(),
&cookie_setting_with_metadata)) {
return true;
}
// Third-party cookie access is disabled, but we may still allow Private
// Aggregation's debug mode in this context if it was only blocked due to the
// 3PCD experiment.
return base::FeatureList::IsEnabled(
kPrivateAggregationDebugReportingCookieDeprecationTesting) &&
cookie_setting_with_metadata.BlockedByThirdPartyCookieBlocking() &&
delegate_->AreThirdPartyCookiesBlockedByCookieDeprecationExperiment();
}
void PrivacySandboxSettingsImpl::SetAllPrivacySandboxAllowedForTesting() {
pref_service_->SetBoolean(prefs::kPrivacySandboxM1FledgeEnabled, true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1TopicsEnabled, true);
pref_service_->SetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled, true);
}
void PrivacySandboxSettingsImpl::SetTopicsBlockedForTesting() {
pref_service_->SetBoolean(prefs::kPrivacySandboxM1TopicsEnabled, false);
}
bool PrivacySandboxSettingsImpl::IsPrivacySandboxRestricted() const {
return delegate_->IsPrivacySandboxRestricted();
}
bool PrivacySandboxSettingsImpl::IsPrivacySandboxCurrentlyUnrestricted() const {
return delegate_->IsPrivacySandboxCurrentlyUnrestricted();
}
bool PrivacySandboxSettingsImpl::IsSubjectToM1NoticeRestricted() const {
return delegate_->IsSubjectToM1NoticeRestricted();
}
bool PrivacySandboxSettingsImpl::IsRestrictedNoticeEnabled() const {
return delegate_->IsRestrictedNoticeEnabled();
}
void PrivacySandboxSettingsImpl::OnCookiesCleared() {
SetTopicsDataAccessibleFromNow();
}
void PrivacySandboxSettingsImpl::OnRelatedWebsiteSetsEnabledPrefChanged() {
for (auto& observer : observers_) {
observer.OnRelatedWebsiteSetsEnabledChanged(AreRelatedWebsiteSetsEnabled());
}
}
void PrivacySandboxSettingsImpl::OnCookieControlsModePrefChanged() {
if (!base::FeatureList::IsEnabled(privacy_sandbox::kAddLimit3pcsSetting)) {
return;
}
CookieControlsMode mode = static_cast<CookieControlsMode>(
pref_change_registrar_.prefs()->GetInteger(prefs::kCookieControlsMode));
if (mode == CookieControlsMode::kOff ||
mode == CookieControlsMode::kIncognitoOnly) {
return;
}
for (Observer& obs : observers_) {
obs.OnRelatedWebsiteSetsEnabledChanged(AreRelatedWebsiteSetsEnabled());
}
}
void PrivacySandboxSettingsImpl::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void PrivacySandboxSettingsImpl::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void PrivacySandboxSettingsImpl::SetDelegateForTesting(
std::unique_ptr<Delegate> delegate) {
delegate_ = std::move(delegate);
}
void PrivacySandboxSettingsImpl::SetTopicsDataAccessibleFromNow() const {
pref_service_->SetTime(prefs::kPrivacySandboxTopicsDataAccessibleSince,
base::Time::Now());
for (auto& observer : observers_) {
observer.OnTopicsDataAccessibleSinceUpdated();
}
}
PrivacySandboxSettingsImpl::Status
PrivacySandboxSettingsImpl::GetSiteAccessAllowedStatus(
const url::Origin& top_frame_origin,
const GURL& url) const {
// Relying on |host_content_settings_map_| instead of |cookie_settings_|
// allows to query whether the site associated with the |url| is allowed to
// access Site data (aka ContentSettingsType::COOKIES) without considering any
// 3P cookie blocking setting.
return content_settings::CookieSettingsBase::IsAllowed(
host_content_settings_map_->GetContentSetting(
url, top_frame_origin.GetURL(), ContentSettingsType::COOKIES))
? Status::kAllowed
: Status::kSiteDataAccessBlocked;
}
PrivacySandboxSettingsImpl::Status
PrivacySandboxSettingsImpl::GetPrivacySandboxAllowedStatus(
bool should_ignore_restriction /*=false*/) const {
if (delegate_->IsIncognitoProfile()) {
return Status::kIncognitoProfile;
}
if (IsPrivacySandboxRestricted() && !should_ignore_restriction) {
return Status::kRestricted;
}
return Status::kAllowed;
}
PrivacySandboxSettingsImpl::Status
PrivacySandboxSettingsImpl::GetM1PrivacySandboxApiEnabledStatus(
const std::string& pref_name) const {
DCHECK(pref_name == prefs::kPrivacySandboxM1TopicsEnabled ||
pref_name == prefs::kPrivacySandboxM1FledgeEnabled ||
pref_name == prefs::kPrivacySandboxM1AdMeasurementEnabled);
if (delegate_->IsCookieDeprecationExperimentEligible() &&
features::kCookieDeprecationTestingDisableAdsAPIs.Get()) {
return Status::kBlockedBy3pcdExperiment;
}
bool should_ignore_restriction =
pref_name == prefs::kPrivacySandboxM1AdMeasurementEnabled &&
IsRestrictedNoticeEnabled();
PrivacySandboxSettingsImpl::Status status =
GetPrivacySandboxAllowedStatus(should_ignore_restriction);
if (!IsAllowed(status)) {
return status;
}
// For Measurement and Relevance APIs, we explicitly do not require the
// underlying pref to be enabled if there is a local flag enabling the APIs to
// allow for local testing.
if (base::FeatureList::IsEnabled(
privacy_sandbox::kOverridePrivacySandboxSettingsLocalTesting)) {
return Status::kAllowed;
}
status = (pref_service_->GetBoolean(pref_name)) ? Status::kAllowed
: Status::kApisDisabled;
return status;
}
TpcdExperimentEligibility
PrivacySandboxSettingsImpl::GetCookieDeprecationExperimentCurrentEligibility()
const {
return delegate_->GetCookieDeprecationExperimentCurrentEligibility();
}
bool PrivacySandboxSettingsImpl::IsCookieDeprecationLabelAllowed() const {
return delegate_->IsCookieDeprecationLabelAllowed();
}
bool PrivacySandboxSettingsImpl::IsCookieDeprecationLabelAllowedForContext(
const url::Origin& top_frame_origin,
const url::Origin& context_origin) const {
if (!IsCookieDeprecationLabelAllowed()) {
return false;
}
return IsAllowed(
GetSiteAccessAllowedStatus(top_frame_origin, context_origin.GetURL()));
}
void PrivacySandboxSettingsImpl::OnBlockAllThirdPartyCookiesChanged() {
for (auto& observer : observers_) {
observer.OnRelatedWebsiteSetsEnabledChanged(AreRelatedWebsiteSetsEnabled());
}
}
bool PrivacySandboxSettingsImpl::AreRelatedWebsiteSetsEnabled() const {
if (tracking_protection_settings_->IsTrackingProtection3pcdEnabled() ||
base::FeatureList::IsEnabled(privacy_sandbox::kAddLimit3pcsSetting)) {
return cookie_settings_->AreThirdPartyCookiesLimited();
}
return pref_service_->GetBoolean(
prefs::kPrivacySandboxRelatedWebsiteSetsEnabled);
}
void PrivacySandboxSettingsImpl::SetOutBlockIsSiteSettingSpecificFromStatus(
Status status,
bool* out_block_is_site_setting_specific) const {
if (out_block_is_site_setting_specific != nullptr) {
*out_block_is_site_setting_specific =
status == Status::kSiteDataAccessBlocked;
}
}
} // namespace privacy_sandbox