| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/network/cookie_settings.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <iterator> |
| #include <memory> |
| |
| #include "base/containers/contains.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/not_fatal_until.h" |
| #include "base/rand_util.h" |
| #include "base/strings/to_string.h" |
| #include "base/types/optional_ref.h" |
| #include "base/types/optional_util.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/content_settings_pattern.h" |
| #include "components/content_settings/core/common/content_settings_rules.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/content_settings/core/common/content_settings_utils.h" |
| #include "components/content_settings/core/common/cookie_settings_base.h" |
| #include "components/content_settings/core/common/features.h" |
| #include "components/content_settings/core/common/host_indexed_content_settings.h" |
| #include "net/base/network_delegate.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "net/cookies/cookie_constants.h" |
| #include "net/cookies/cookie_inclusion_status.h" |
| #include "net/cookies/cookie_setting_override.h" |
| #include "net/cookies/cookie_util.h" |
| #include "net/cookies/static_cookie_policy.h" |
| #include "net/first_party_sets/first_party_set_metadata.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/tpcd/metadata/manager.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace network { |
| namespace { |
| |
| bool ShouldApply3pcdRelatedReasons(const net::CanonicalCookie& cookie) { |
| return cookie.SameSite() == net::CookieSameSite::NO_RESTRICTION && |
| !cookie.IsPartitioned(); |
| } |
| |
| bool IsValidType(ContentSettingsType type) { |
| // ContentSettingsType::TPCD_METADATA_GRANTS settings are managed by the |
| // `network::tpcd::metadata::Manager` and are considered valid ContentSettings |
| // for CookieSettings. |
| if (type == ContentSettingsType::TPCD_METADATA_GRANTS) { |
| return true; |
| } |
| return CookieSettings::GetContentSettingsTypes().contains(type); |
| } |
| |
| void RecordAllowedByStorageAccessType( |
| CookieSettings::AllowedByStorageAccessType value) { |
| base::UmaHistogramEnumeration( |
| "API.EffectiveStorageAccess.AllowedByStorageAccessType", value); |
| } |
| |
| net::CookieInclusionStatus::ExemptionReason GetExemptionReason( |
| CookieSettings::ThirdPartyCookieAllowMechanism allow_mechanism) { |
| using AllowMechanism = CookieSettings::ThirdPartyCookieAllowMechanism; |
| using ExemptionReason = net::CookieInclusionStatus::ExemptionReason; |
| switch (allow_mechanism) { |
| case AllowMechanism::kAllowByExplicitSetting: |
| return ExemptionReason::kUserSetting; |
| case AllowMechanism::kAllowBy3PCDHeuristics: |
| return ExemptionReason::k3PCDHeuristics; |
| case AllowMechanism::kAllowBy3PCDMetadataSourceUnspecified: |
| case AllowMechanism::kAllowBy3PCDMetadataSourceTest: |
| case AllowMechanism::kAllowBy3PCDMetadataSource1pDt: |
| case AllowMechanism::kAllowBy3PCDMetadataSource3pDt: |
| case AllowMechanism::kAllowBy3PCDMetadataSourceDogFood: |
| case AllowMechanism::kAllowBy3PCDMetadataSourceCriticalSector: |
| case AllowMechanism::kAllowBy3PCDMetadataSourceCuj: |
| case AllowMechanism::kAllowBy3PCDMetadataSourceGovEduTld: |
| return ExemptionReason::k3PCDMetadata; |
| case AllowMechanism::kAllowBy3PCD: |
| return ExemptionReason::k3PCDDeprecationTrial; |
| case AllowMechanism::kAllowByTopLevel3PCD: |
| return ExemptionReason::kTopLevel3PCDDeprecationTrial; |
| case AllowMechanism::kAllowByGlobalSetting: |
| case AllowMechanism::kAllowByEnterprisePolicyCookieAllowedForUrls: |
| return ExemptionReason::kEnterprisePolicy; |
| case AllowMechanism::kAllowByStorageAccess: |
| return ExemptionReason::kStorageAccess; |
| case AllowMechanism::kAllowByTopLevelStorageAccess: |
| return ExemptionReason::kTopLevelStorageAccess; |
| case AllowMechanism::kNone: |
| return ExemptionReason::kNone; |
| case AllowMechanism::kAllowByScheme: |
| return ExemptionReason::kScheme; |
| case AllowMechanism::kAllowBySandboxValue: |
| return ExemptionReason::kSameSiteNoneCookiesInSandbox; |
| } |
| } |
| |
| bool IsOriginOpaqueHttpOrHttps( |
| base::optional_ref<const url::Origin> top_frame_origin) { |
| if (!top_frame_origin) { |
| return false; |
| } |
| if (!top_frame_origin->opaque()) { |
| return false; |
| } |
| const GURL url = |
| top_frame_origin->GetTupleOrPrecursorTupleIfOpaque().GetURL(); |
| return url.SchemeIsHTTPOrHTTPS(); |
| } |
| |
| } // namespace |
| |
| // static |
| bool CookieSettings::IsCookieAllowed(const net::CanonicalCookie& cookie, |
| const CookieSettingWithMetadata& setting) { |
| return IsAllowed(setting.cookie_setting()) || |
| (cookie.IsPartitioned() && setting.allow_partitioned_cookies()); |
| } |
| |
| // static |
| net::NetworkDelegate::PrivacySetting CookieSettings::PrivacySetting( |
| const CookieSettingWithMetadata& setting) { |
| if (IsAllowed(setting.cookie_setting())) { |
| return net::NetworkDelegate::PrivacySetting::kStateAllowed; |
| } |
| |
| if (setting.allow_partitioned_cookies()) { |
| return net::NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly; |
| } |
| |
| return net::NetworkDelegate::PrivacySetting::kStateDisallowed; |
| } |
| |
| CookieSettings::CookieSettings() { |
| // Initialize content_settings_ until we receive data. |
| for (auto type : GetContentSettingsTypes()) { |
| set_content_settings(type, {}); |
| } |
| } |
| |
| CookieSettings::~CookieSettings() = default; |
| |
| void CookieSettings::set_content_settings( |
| ContentSettingsType type, |
| const ContentSettingsForOneType& settings) { |
| CHECK_NE(type, ContentSettingsType::TPCD_METADATA_GRANTS) |
| << "TPCD Metadata exceptions are managed by the " |
| "`network::tpcd::metadata::Manager`."; |
| CHECK(IsValidType(type)) << static_cast<int>(type); |
| |
| content_settings_[type] = |
| content_settings::HostIndexedContentSettings::Create(settings); |
| |
| if (type == ContentSettingsType::COOKIES) { |
| // Cookies use allow-by-default settings, so ensure the default is set |
| // appropriately. |
| if (settings.empty() || |
| settings.back().primary_pattern != ContentSettingsPattern::Wildcard() || |
| settings.back().secondary_pattern != |
| ContentSettingsPattern::Wildcard()) { |
| auto& index = content_settings_[type].emplace_back( |
| content_settings::ProviderType::kDefaultProvider, false); |
| index.SetValue(ContentSettingsPattern::Wildcard(), |
| ContentSettingsPattern::Wildcard(), |
| base::Value(CONTENT_SETTING_ALLOW), /*metadata=*/{}); |
| } |
| } |
| } |
| |
| DeleteCookiePredicate CookieSettings::CreateDeleteCookieOnExitPredicate() |
| const { |
| if (!HasSessionOnlyOrigins()) { |
| return DeleteCookiePredicate(); |
| } |
| ContentSettingsForOneType settings; |
| // TODO(b/316530672): Ideally, clear on exit would work with the index |
| // directly to benefit from faster lookup times instead of iterating over |
| // a vector of content settings. |
| for (const auto& index : |
| GetHostIndexedContentSettings(ContentSettingsType::COOKIES)) { |
| for (const auto& entry : index) { |
| settings.emplace_back( |
| entry.first.primary_pattern, entry.first.secondary_pattern, |
| entry.second.value.Clone(), index.source(), *index.off_the_record(), |
| entry.second.metadata.Clone()); |
| } |
| } |
| |
| return base::BindRepeating(&CookieSettings::ShouldDeleteCookieOnExit, |
| base::Unretained(this), std::move(settings)); |
| } |
| |
| bool CookieSettings::ShouldIgnoreSameSiteRestrictions( |
| const GURL& url, |
| const net::SiteForCookies& site_for_cookies) const { |
| return base::Contains(secure_origin_cookies_allowed_schemes_, |
| site_for_cookies.scheme()) && |
| url.SchemeIsCryptographic(); |
| } |
| |
| bool CookieSettings::IsCookieAccessible( |
| const net::CanonicalCookie& cookie, |
| const GURL& url, |
| const net::SiteForCookies& site_for_cookies, |
| base::optional_ref<const url::Origin> top_frame_origin, |
| const net::FirstPartySetMetadata& first_party_set_metadata, |
| net::CookieSettingOverrides overrides, |
| net::CookieInclusionStatus* cookie_inclusion_status) const { |
| const CookieSettingWithMetadata setting_with_metadata = |
| GetCookieSettingWithMetadata(url, site_for_cookies, |
| top_frame_origin.as_ptr(), overrides); |
| bool allowed = IsCookieAllowed(cookie, setting_with_metadata); |
| if (cookie_inclusion_status) { |
| AugmentInclusionStatus(cookie, top_frame_origin, setting_with_metadata, |
| first_party_set_metadata, overrides, |
| *cookie_inclusion_status); |
| } |
| |
| RecordAllowedByStorageAccessType( |
| setting_with_metadata.allowed_by_storage_access_type()); |
| |
| return allowed; |
| } |
| |
| // Returns whether third-party cookie blocking should be bypassed (i.e. always |
| // allow the cookie regardless of cookie content settings and third-party |
| // cookie blocking settings. |
| // This just checks the scheme of the |url| and |site_for_cookies|: |
| // - Allow cookies if the |site_for_cookies| is a chrome:// scheme URL, and |
| // the |url| has a secure scheme. |
| // - Allow cookies if the |site_for_cookies| and the |url| match in scheme |
| // and both have the Chrome extensions scheme. |
| bool CookieSettings::ShouldAlwaysAllowCookies( |
| const GURL& url, |
| const GURL& first_party_url) const { |
| return (base::Contains(secure_origin_cookies_allowed_schemes_, |
| first_party_url.scheme()) && |
| url.SchemeIsCryptographic()) || |
| (base::Contains(matching_scheme_cookies_allowed_schemes_, |
| url.scheme()) && |
| url.SchemeIs(first_party_url.scheme())); |
| } |
| |
| net::NetworkDelegate::PrivacySetting CookieSettings::IsPrivacyModeEnabled( |
| const GURL& url, |
| const net::SiteForCookies& site_for_cookies, |
| base::optional_ref<const url::Origin> top_frame_origin, |
| net::CookieSettingOverrides overrides) const { |
| return PrivacySetting(GetCookieSettingWithMetadata( |
| url, site_for_cookies, top_frame_origin.as_ptr(), overrides)); |
| } |
| |
| CookieSettings::CookieSettingWithMetadata |
| CookieSettings::GetCookieSettingWithMetadata( |
| const GURL& url, |
| const net::SiteForCookies& site_for_cookies, |
| base::optional_ref<const url::Origin> top_frame_origin, |
| net::CookieSettingOverrides overrides) const { |
| return GetCookieSettingInternal( |
| url, site_for_cookies, |
| FirstPartyURLForMetadata(site_for_cookies, top_frame_origin), overrides, |
| nullptr); |
| } |
| |
| // static |
| GURL CookieSettings::FirstPartyURLForMetadata( |
| const net::SiteForCookies& site_for_cookies, |
| base::optional_ref<const url::Origin> top_frame_origin) { |
| return IsOriginOpaqueHttpOrHttps(top_frame_origin) |
| ? top_frame_origin->GetTupleOrPrecursorTupleIfOpaque().GetURL() |
| : GetFirstPartyURL(site_for_cookies, top_frame_origin.as_ptr()); |
| } |
| |
| bool CookieSettings::AnnotateAndMoveUserBlockedCookies( |
| const GURL& url, |
| const net::SiteForCookies& site_for_cookies, |
| base::optional_ref<const url::Origin> top_frame_origin, |
| const net::FirstPartySetMetadata& first_party_set_metadata, |
| net::CookieSettingOverrides overrides, |
| net::CookieAccessResultList& maybe_included_cookies, |
| net::CookieAccessResultList& excluded_cookies) const { |
| const CookieSettingWithMetadata setting_with_metadata = |
| GetCookieSettingWithMetadata(url, site_for_cookies, top_frame_origin, |
| overrides); |
| // Add `WARN_THIRD_PARTY_PHASEOUT` `WarningReason` for allowed cookies |
| // that meets the conditions and add the `ExclusionReason` for cookies |
| // that ought to be blocked. |
| for (net::CookieWithAccessResult& cookie : maybe_included_cookies) { |
| AugmentInclusionStatus(cookie.cookie, top_frame_origin, |
| setting_with_metadata, first_party_set_metadata, |
| overrides, cookie.access_result.status); |
| } |
| for (net::CookieWithAccessResult& cookie : excluded_cookies) { |
| AugmentInclusionStatus(cookie.cookie, top_frame_origin, |
| setting_with_metadata, first_party_set_metadata, |
| overrides, cookie.access_result.status); |
| } |
| const auto to_be_moved = std::ranges::stable_partition( |
| maybe_included_cookies, [](const net::CookieWithAccessResult& cookie) { |
| return cookie.access_result.status.IsInclude(); |
| }); |
| excluded_cookies.insert(excluded_cookies.end(), |
| std::make_move_iterator(to_be_moved.begin()), |
| std::make_move_iterator(to_be_moved.end())); |
| maybe_included_cookies.erase(to_be_moved.begin(), to_be_moved.end()); |
| |
| net::cookie_util::DCheckIncludedAndExcludedCookieLists(maybe_included_cookies, |
| excluded_cookies); |
| |
| RecordAllowedByStorageAccessType( |
| setting_with_metadata.allowed_by_storage_access_type()); |
| |
| return IsAllowed(setting_with_metadata.cookie_setting()) || |
| !maybe_included_cookies.empty(); |
| } |
| |
| bool CookieSettings::HasSessionOnlyOrigins() const { |
| for (const auto& index : |
| GetHostIndexedContentSettings(ContentSettingsType::COOKIES)) { |
| for (const auto& entry : index) { |
| if (content_settings::ValueToContentSetting(entry.second.value) == |
| CONTENT_SETTING_SESSION_ONLY) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| const std::vector<content_settings::HostIndexedContentSettings>& |
| CookieSettings::GetHostIndexedContentSettings(ContentSettingsType type) const { |
| CHECK(IsValidType(type)) << static_cast<int>(type); |
| return content_settings_.at(type); |
| } |
| |
| ContentSetting CookieSettings::GetContentSetting( |
| const GURL& primary_url, |
| const GURL& secondary_url, |
| ContentSettingsType content_type, |
| content_settings::SettingInfo* info) const { |
| SCOPED_UMA_HISTOGRAM_TIMER_MICROS_SUBSAMPLED( |
| "ContentSettings.GetContentSetting.Network.Duration", |
| base::ShouldRecordSubsampledMetric(0.001)); |
| |
| if (content_type == ContentSettingsType::TPCD_METADATA_GRANTS) { |
| if (tpcd_metadata_manager_) { |
| return tpcd_metadata_manager_->GetContentSetting(primary_url, |
| secondary_url, info); |
| } |
| } else { |
| for (const auto& index : GetHostIndexedContentSettings(content_type)) { |
| const content_settings::RuleEntry* result = |
| index.Find(primary_url, secondary_url); |
| if (result) { |
| if (info) { |
| info->SetAttributes(*result); |
| info->source = content_settings::GetSettingSourceFromProviderType( |
| index.source()); |
| } |
| return content_settings::ValueToContentSetting(result->second.value); |
| } |
| } |
| } |
| |
| if (info) { |
| info->primary_pattern = ContentSettingsPattern::Wildcard(); |
| info->secondary_pattern = ContentSettingsPattern::Wildcard(); |
| info->metadata = {}; |
| } |
| return CONTENT_SETTING_BLOCK; |
| } |
| |
| bool CookieSettings::IsThirdPartyCookiesAllowedScheme( |
| std::string_view scheme) const { |
| return third_party_cookies_allowed_schemes_.contains(scheme); |
| } |
| |
| bool CookieSettings::ShouldBlockThirdPartyCookies( |
| base::optional_ref<const url::Origin> top_frame_origin, |
| net::CookieSettingOverrides overrides) const { |
| if (std::optional<bool> modifier_decision = |
| MaybeBlockThirdPartyCookiesPerModifiers(top_frame_origin, |
| overrides)) { |
| return modifier_decision.value(); |
| } |
| return block_third_party_cookies_ || |
| net::cookie_util::IsForceThirdPartyCookieBlockingEnabled() || |
| tracking_protection_enabled_for_3pcd_; |
| } |
| |
| bool CookieSettings::IsThirdPartyPhaseoutEnabled( |
| base::optional_ref<const url::Origin> top_frame_origin, |
| net::CookieSettingOverrides overrides) const { |
| switch (GetModifierMode(top_frame_origin, overrides)) { |
| case ModifierMode::kUndefined: |
| return net::cookie_util::IsForceThirdPartyCookieBlockingEnabled() || |
| tracking_protection_enabled_for_3pcd_; |
| case ModifierMode::kPhaseout: |
| return true; |
| case ModifierMode::kAllow: |
| case ModifierMode::kBlock: |
| return false; |
| } |
| } |
| |
| bool CookieSettings::MitigationsEnabledFor3pcd() const { |
| return mitigations_enabled_for_3pcd_; |
| } |
| |
| void CookieSettings::AugmentInclusionStatus( |
| const net::CanonicalCookie& cookie, |
| base::optional_ref<const url::Origin> top_frame_origin, |
| const CookieSettings::CookieSettingWithMetadata& setting_with_metadata, |
| const net::FirstPartySetMetadata& first_party_set_metadata, |
| net::CookieSettingOverrides overrides, |
| net::CookieInclusionStatus& out_status) const { |
| const bool could_be_affected_by_tpc_phaseout = |
| !setting_with_metadata.is_explicit_setting() && |
| setting_with_metadata.is_third_party_request(); |
| |
| if (IsCookieAllowed(cookie, setting_with_metadata)) { |
| if (!ShouldApply3pcdRelatedReasons(cookie)) { |
| return; |
| } |
| const ThirdPartyCookieAllowMechanism allow_mechanism( |
| setting_with_metadata.third_party_cookie_allow_mechanism()); |
| const bool has_exemption = |
| allow_mechanism != ThirdPartyCookieAllowMechanism::kNone; |
| if (ShouldBlockThirdPartyCookies(top_frame_origin, overrides) && |
| setting_with_metadata.is_third_party_request()) { |
| CHECK(has_exemption); |
| out_status.MaybeSetExemptionReason(GetExemptionReason(allow_mechanism)); |
| } else if (could_be_affected_by_tpc_phaseout) { |
| out_status.AddWarningReason( |
| net::CookieInclusionStatus::WarningReason::WARN_THIRD_PARTY_PHASEOUT); |
| } |
| return; |
| } |
| |
| // The cookie is blocked. |
| |
| if (setting_with_metadata.allow_partitioned_cookies() && |
| could_be_affected_by_tpc_phaseout && |
| IsThirdPartyPhaseoutEnabled(top_frame_origin, overrides)) { |
| // This cookie is blocked due to 3PCD. |
| if (!ShouldApply3pcdRelatedReasons(cookie)) { |
| return; |
| } |
| out_status.AddExclusionReason(net::CookieInclusionStatus::ExclusionReason:: |
| EXCLUDE_THIRD_PARTY_PHASEOUT); |
| |
| if (first_party_set_metadata.AreSitesInSameFirstPartySet()) { |
| out_status.AddExclusionReason( |
| net::CookieInclusionStatus::ExclusionReason:: |
| EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET); |
| } |
| return; |
| } |
| |
| // The cookie is blocked, but not by 3PCD. |
| out_status.AddExclusionReason( |
| net::CookieInclusionStatus::ExclusionReason::EXCLUDE_USER_PREFERENCES); |
| } |
| |
| bool CookieSettings::ShouldAlwaysAllowCookiesForTesting( |
| const GURL& url, |
| const GURL& first_party_url) const { |
| return ShouldAlwaysAllowCookies(url, first_party_url); |
| } |
| |
| } // namespace network |