910e62b5创建于 1月15日历史提交
// 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) {
  if (base::ShouldRecordSubsampledMetric(0.01)) {
    UMA_HISTOGRAM_ENUMERATION(
        "API.EffectiveStorageAccess.AllowedByStorageAccessType.Subsampled",
        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