blob: 090b5f495b84d638d41a08bb75a2a6fccae85319 [file] [log] [blame]
// 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 "chrome/browser/extensions/api/cookies/cookies_helpers.h"
#include <limits>
#include <memory>
#include "extensions/buildflags/buildflags.h"
#include "net/cookies/canonical_cookie.h"
#include "testing/gtest/include/gtest/gtest.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
namespace extensions {
namespace {
std::optional<extensions::api::cookies::CookiePartitionKey>
BuildApiPartitionKey(const std::optional<std::string>& top_level_site,
const std::optional<bool>& has_cross_site_ancestor) {
base::Value::Dict partition_key_vals;
if (top_level_site.has_value()) {
partition_key_vals.Set("topLevelSite", top_level_site.value());
}
if (has_cross_site_ancestor.has_value()) {
partition_key_vals.Set("hasCrossSiteAncestor",
has_cross_site_ancestor.value());
}
auto partition_key = extensions::api::cookies::CookiePartitionKey::FromValue(
partition_key_vals);
CHECK(partition_key.has_value());
return partition_key;
}
} // namespace
// Tests that cookies with an expiration date too far in the future to represent
// with base::Time serialize gracefully.
// Regression test for https://crbug.com/848221.
TEST(CookiesHelperUnittest, CookieConversionWithInfiniteExpirationDate) {
// Set a cookie to expire at base::Time::Max(). This can happen when the
// cookie is set to expire farther in the future than we can accurately
// represent with base::Time(). Note that, in practice, this is really only
// applicable on 32-bit machines, but we can fake it a bit for cross-platform
// testing by just setting the expiration date directly.
const base::Time kExpirationDate = base::Time::Max();
auto cookie = net::CanonicalCookie::CreateUnsafeCookieForTesting(
"cookiename", "cookievalue", "example.com", "/", base::Time::Now(),
kExpirationDate, base::Time(), base::Time(), false, false,
net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT);
// Serialize the cookie to JSON. We need to gracefully handle the infinite
// expiration date, which should be converted to the maximum value.
api::cookies::Cookie serialized_cookie =
cookies_helpers::CreateCookie(*cookie, "1");
base::Value::Dict value_cookie = serialized_cookie.ToValue();
std::optional<double> expiration_time =
value_cookie.FindDouble("expirationDate");
ASSERT_TRUE(expiration_time);
EXPECT_EQ(std::numeric_limits<double>::max(), *expiration_time);
}
TEST(CookiesHelperUnittest, ValidateCrossSiteAncestorErrorCases) {
std::string site = "https://example.com";
std::string invalid_site = "invalid";
struct {
std::string description;
std::string url;
std::optional<std::string> top_level_site;
std::optional<bool> has_cross_site_ancestor;
std::string error_str;
} error_cases[] = {
{/*description=*/"No top_level_site and a has_cross_site_ancestor",
/*url=*/site, /*top_level_site=*/std::nullopt,
/*has_cross_site_ancestor=*/true,
/*error_str=*/
"CookiePartitionKey.topLevelSite is not present when "
"CookiePartitionKey.hasCrossSiteAncestor is present."},
{/*description=*/"Empty string for top_level_site and a "
"has_cross_site_ancestor` value of true",
/*url=*/site, /*top_level_site=*/"", /*has_cross_site_ancestor=*/true,
/*error_str=*/"CookiePartitionKey.hasCrossSiteAncestor is invalid."},
{/*description=*/"Invalid url",
/*url=*/invalid_site, /*top_level_site=*/site,
/*has_cross_site_ancestor=*/true, /*error_str=*/"Invalid url_string."},
{/*description=*/"Invalid top_level_site",
/*url=*/site, /*top_level_site=*/invalid_site,
/*has_cross_site_ancestor=*/true,
/*error_str=*/"Invalid value for CookiePartitionKey.topLevelSite."},
{/*description=*/"has_cross_site_ancestor can not be true if url and "
"top_level_site` aren't first party.",
/*url=*/site, /*top_level_site=*/invalid_site,
/*has_cross_site_ancestor=*/false,
/*error_str=*/"Invalid value for CookiePartitionKey.topLevelSite."},
{/*description=*/"has_cross_site_ancestor must be populated for "
"validation.",
/*url=*/site, /*top_level_site=*/site,
/*has_cross_site_ancestor=*/std::nullopt,
/*error_str=*/
"Can not validate an empty value "
"for hasCrossSiteAncestor."},
};
for (const auto& tc : error_cases) {
SCOPED_TRACE(tc.description);
auto partition_key =
BuildApiPartitionKey(tc.top_level_site, tc.has_cross_site_ancestor);
std::string error;
EXPECT_FALSE(cookies_helpers::ValidateCrossSiteAncestor(
tc.url, partition_key, &error));
EXPECT_EQ(tc.error_str, error);
}
}
TEST(CookiesHelperUnittest, CalculateHasCrossSiteAncestor) {
std::string site = "https://example.com";
std::string cross_site = "https://crosssite.com";
std::string invalid_site = "invalid";
struct {
std::string description;
std::string url;
std::optional<std::string> top_level_site;
std::optional<bool> has_cross_site_ancestor;
base::expected<bool, std::string> result;
} cases[] = {
{/*description=*/"Valid same-site case with empty hasCrossSiteAncestor ",
/*url=*/site,
/*top_level_site=*/site,
/*has_cross_site_ancestor=*/std::nullopt,
/*result=*/false},
{/*description=*/"Valid cross-site case with empty hasCrossSiteAncestor ",
/*url=*/site,
/*top_level_site=*/cross_site,
/*has_cross_site_ancestor=*/std::nullopt,
/*result=*/true},
{
/*description=*/"Return already set value",
/*url=*/site,
/*top_level_site=*/site,
/*has_cross_site_ancestor=*/true,
/*result=*/true,
},
{
/*description=*/"Empty top_level_site indicates an unpartitioned "
"cookie which always has a hasCrossSiteAncestor of "
"false.",
/*url=*/site,
/*top_level_site=*/"",
/*has_cross_site_ancestor=*/std::nullopt,
/*result=*/false,
},
{/*description=*/"Invalid url_string",
/*url=*/invalid_site,
/*top_level_site=*/site,
/*has_cross_site_ancestor=*/std::nullopt,
/*result=*/base::unexpected("Invalid url_string.")},
{/*description=*/"Invalid value for CookiePartitionKey.topLevelSite",
/*url=*/site,
/*top_level_site=*/invalid_site,
/*has_cross_site_ancestor=*/std::nullopt,
/*result=*/
base::unexpected("Invalid value for CookiePartitionKey.topLevelSite.")},
};
for (const auto& tc : cases) {
SCOPED_TRACE(tc.description);
auto partition_key =
BuildApiPartitionKey(tc.top_level_site, tc.has_cross_site_ancestor);
base::expected<bool, std::string> result =
cookies_helpers::CalculateHasCrossSiteAncestor(tc.url, partition_key);
EXPECT_EQ(tc.result, result);
}
}
TEST(CookiesHelperUnittest, ToNetCookiePartitionKey) {
std::string site = "https://example.com";
struct {
std::string description;
std::optional<std::string> top_level_site;
std::optional<bool> has_cross_site_ancestor;
base::expected<std::optional<net::CookiePartitionKey>, std::string>
expected;
} cases[] = {
{/*description=*/"Valid unpartitioned case, no hasCrossSiteAncestor or "
"topLevelSite",
/*top_level_site=*/std::nullopt,
/*has_cross_site_ancestor=*/std::nullopt,
/*expected=*/std::nullopt},
{/*description=*/"Valid unpartitioned case, no hasCrossSiteAncestor and "
"empty topLevelSite",
/*top_level_site=*/"",
/*has_cross_site_ancestor=*/std::nullopt,
/*expected=*/std::nullopt},
{/*description=*/"Valid unpartitioned case, empty topLevelSite and "
"hasCrossSiteAncestor value of false",
/*top_level_site=*/"",
/*has_cross_site_ancestor=*/false,
/*expected=*/std::nullopt},
{/*description=*/"Valid case with hasCrossSiteAncestor false",
/*top_level_site=*/site,
/*has_cross_site_ancestor=*/false,
/*expected=*/
net::CookiePartitionKey::FromURLForTesting(
GURL(site), net::CookiePartitionKey::AncestorChainBit::kSameSite)},
{/*description=*/"Valid case with hasCrossSiteAncestor true",
/*top_level_site=*/site,
/*has_cross_site_ancestor=*/true,
/*expected=*/net::CookiePartitionKey::FromURLForTesting(GURL(site))},
{/*description=*/"No hasCrossSiteAncestor value default to true",
/*top_level_site=*/site,
/*has_cross_site_ancestor=*/std::nullopt,
/*expected=*/net::CookiePartitionKey::FromURLForTesting(GURL(site))},
{/*description=*/"HasCrossSiteAncestor populated but no topLevelSite",
/*top_level_site=*/std::nullopt,
/*has_cross_site_ancestor=*/true,
/*expected=*/
base::unexpected(
"CookiePartitionKey.topLevelSite unexpectedly not present.")},
{/*description=*/"Invalid topLevelSite",
/*top_level_site=*/"INVALID",
/*has_cross_site_ancestor=*/true,
/*expected=*/
base::unexpected(
"Cannot deserialize opaque origin to CookiePartitionKey")},
};
for (const auto& tc : cases) {
SCOPED_TRACE(tc.description);
auto partition_key =
BuildApiPartitionKey(tc.top_level_site, tc.has_cross_site_ancestor);
if (!tc.expected.has_value()) {
DLOG(ERROR)
<< cookies_helpers::ToNetCookiePartitionKey(partition_key).error();
}
EXPECT_EQ(tc.expected,
cookies_helpers::ToNetCookiePartitionKey(partition_key));
}
}
} // namespace extensions