blob: ab34588e8f6c940d455bfe0c3f946371dbf506f9 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/base/isolation_info.h"
#include "base/check_op.h"
#include "base/unguessable_token.h"
#include "net/base/features.h"
#include "net/base/isolation_info.pb.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace net {
namespace {
// Checks that |origin| is consistent with |site_for_cookies|.
bool ValidateSameSite(const url::Origin& origin,
const SiteForCookies& site_for_cookies) {
// If not sending SameSite cookies, or sending them for a non-scheme, consider
// all origins consistent. Note that SiteForCookies should never be created
// for websocket schemes for valid navigations, since frames can't be
// navigated to those schemes.
if (site_for_cookies.IsNull() ||
(site_for_cookies.scheme() != url::kHttpScheme &&
site_for_cookies.scheme() != url::kHttpsScheme)) {
return true;
}
// Shouldn't send cookies for opaque origins.
if (origin.opaque())
return false;
// TODO(https://crbug.com/1060631): GetURL() is expensive. Maybe make a
// version of IsFirstParty that works on origins?
return site_for_cookies.IsFirstParty(origin.GetURL());
}
// Checks if these values are consistent. See IsolationInfo::Create() for
// descriptions of consistent sets of values. Also allows values used by the
// 0-argument constructor.
bool IsConsistent(IsolationInfo::RequestType request_type,
const absl::optional<url::Origin>& top_frame_origin,
const absl::optional<url::Origin>& frame_origin,
const SiteForCookies& site_for_cookies,
absl::optional<std::set<SchemefulSite>> party_context,
const base::UnguessableToken* nonce) {
// Check for the default-constructed case.
if (!top_frame_origin) {
return request_type == IsolationInfo::RequestType::kOther &&
!frame_origin && !nonce && site_for_cookies.IsNull() &&
!party_context;
}
// if `kForceIsolationInfoFrameOriginToTopLevelFrame` is enabled the
// `top_frame_origin` should be equal to the `frame_origin`.
if (base::FeatureList::IsEnabled(
net::features::kForceIsolationInfoFrameOriginToTopLevelFrame) &&
top_frame_origin != frame_origin) {
return false;
}
// |frame_origin| may only be nullopt if |top_frame_origin| is as well.
if (!frame_origin)
return false;
// As long as there is a |top_frame_origin|, |site_for_cookies| must be
// consistent with the |top_frame_origin|.
if (!ValidateSameSite(*top_frame_origin, site_for_cookies))
return false;
switch (request_type) {
case IsolationInfo::RequestType::kMainFrame:
// TODO(https://crbug.com/1056706): Check that |top_frame_origin| and
// |frame_origin| are the same, once the ViewSource code creates a
// consistent IsolationInfo object.
//
// TODO(https://crbug.com/1060631): Once CreatePartial() is removed, check
// if SiteForCookies is non-null if the scheme is HTTP or HTTPS.
//
// TODO(https://crbug.com/1151947): Once CreatePartial() is removed, check
// if party_context is non-null and empty.
return true;
case IsolationInfo::RequestType::kSubFrame:
// For subframe navigations, the subframe's origin may not be consistent
// with the SiteForCookies, so SameSite cookies may be sent if there's a
// redirect to main frames site.
return true;
case IsolationInfo::RequestType::kOther:
// SiteForCookies must consistent with the frame origin as well for
// subresources.
return ValidateSameSite(*frame_origin, site_for_cookies);
}
}
} // namespace
IsolationInfo::IsolationInfo()
: IsolationInfo(RequestType::kOther,
absl::nullopt,
absl::nullopt,
SiteForCookies(),
nullptr /* nonce */,
absl::nullopt) {}
IsolationInfo::IsolationInfo(const IsolationInfo&) = default;
IsolationInfo::IsolationInfo(IsolationInfo&&) = default;
IsolationInfo::~IsolationInfo() = default;
IsolationInfo& IsolationInfo::operator=(const IsolationInfo&) = default;
IsolationInfo& IsolationInfo::operator=(IsolationInfo&&) = default;
IsolationInfo IsolationInfo::CreateForInternalRequest(
const url::Origin& top_frame_origin) {
return IsolationInfo(RequestType::kOther, top_frame_origin, top_frame_origin,
SiteForCookies::FromOrigin(top_frame_origin),
nullptr /* nonce */,
std::set<SchemefulSite>() /* party_context */);
}
IsolationInfo IsolationInfo::CreateTransient() {
url::Origin opaque_origin;
return IsolationInfo(RequestType::kOther, opaque_origin, opaque_origin,
SiteForCookies(), nullptr /* nonce */,
absl::nullopt /* party_context */);
}
absl::optional<IsolationInfo> IsolationInfo::Deserialize(
const std::string& serialized) {
proto::IsolationInfo proto;
if (!proto.ParseFromString(serialized))
return absl::nullopt;
absl::optional<url::Origin> top_frame_origin;
if (proto.has_top_frame_origin())
top_frame_origin = url::Origin::Create(GURL(proto.top_frame_origin()));
absl::optional<url::Origin> frame_origin;
if (proto.has_frame_origin())
frame_origin = url::Origin::Create(GURL(proto.frame_origin()));
absl::optional<std::set<SchemefulSite>> party_context;
if (proto.has_party_context()) {
party_context = std::set<SchemefulSite>();
for (const auto& site : proto.party_context().site()) {
party_context->insert(SchemefulSite::Deserialize(site));
}
}
return IsolationInfo::CreateIfConsistent(
static_cast<RequestType>(proto.request_type()),
std::move(top_frame_origin), std::move(frame_origin),
SiteForCookies::FromUrl(GURL(proto.site_for_cookies())),
std::move(party_context), nullptr);
}
IsolationInfo IsolationInfo::Create(
RequestType request_type,
const url::Origin& top_frame_origin,
const url::Origin& frame_origin,
const SiteForCookies& site_for_cookies,
absl::optional<std::set<SchemefulSite>> party_context,
const base::UnguessableToken* nonce) {
return IsolationInfo(request_type, top_frame_origin, frame_origin,
site_for_cookies, nonce, std::move(party_context));
}
IsolationInfo IsolationInfo::CreatePartial(
RequestType request_type,
const net::NetworkIsolationKey& network_isolation_key) {
if (!network_isolation_key.IsFullyPopulated())
return IsolationInfo();
// TODO(https://crbug.com/1148927): Use null origins in this case.
url::Origin top_frame_origin =
network_isolation_key.GetTopFrameSite()->site_as_origin_;
url::Origin frame_origin;
if (!base::FeatureList::IsEnabled(
net::features::kForceIsolationInfoFrameOriginToTopLevelFrame) &&
network_isolation_key.GetFrameSite().has_value()) {
frame_origin = network_isolation_key.GetFrameSite()->site_as_origin_;
} else if (request_type == RequestType::kMainFrame) {
frame_origin = top_frame_origin;
} else {
frame_origin = url::Origin();
}
const base::UnguessableToken* nonce =
network_isolation_key.GetNonce()
? &network_isolation_key.GetNonce().value()
: nullptr;
return IsolationInfo(request_type, top_frame_origin, frame_origin,
SiteForCookies(), nonce,
absl::nullopt /* party_context */);
}
absl::optional<IsolationInfo> IsolationInfo::CreateIfConsistent(
RequestType request_type,
const absl::optional<url::Origin>& top_frame_origin,
const absl::optional<url::Origin>& frame_origin,
const SiteForCookies& site_for_cookies,
absl::optional<std::set<SchemefulSite>> party_context,
const base::UnguessableToken* nonce) {
if (!IsConsistent(request_type, top_frame_origin, frame_origin,
site_for_cookies, party_context, nonce)) {
return absl::nullopt;
}
return IsolationInfo(request_type, top_frame_origin, frame_origin,
site_for_cookies, nonce, std::move(party_context));
}
IsolationInfo IsolationInfo::CreateForRedirect(
const url::Origin& new_origin) const {
if (request_type_ == RequestType::kOther)
return *this;
if (request_type_ == RequestType::kSubFrame) {
return IsolationInfo(
request_type_, top_frame_origin_, new_origin, site_for_cookies_,
nonce_.has_value() ? &nonce_.value() : nullptr, party_context_);
}
DCHECK_EQ(RequestType::kMainFrame, request_type_);
DCHECK(!party_context_ || party_context_->empty());
return IsolationInfo(request_type_, new_origin, new_origin,
SiteForCookies::FromOrigin(new_origin),
nonce_.has_value() ? &nonce_.value() : nullptr,
party_context_);
}
const absl::optional<url::Origin>& IsolationInfo::frame_origin() const {
// TODO: @brgoldstein, add CHECK that
// `kForceIsolationInfoFrameOriginToTopLevelFrame` is not enabled.
return frame_origin_;
}
bool IsolationInfo::IsEqualForTesting(const IsolationInfo& other) const {
return (request_type_ == other.request_type_ &&
top_frame_origin_ == other.top_frame_origin_ &&
frame_origin_ == other.frame_origin_ &&
network_isolation_key_ == other.network_isolation_key_ &&
nonce_ == other.nonce_ &&
site_for_cookies_.IsEquivalent(other.site_for_cookies_) &&
party_context_ == other.party_context_);
}
IsolationInfo IsolationInfo::ToDoUseTopFrameOriginAsWell(
const url::Origin& incorrectly_used_frame_origin) {
return IsolationInfo(
RequestType::kOther, incorrectly_used_frame_origin,
incorrectly_used_frame_origin,
SiteForCookies::FromOrigin(incorrectly_used_frame_origin),
nullptr /* nonce */, std::set<SchemefulSite>() /* party_context */);
}
std::string IsolationInfo::Serialize() const {
if (network_isolation_key().IsTransient())
return "";
proto::IsolationInfo info;
info.set_request_type(static_cast<int32_t>(request_type_));
if (top_frame_origin_)
info.set_top_frame_origin(top_frame_origin_->Serialize());
if (frame_origin_)
info.set_frame_origin(frame_origin_->Serialize());
info.set_site_for_cookies(site_for_cookies_.RepresentativeUrl().spec());
if (party_context_) {
auto* pc = info.mutable_party_context();
for (const auto& site : *party_context_) {
pc->add_site(site.Serialize());
}
}
return info.SerializeAsString();
}
IsolationInfo::IsolationInfo(
RequestType request_type,
const absl::optional<url::Origin>& top_frame_origin,
const absl::optional<url::Origin>& frame_origin,
const SiteForCookies& site_for_cookies,
const base::UnguessableToken* nonce,
absl::optional<std::set<SchemefulSite>> party_context)
: request_type_(request_type),
top_frame_origin_(top_frame_origin),
frame_origin_(
base::FeatureList::IsEnabled(
net::features::kForceIsolationInfoFrameOriginToTopLevelFrame)
? top_frame_origin
: frame_origin),
network_isolation_key_(
!top_frame_origin
? NetworkIsolationKey()
: NetworkIsolationKey(SchemefulSite(*top_frame_origin),
SchemefulSite(*frame_origin),
nonce)),
site_for_cookies_(site_for_cookies),
nonce_(nonce ? absl::make_optional(*nonce) : absl::nullopt),
party_context_(party_context.has_value() &&
party_context->size() > kPartyContextMaxSize
? absl::nullopt
: party_context) {
DCHECK(IsConsistent(request_type_, top_frame_origin_, frame_origin_,
site_for_cookies_, party_context_, nonce));
}
} // namespace net