// Copyright 2024 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_DEVICE_BOUND_SESSIONS_COOKIE_CRAVING_H_
#define NET_DEVICE_BOUND_SESSIONS_COOKIE_CRAVING_H_
#include <optional>
#include <string>
#include "base/time/time.h"
#include "net/base/net_export.h"
#include "net/cookies/cookie_base.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_partition_key.h"
namespace net {
class CanonicalCookie;
}
namespace net::device_bound_sessions {
namespace proto {
class CookieCraving;
}
// This class represents the need for a certain cookie to be present. It is not
// a cookie itself, but rather represents a requirement which can be satisfied
// by a real cookie (i.e. a CanonicalCookie). Each CookieCraving is specified by
// and associated with a DBSC (Device Bound Session Credentials) session.
//
// In general, CookieCraving behavior is intended to be as close as possible to
// CanonicalCookie, especially the inclusion logic, since they need to be
// matched up. However, some notable differences include:
//
// CookieCraving does not have a value field, i.e. they only have a name (and
// other attributes). The name can be the empty string. The name of a cookie is
// needed to identify it, but the value of a cookie is not relevant to its
// inclusion or exclusion, so CookieCraving omits it.
//
// CookieCraving does not have an expiry date. The expiry date of a
// CanonicalCookie often depends upon the creation time (if it is set via
// Max-Age), and a DBSC session config is not necessarily created at the same
// time as its corresponding Set-Cookie header, so we cannot guarantee that
// they'd match. DBSC also does not require a specific expiry date for the
// cookies whose presence it guarantees.
//
// CookieCraving does not implement lax-allow-unsafe behavior (it does not
// set a non-zero age threshold for it). The default CanonicalCookie
// lax-allow-unsafe behavior is problematic because it can result in two
// identical set-cookie lines (set from the same URL) exhibiting different
// inclusion results, if they happen to be on opposite sides of the
// lax-allow-unsafe age threshold. By not implementing lax-allow-unsafe,
// CookieCraving may sometimes be excluded even when a corresponding
// CanonicalCookie would be included for being under its lax-allow-unsafe age
// threshold. This means that servers deploying DBSC with SameSite-unspecified
// cookies SHOULD NOT rely on the presence of SameSite-unspecified cookies
// within 2 minutes of their creation time on cross-site POST and other unsafe
// request types, as DBSC cannot make any such guarantee.
class NET_EXPORT CookieCraving : public CookieBase {
public:
// Creates a new CookieCraving in the context of `url`, given a `name` and
// associated cookie `attributes`. (Note that CookieCravings do not have a
// "value".) `url` must be valid. `creation_time` may not be null. May return
// nullopt if an attribute value is invalid. If a CookieCraving is returned,
// it will satisfy IsValid(). If there is leading or trailing whitespace in
// `name`, it will get trimmed.
//
// `cookie_partition_key` only needs to be present if the attributes contain
// the Partitioned attribute. std::nullopt indicates an unpartitioned
// CookieCraving will be created. If there is a partition key but the
// attributes do not specify Partitioned, the resulting CookieCraving will be
// unpartitioned. If the partition_key is nullopt, the CookieCraving will
// always be unpartitioned even if the attributes specify Partitioned.
//
// SameSite and HttpOnly related parameters are not checked here,
// so creation of CookieCravings with e.g. SameSite=Strict from a cross-site
// context is allowed. Create() also does not check whether `url` has a secure
// scheme if attempting to create a Secure cookie. The Secure, SameSite, and
// HttpOnly related parameters should be checked when deciding CookieCraving
// inclusion for a given request/context.
//
// In general this is intended to closely mirror CanonicalCookie::Create.
// However, there are some simplifying assumptions made*, and metrics are not
// (currently) logged so as to not interfere with CanonicalCookie metrics.
// There is also no (current) need for a CookieInclusionStatus to be returned.
//
// * Simplifying assumptions (differing from CanonicalCookie):
// - The Domain() member of a CookieCraving is required to be non-empty,
// which CanonicalCookie does not require.
// - Cookie name prefixes (__Host- and __Secure-) are always checked
// case-insensitively, unlike CanonicalCookie which reads a Feature value
// to decide whether to check insensitively.
// - CanonicalCookie allows non-cryptographic URLs to create a cookie with a
// secure source_scheme, if that cookie was Secure, on the basis that that
// URL might be trustworthy when checked later. CookieCraving does not
// allow this.
static std::optional<CookieCraving> Create(
const GURL& url,
const std::string& name,
const std::string& attributes,
base::Time creation_time,
std::optional<CookiePartitionKey> cookie_partition_key);
CookieCraving(const CookieCraving& other);
CookieCraving(CookieCraving&& other);
CookieCraving& operator=(const CookieCraving& other);
CookieCraving& operator=(CookieCraving&& other);
~CookieCraving() override;
// Returns whether all CookieCraving fields are consistent, in canonical form,
// etc. (Mostly analogous to CanonicalCookie::IsCanonical, except without
// checks for access time.) Essentially, if this returns true, then this
// CookieCraving instance could have been created by Create().
// Other public methods of this class may not be called if IsValid() is false.
bool IsValid() const;
// Returns whether the given "real" cookie satisfies this CookieCraving, in
// the sense that DBSC will consider the required cookie present.
// The provided CanonicalCookie must be canonical.
bool IsSatisfiedBy(const CanonicalCookie& canonical_cookie) const;
std::string DebugString() const;
bool IsEqualForTesting(const CookieCraving& other) const;
// May return an invalid instance.
static CookieCraving CreateUnsafeForTesting(
std::string name,
std::string domain,
std::string path,
base::Time creation,
bool secure,
bool httponly,
CookieSameSite same_site,
std::optional<CookiePartitionKey> partition_key,
CookieSourceScheme source_scheme,
int source_port);
// Returns a protobuf object. May only be called for
// a valid CookieCraving object.
proto::CookieCraving ToProto() const;
// Creates a CookieCraving object from a protobuf
// object. If the protobuf contents are invalid,
// a std::nullopt is returned.
static std::optional<CookieCraving> CreateFromProto(
const proto::CookieCraving& proto);
private:
CookieCraving();
// Prefer Create() over this constructor. This may return non-valid instances.
CookieCraving(std::string name,
std::string domain,
std::string path,
base::Time creation,
bool secure,
bool httponly,
CookieSameSite same_site,
std::optional<CookiePartitionKey> partition_key,
CookieSourceScheme source_scheme,
int source_port);
};
// Outputs a debug string, e.g. for more helpful test failure messages.
NET_EXPORT std::ostream& operator<<(std::ostream& os, const CookieCraving& cc);
} // namespace net::device_bound_sessions
#endif // NET_DEVICE_BOUND_SESSIONS_COOKIE_CRAVING_H_