blob: ca5341ab9b075fa27c5674a01471696c26e7ba0e [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_COOKIES_COOKIE_PARTITION_KEY_H_
#define NET_COOKIES_COOKIE_PARTITION_KEY_H_
#include <compare>
#include <optional>
#include <string>
#include "base/auto_reset.h"
#include "base/types/expected.h"
#include "base/types/optional_ref.h"
#include "net/base/cronet_buildflags.h"
#include "net/base/net_export.h"
#include "net/base/network_isolation_key.h"
#include "net/base/schemeful_site.h"
#include "url/gurl.h"
#if !BUILDFLAG(CRONET_BUILD)
#include "mojo/public/cpp/bindings/default_construct_tag.h"
#endif
namespace net {
class SiteForCookies;
class NET_EXPORT CookiePartitionKey {
public:
class NET_EXPORT SerializedCookiePartitionKey {
public:
const std::string& TopLevelSite() const;
bool has_cross_site_ancestor() const;
std::string GetDebugString() const;
// This constructor does not check if the values being serialized are valid.
// The caller of this function must ensure that only valid values are passed
// to this method.
SerializedCookiePartitionKey(base::PassKey<CookiePartitionKey> key,
const std::string& site,
bool has_cross_site_ancestor);
private:
std::string top_level_site_;
bool has_cross_site_ancestor_;
};
// An enumerated value representing whether any frame in the PartitionKey's
// ancestor chain (including the top-level document's site) is cross-site with
// the current frame. These values are persisted to disk. Entries should not
// be renumbered and numeric values should never be reused.
enum class AncestorChainBit {
// All frames in the ancestor chain are pairwise same-site.
kSameSite = 0,
// At least one frame in the ancestor chain is cross-site with
// the current frame.
kCrossSite = 1,
};
static AncestorChainBit BoolToAncestorChainBit(bool val);
CookiePartitionKey() = delete;
#if !BUILDFLAG(CRONET_BUILD)
explicit CookiePartitionKey(mojo::DefaultConstruct::Tag);
#endif
CookiePartitionKey(const CookiePartitionKey& other);
CookiePartitionKey(CookiePartitionKey&& other);
CookiePartitionKey& operator=(const CookiePartitionKey& other);
CookiePartitionKey& operator=(CookiePartitionKey&& other);
~CookiePartitionKey();
bool operator==(const CookiePartitionKey& other) const;
std::strong_ordering operator<=>(const CookiePartitionKey& other) const;
// Methods for serializing and deserializing a partition key to/from a string.
// This is currently used for:
// - Storing persistent partitioned cookies
// - Loading partitioned cookies into Java code
// - Sending cookie partition keys as strings in the DevTools protocol
//
// This function returns true if the partition key is not opaque and if nonce_
// is not present. We do not want to serialize cookies with opaque origins or
// nonce in their partition key to disk, because if the browser session ends
// we will not be able to attach the saved cookie to any future requests. This
// is because opaque origins' nonces are only stored in volatile memory.
//
// TODO(crbug.com/40188414) Investigate ways to persist partition keys with
// opaque origins if a browser session is restored.
[[nodiscard]] static base::expected<SerializedCookiePartitionKey, std::string>
Serialize(base::optional_ref<const CookiePartitionKey> in);
static CookiePartitionKey FromURLForTesting(
const GURL& url,
AncestorChainBit ancestor_chain_bit = AncestorChainBit::kCrossSite,
std::optional<base::UnguessableToken> nonce = std::nullopt) {
return CookiePartitionKey(SchemefulSite(url), nonce, ancestor_chain_bit);
}
// Create a partition key from a network isolation key. Partition key is
// derived from the key's top-frame site. For scripts, the request_site
// is the url of the context running the code.
static std::optional<CookiePartitionKey> FromNetworkIsolationKey(
const NetworkIsolationKey& network_isolation_key,
const SiteForCookies& site_for_cookies,
const SchemefulSite& request_site,
bool main_frame_navigation);
// Create a new CookiePartitionKey from the site of an existing
// CookiePartitionKey. This should only be used for sites of partition keys
// which were already created using Deserialize or FromNetworkIsolationKey.
static CookiePartitionKey FromWire(
const SchemefulSite& site,
AncestorChainBit ancestor_chain_bit,
std::optional<base::UnguessableToken> nonce = std::nullopt) {
return CookiePartitionKey(site, nonce, ancestor_chain_bit);
}
// Create a new CookiePartitionKey in a script running in a renderer. We do
// not trust the renderer to provide us with a cookie partition key, so we let
// the renderer use this method to indicate the cookie is partitioned but the
// key still needs to be determined.
//
// When the browser is ingesting cookie partition keys from the renderer,
// either the `from_script_` flag should be set or the cookie partition key
// should match the browser's. Otherwise the renderer may be compromised.
//
// TODO(crbug.com/40188414) Consider removing this factory method and
// `from_script_` flag when BlinkStorageKey is available in
// ServiceWorkerGlobalScope.
static CookiePartitionKey FromScript() { return CookiePartitionKey(true); }
// Create a new CookiePartitionKey from the components of a StorageKey.
// Forwards to FromWire, but unlike that method in this one the optional nonce
// argument has no default. It also checks that cookie partitioning is enabled
// before returning a valid key, which FromWire does not check.
[[nodiscard]] static std::optional<CookiePartitionKey>
FromStorageKeyComponents(
const SchemefulSite& top_level_site,
AncestorChainBit ancestor_chain_bit,
base::optional_ref<const base::UnguessableToken> nonce);
// FromStorage is a factory method which is meant for creating a new
// CookiePartitionKey using properties of a previously existing
// CookiePartitionKey that was already ingested into storage. This should NOT
// be used to create a new CookiePartitionKey that was not previously saved in
// storage.
[[nodiscard]] static base::expected<std::optional<CookiePartitionKey>,
std::string>
FromStorage(const std::string& top_level_site, bool has_cross_site_ancestor);
// This method should be used when the data provided is expected to be
// non-null but might be invalid or comes from a potentially untrustworthy
// source (such as user-supplied data).
//
// This reserves FromStorage to handle cases that can result in a null key
// (and perfectly validly, like in the case when the top_level_site is empty).
[[nodiscard]] static base::expected<CookiePartitionKey, std::string>
FromUntrustedInput(const std::string& top_level_site,
bool has_cross_site_ancestor);
const SchemefulSite& site() const { return site_; }
bool from_script() const { return from_script_; }
// Returns true if the current partition key can be serialized to a string.
// Cookie partition keys whose internal site is opaque cannot be serialized.
bool IsSerializeable() const;
// Returns true if unpartitioned cookie access is forbidden for the current
// cookie partition key.
bool ForbidsUnpartitionedCookieAccess() const { return nonce_.has_value(); }
const std::optional<base::UnguessableToken>& nonce() const { return nonce_; }
static bool HasNonce(base::optional_ref<const CookiePartitionKey> key) {
return key && key->nonce();
}
bool IsThirdParty() const {
return ancestor_chain_bit_ == AncestorChainBit::kCrossSite;
}
#if BUILDFLAG(IS_ANDROID)
// Globally disable cookie partitioning. This must be called before any
// CookiePartitionKeys are created.
// This is used to disable CHIPS in WebView, and should not be used by any
// other embedder.
static void DisablePartitioningInWebView();
// Return whether partitioning has been disabled in WebView.
// Other embedders should not use this method.
static bool IsPartitioningDisabledInWebView();
// Disable partitioning in unit tests.
[[nodiscard]]
static base::AutoReset<bool> DisablePartitioningInScopeForTesting();
#endif // BUILDFLAG(IS_ANDROID)
private:
// Used by DeserializeInternal to determine how strict the context should be
// about inconsistencies in the input.
enum class ParsingMode {
// The top_level_site string must be serialized exactly as a SchemefulSite
// would be.
// Use this when reading from storage.
kStrict = 0,
// The top_level_site string must be coercible to a SchemefulSite.
// Use this for user input.
kLoose = 1,
};
explicit CookiePartitionKey(const SchemefulSite& site,
std::optional<base::UnguessableToken> nonce,
AncestorChainBit ancestor_chain_bit);
explicit CookiePartitionKey(bool from_script);
// This method holds the deserialization logic for validating input from
// DeserializeForTesting and FromUntrustedInput which can be used to pass
// unserializable top_level_site values.
[[nodiscard]] static base::expected<CookiePartitionKey, std::string>
DeserializeInternal(
const std::string& top_level_site,
CookiePartitionKey::AncestorChainBit has_cross_site_ancestor,
CookiePartitionKey::ParsingMode parsing_mode);
AncestorChainBit GetAncestorChainBit() const { return ancestor_chain_bit_; }
#if BUILDFLAG(IS_ANDROID)
static bool g_partitioning_disabled_in_webview_;
// Used to assert that no constructors are called before partitioning is
// disabled.
static bool g_constructor_called_;
#endif // BUILDFLAG(IS_ANDROID)
SchemefulSite site_;
bool from_script_ = false;
// Having a nonce is a way to force a transient opaque `CookiePartitionKey`
// for non-opaque origins.
std::optional<base::UnguessableToken> nonce_;
AncestorChainBit ancestor_chain_bit_ = AncestorChainBit::kCrossSite;
};
// Used so that CookiePartitionKeys can be the arguments of DCHECK_EQ.
NET_EXPORT std::ostream& operator<<(std::ostream& os,
const CookiePartitionKey& cpk);
} // namespace net
#endif // NET_COOKIES_COOKIE_PARTITION_KEY_H_