| // 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 <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/ranges/algorithm.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_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/privacy_sandbox/privacy_sandbox_features.h" |
| #include "components/privacy_sandbox/privacy_sandbox_prefs.h" |
| #include "content/public/common/content_features.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/cookies/site_for_cookies.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace privacy_sandbox { |
| |
| namespace { |
| |
| 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 kIsFledgeAllowedHistogram[] = "PrivacySandbox.IsFledgeAllowed"; |
| constexpr char kIsSharedStorageAllowedHistogram[] = |
| "PrivacySandbox.IsSharedStorageAllowed"; |
| constexpr char kIsSharedStorageSelectURLAllowedHistogram[] = |
| "PrivacySandbox.IsSharedStorageSelectURLAllowed"; |
| constexpr char kIsPrivateAggregationAllowedHistogram[] = |
| "PrivacySandbox.IsPrivateAggregationAllowed"; |
| |
| bool IsCookiesClearOnExitEnabled(HostContentSettingsMap* map) { |
| return map->GetDefaultContentSetting(ContentSettingsType::COOKIES, |
| /*provider_id=*/nullptr) == |
| 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())); |
| } |
| |
| } // namespace |
| |
| // static |
| bool PrivacySandboxSettingsImpl::IsAllowed(Status status) { |
| return status == Status::kAllowed; |
| } |
| |
| // static |
| void PrivacySandboxSettingsImpl::JoinHistogram(const char* name, |
| Status status) { |
| base::UmaHistogramEnumeration(name, status); |
| } |
| |
| PrivacySandboxSettingsImpl::PrivacySandboxSettingsImpl( |
| std::unique_ptr<Delegate> delegate, |
| HostContentSettingsMap* host_content_settings_map, |
| scoped_refptr<content_settings::CookieSettings> cookie_settings, |
| PrefService* pref_service) |
| : delegate_(std::move(delegate)), |
| host_content_settings_map_(host_content_settings_map), |
| cookie_settings_(cookie_settings), |
| pref_service_(pref_service), |
| attestations_({}) { |
| DCHECK(pref_service_); |
| DCHECK(host_content_settings_map_); |
| DCHECK(cookie_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(); |
| } |
| |
| pref_change_registrar_.Init(pref_service_); |
| pref_change_registrar_.Add( |
| prefs::kPrivacySandboxFirstPartySetsEnabled, |
| base::BindRepeating( |
| &PrivacySandboxSettingsImpl::OnFirstPartySetsEnabledPrefChanged, |
| base::Unretained(this))); |
| } |
| |
| PrivacySandboxSettingsImpl::~PrivacySandboxSettingsImpl() = default; |
| |
| 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::vector<browsing_topics::Topic>& |
| PrivacySandboxSettingsImpl::GetFinchDisabledTopics() { |
| if (finch_disabled_topics_.size() > 0) { |
| return finch_disabled_topics_; |
| } |
| std::string disabled_topics_string = |
| blink::features::kBrowsingTopicsDisabledTopicsList.Get(); |
| if (disabled_topics_string.empty()) { |
| return finch_disabled_topics_; |
| } |
| std::vector<std::string> tokens = base::SplitString( |
| disabled_topics_string, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (const std::string& token : tokens) { |
| int disabled_topic_id; |
| CHECK(base::StringToInt(token, &disabled_topic_id)); |
| finch_disabled_topics_.emplace_back(disabled_topic_id); |
| } |
| return finch_disabled_topics_; |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsTopicsAllowed() const { |
| // M1 specific |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = GetM1TopicAllowedStatus(); |
| JoinHistogram(kIsTopicsAllowedHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| // Topics API calculation should be prevented if the user has blocked 3PC |
| // cookies, as there will be no context specific check. |
| const auto cookie_controls_mode = |
| static_cast<content_settings::CookieControlsMode>( |
| pref_service_->GetInteger(prefs::kCookieControlsMode)); |
| const auto default_content_setting = |
| cookie_settings_->GetDefaultCookieSetting(/*provider_id=*/nullptr); |
| |
| const bool third_party_cookies_blocked = |
| default_content_setting == ContentSetting::CONTENT_SETTING_BLOCK || |
| cookie_controls_mode == |
| content_settings::CookieControlsMode::kBlockThirdParty; |
| return IsPrivacySandboxEnabled() && !third_party_cookies_blocked; |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsTopicsAllowedForContext( |
| const url::Origin& top_frame_origin, |
| const GURL& url) const { |
| // Check for attestation on the calling context's site. |
| if (!attestations_.IsSiteAttested( |
| net::SchemefulSite(url), |
| PrivacySandboxAttestationsGatedAPI::kTopics)) { |
| JoinHistogram(kIsTopicsAllowedForContextHistogram, |
| Status::kAttestationFailed); |
| return false; |
| } |
| |
| // M1 specific |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = GetM1TopicAllowedStatus(); |
| if (IsAllowed(status)) { |
| status = GetSiteAccessAllowedStatus(top_frame_origin, url); |
| } |
| JoinHistogram(kIsTopicsAllowedForContextHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| // If the Topics API is disabled completely, it is not available in any |
| // context. |
| return IsTopicsAllowed() && |
| IsPrivacySandboxEnabledForContext(top_frame_origin, url); |
| } |
| |
| 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)); |
| } |
| } |
| |
| 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::GetM1AttributionReportingAllowedStatus( |
| 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 { |
| // M1 specific |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = GetM1PrivacySandboxApiEnabledStatus( |
| prefs::kPrivacySandboxM1AdMeasurementEnabled); |
| JoinHistogram(kIsAttributionReportingEverAllowedHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| return IsPrivacySandboxEnabled(); |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsAttributionReportingAllowed( |
| const url::Origin& top_frame_origin, |
| const url::Origin& reporting_origin) const { |
| // Check for attestation on the reporting origin. |
| if (!attestations_.IsSiteAttested( |
| net::SchemefulSite(reporting_origin), |
| PrivacySandboxAttestationsGatedAPI::kAttributionReporting)) { |
| JoinHistogram(kIsAttributionReportingAllowedHistogram, |
| Status::kAttestationFailed); |
| return false; |
| } |
| |
| // M1 specific |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = GetM1AttributionReportingAllowedStatus(top_frame_origin, |
| reporting_origin); |
| JoinHistogram(kIsAttributionReportingAllowedHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| return IsPrivacySandboxEnabledForContext(top_frame_origin, |
| reporting_origin.GetURL()); |
| } |
| |
| bool PrivacySandboxSettingsImpl::MaySendAttributionReport( |
| const url::Origin& source_origin, |
| const url::Origin& destination_origin, |
| const url::Origin& reporting_origin) const { |
| // Check for attestation on the reporting origin. |
| if (!attestations_.IsSiteAttested( |
| net::SchemefulSite(reporting_origin), |
| PrivacySandboxAttestationsGatedAPI::kAttributionReporting)) { |
| JoinHistogram(kMaySendAttributionReportHistogram, |
| Status::kAttestationFailed); |
| return false; |
| } |
| |
| // M1 specific |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = GetM1AttributionReportingAllowedStatus( |
| /*top_frame_origin=*/source_origin, |
| /*reporting_origin=*/reporting_origin); |
| if (IsAllowed(status)) { |
| status = GetM1AttributionReportingAllowedStatus( |
| /*top_frame_origin=*/destination_origin, |
| /*reporting_origin=*/reporting_origin); |
| } |
| JoinHistogram(kMaySendAttributionReportHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| // The |reporting_origin| needs to have been accessible in both source |
| // and trigger contexts. These are both checked when they occur, but |
| // user settings may have changed between then and when the attribution report |
| // is sent. |
| return IsPrivacySandboxEnabledForContext(/*top_frame_origin=*/source_origin, |
| reporting_origin.GetURL()) && |
| IsPrivacySandboxEnabledForContext( |
| /*top_frame_origin=*/destination_origin, |
| reporting_origin.GetURL()); |
| } |
| |
| 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"; |
| return; |
| } |
| |
| 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) { |
| absl::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 (base::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()); |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsFledgeAllowed( |
| const url::Origin& top_frame_origin, |
| const url::Origin& auction_party) 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. |
| if (!attestations_.IsSiteAttested( |
| net::SchemefulSite(auction_party), |
| PrivacySandboxAttestationsGatedAPI::kProtectedAudience)) { |
| JoinHistogram(kIsFledgeAllowedHistogram, Status::kAttestationFailed); |
| return false; |
| } |
| |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = GetM1FledgeAllowedStatus(top_frame_origin, auction_party); |
| JoinHistogram(kIsFledgeAllowedHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| return IsPrivacySandboxEnabledForContext(top_frame_origin, |
| auction_party.GetURL()); |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsSharedStorageAllowed( |
| const url::Origin& top_frame_origin, |
| const url::Origin& accessing_origin) const { |
| // Check for attestation on the caller's site. |
| if (!attestations_.IsSiteAttested( |
| net::SchemefulSite(accessing_origin), |
| PrivacySandboxAttestationsGatedAPI::kSharedStorage)) { |
| JoinHistogram(kIsSharedStorageAllowedHistogram, Status::kAttestationFailed); |
| return false; |
| } |
| |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = GetPrivacySandboxAllowedStatus(); |
| if (IsAllowed(status)) { |
| status = GetSiteAccessAllowedStatus(top_frame_origin, |
| accessing_origin.GetURL()); |
| } |
| JoinHistogram(kIsSharedStorageAllowedHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| // Ensures that Shared Storage is only allowed if both Privacy Sandbox is |
| // enabled and full cookie access is enabled for this context. |
| return IsPrivacySandboxEnabledForContext(top_frame_origin, |
| accessing_origin.GetURL()); |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsSharedStorageSelectURLAllowed( |
| const url::Origin& top_frame_origin, |
| const url::Origin& accessing_origin) const { |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = |
| GetM1FledgeAllowedStatus(top_frame_origin, accessing_origin); |
| JoinHistogram(kIsSharedStorageSelectURLAllowedHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| return IsSharedStorageAllowed(top_frame_origin, accessing_origin); |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsPrivateAggregationAllowed( |
| const url::Origin& top_frame_origin, |
| const url::Origin& reporting_origin) const { |
| // Check for attestation on the worklet's site. |
| if (!attestations_.IsSiteAttested( |
| net::SchemefulSite(reporting_origin), |
| PrivacySandboxAttestationsGatedAPI::kPrivateAggregation)) { |
| JoinHistogram(kIsPrivateAggregationAllowedHistogram, |
| Status::kAttestationFailed); |
| return false; |
| } |
| |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| Status status = GetM1AttributionReportingAllowedStatus(top_frame_origin, |
| reporting_origin); |
| JoinHistogram(kIsPrivateAggregationAllowedHistogram, status); |
| return IsAllowed(status); |
| } |
| |
| return IsPrivacySandboxEnabledForContext(top_frame_origin, |
| reporting_origin.GetURL()); |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsPrivacySandboxEnabled() const { |
| PrivacySandboxSettingsImpl::Status status = GetPrivacySandboxAllowedStatus(); |
| if (!IsAllowed(status)) { |
| return false; |
| } |
| |
| // 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 true; |
| } |
| |
| return pref_service_->GetBoolean(prefs::kPrivacySandboxApisEnabledV2); |
| } |
| |
| void PrivacySandboxSettingsImpl::SetAllPrivacySandboxAllowedForTesting() { |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| pref_service_->SetBoolean(prefs::kPrivacySandboxM1FledgeEnabled, true); |
| pref_service_->SetBoolean(prefs::kPrivacySandboxM1TopicsEnabled, true); |
| pref_service_->SetBoolean(prefs::kPrivacySandboxM1AdMeasurementEnabled, |
| true); |
| return; |
| } |
| |
| pref_service_->SetBoolean(prefs::kPrivacySandboxApisEnabledV2, true); |
| } |
| |
| void PrivacySandboxSettingsImpl::SetTopicsBlockedForTesting() { |
| if (base::FeatureList::IsEnabled(privacy_sandbox::kPrivacySandboxSettings4)) { |
| pref_service_->SetBoolean(prefs::kPrivacySandboxM1TopicsEnabled, false); |
| return; |
| } |
| |
| pref_service_->SetBoolean(prefs::kPrivacySandboxApisEnabledV2, false); |
| } |
| |
| void PrivacySandboxSettingsImpl::SetPrivacySandboxEnabled(bool enabled) { |
| pref_service_->SetBoolean(prefs::kPrivacySandboxApisEnabledV2, enabled); |
| } |
| |
| 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 privacy_sandbox::kPrivacySandboxSettings4RestrictedNotice.Get(); |
| } |
| |
| void PrivacySandboxSettingsImpl::OnCookiesCleared() { |
| SetTopicsDataAccessibleFromNow(); |
| } |
| |
| void PrivacySandboxSettingsImpl::OnFirstPartySetsEnabledPrefChanged() { |
| if (!base::FeatureList::IsEnabled(features::kFirstPartySets)) { |
| return; |
| } |
| |
| for (auto& observer : observers_) { |
| observer.OnFirstPartySetsEnabledChanged( |
| pref_service_->GetBoolean(prefs::kPrivacySandboxFirstPartySetsEnabled)); |
| } |
| } |
| |
| 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::SetPrivacySandboxAttestationsMapForTesting( |
| const PrivacySandboxAttestationsMap& attestations_map) { |
| attestations_ = PrivacySandboxAttestations(attestations_map); |
| } |
| |
| void PrivacySandboxSettingsImpl::AddPrivacySandboxAttestationOverride( |
| const GURL& url) { |
| attestations_.AddOverride(net::SchemefulSite(url)); |
| } |
| |
| const std::vector<net::SchemefulSite> |
| PrivacySandboxSettingsImpl::GetAttestationOverridesForTesting() const { |
| return attestations_.GetOverridesForTesting(); // IN-TEST |
| } |
| |
| bool PrivacySandboxSettingsImpl::IsPrivacySandboxEnabledForContext( |
| const absl::optional<url::Origin>& top_frame_origin, |
| const GURL& url) const { |
| if (!IsPrivacySandboxEnabled()) { |
| return false; |
| } |
| |
| // 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. |
| return cookie_settings_->IsFullCookieAccessAllowed( |
| url, net::SiteForCookies(), top_frame_origin, |
| net::CookieSettingOverrides()); |
| } |
| |
| 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() const { |
| if (delegate_->IsIncognitoProfile()) { |
| return Status::kIncognitoProfile; |
| } |
| |
| if (IsPrivacySandboxRestricted()) { |
| 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); |
| |
| PrivacySandboxSettingsImpl::Status status = GetPrivacySandboxAllowedStatus(); |
| 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; |
| } |
| |
| } // namespace privacy_sandbox |