[rSAFor] Require CORS for TopLevelStorageAccess grants eligibility
TopLevelStorageAccess grants should only be considered when the
request mode is 'cors'. To perform this check, add enum CookieSettingOverride::kTopLevelStorageAccessGrantEligible to
url_request.cookie_setting_overrides_ when the request mode is cors,
and only allow cross-site cookie granted by TopLevelStorageAccess
if this require enum exists.
Bug: 1401091
Change-Id: I41a74f530882e38c8051a53013e8a3bf149f1b02
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4144296
Reviewed-by: Maks Orlovich <[email protected]>
Reviewed-by: Chris Fredrickson <[email protected]>
Commit-Queue: Shuran Huang <[email protected]>
Reviewed-by: Takashi Toyoshima <[email protected]>
Reviewed-by: Balazs Engedy <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1096976}
diff --git a/chrome/browser/net/storage_test_utils.cc b/chrome/browser/net/storage_test_utils.cc
index 6561496..c36ddf9 100644
--- a/chrome/browser/net/storage_test_utils.cc
+++ b/chrome/browser/net/storage_test_utils.cc
@@ -170,4 +170,16 @@
.ExtractBool();
}
+std::string FetchWithCredentials(content::RenderFrameHost* frame,
+ const GURL& url,
+ const bool cors_enabled) {
+ constexpr char script[] = R"(
+ fetch($1, {mode: $2, credentials: 'include'})
+ .then((result) => result.text());
+ )";
+ const std::string mode = cors_enabled ? "cors" : "no-cors";
+ return content::EvalJs(frame, content::JsReplace(script, url, mode))
+ .ExtractString();
+}
+
} // namespace storage::test
diff --git a/chrome/browser/net/storage_test_utils.h b/chrome/browser/net/storage_test_utils.h
index b54023e..94b462e 100644
--- a/chrome/browser/net/storage_test_utils.h
+++ b/chrome/browser/net/storage_test_utils.h
@@ -7,6 +7,8 @@
#include <string>
+class GURL;
+
namespace content {
class RenderFrameHost;
} // namespace content
@@ -45,6 +47,13 @@
// value of true; false otherwise.
bool HasStorageAccessForFrame(content::RenderFrameHost* frame);
+// Helper to see if a credentialed fetch has cookies access via top-level
+// storage access grants. Returns the content of the response if the promise
+// resolves. `cors_enabled` sets fetch RequestMode to be "cors" or "no-cors".
+std::string FetchWithCredentials(content::RenderFrameHost* frame,
+ const GURL& url,
+ const bool cors_enabled);
+
} // namespace test
} // namespace storage
#endif // CHROME_BROWSER_NET_STORAGE_TEST_UTILS_H_
diff --git a/chrome/browser/top_level_storage_access_api/request_storage_access_for_origin_browsertest.cc b/chrome/browser/top_level_storage_access_api/request_storage_access_for_origin_browsertest.cc
index 8966346..973160b 100644
--- a/chrome/browser/top_level_storage_access_api/request_storage_access_for_origin_browsertest.cc
+++ b/chrome/browser/top_level_storage_access_api/request_storage_access_for_origin_browsertest.cc
@@ -188,6 +188,13 @@
return ChildFrameAt(GetFrame(), 0);
}
+ std::string CookiesFromFetchWithCredentials(content::RenderFrameHost* frame,
+ const std::string& host,
+ const bool cors_enabled) {
+ return storage::test::FetchWithCredentials(
+ frame, https_server_.GetURL(host, "/echoheader?cookie"), cors_enabled);
+ }
+
net::test_server::EmbeddedTestServer& https_server() { return https_server_; }
private:
@@ -204,14 +211,12 @@
SetBlockThirdPartyCookies(true);
// Set a cookie on `kHostB` and `kHostC`.
- content::SetCookie(browser()->profile(), GetURL(kHostB),
- "thirdparty=b;SameSite=None;Secure");
+ SetCrossSiteCookieOnHost(kHostB);
ASSERT_EQ(content::GetCookies(browser()->profile(), GetURL(kHostB)),
- "thirdparty=b");
- content::SetCookie(browser()->profile(), GetURL(kHostC),
- "thirdparty=c;SameSite=None;Secure");
+ "cross-site=b.test");
+ SetCrossSiteCookieOnHost(kHostC);
ASSERT_EQ(content::GetCookies(browser()->profile(), GetURL(kHostC)),
- "thirdparty=c");
+ "cross-site=c.test");
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/iframe.html");
@@ -253,15 +258,23 @@
->GetCookieManagerForBrowserProcess()
->SetTopLevelStorageAccessSettings(settings, base::DoNothing());
+ // document.hasStorageAccess() does not have cookie access with top-level
+ // storage access grant.
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
NavigateFrameTo(kHostB, "/iframe.html");
NavigateNestedFrameTo(kHostC, "/echoheader?cookie");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
- EXPECT_EQ(GetNestedFrameContent(), "thirdparty=c");
- EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "thirdparty=c");
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
+ EXPECT_EQ(GetNestedFrameContent(), "None");
+ EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "");
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "None");
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetNestedFrame(), kHostC,
+ /*cors_enabled=*/true),
+ "cross-site=c.test");
}
IN_PROC_BROWSER_TEST_F(RequestStorageAccessForOriginBrowserTest,
@@ -336,6 +349,9 @@
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostASubdomain,
+ /*cors_enabled=*/true),
+ "None");
}
// Tests to validate First-Party Set use with `requestStorageAccessForOrigin`.
@@ -384,23 +400,29 @@
// the requestor, because it is a service domain.
EXPECT_TRUE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), GetURL(kHostB).spec()));
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "cross-site=b.test");
// Repeated calls should also return true.
EXPECT_TRUE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), GetURL(kHostB).spec()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
- // the cookie is sent.
+ // the cookie is sent for the cors-enabled subresource request.
NavigateFrameTo(kHostB, "/echoheader?cookie");
- EXPECT_EQ(GetFrameContent(), "cross-site=b.test");
- EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "cross-site=b.test");
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
- // Also validate that an additional site C was not granted access.
- NavigateFrameTo(kHostC, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "cross-site=b.test");
+
+ // Also validate that an additional site C was not granted access.
+ NavigateFrameTo(kHostC, "/echoheader?cookie");
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostC,
+ /*cors_enabled=*/true),
+ "None");
content::FetchHistogramsFromChildProcesses();
@@ -426,6 +448,9 @@
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostA,
+ /*cors_enabled=*/true),
+ "None");
// The promise should be rejected; `khostB` is a service domain.
EXPECT_FALSE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), GetURL(kHostA).spec()));
@@ -437,6 +462,9 @@
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostA,
+ /*cors_enabled=*/true),
+ "None");
content::FetchHistogramsFromChildProcesses();
EXPECT_THAT(histogram_tester.GetBucketCount(
@@ -461,12 +489,18 @@
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "None");
// `kHostB` cannot be granted access via `RequestStorageAccessForOrigin`,
// because the call is not from the top-level page and because `kHostB` is a
// service domain.
EXPECT_FALSE(storage::test::RequestStorageAccessForOrigin(
GetFrame(), GetURL(kHostA).spec()));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "None");
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is not sent.
@@ -474,6 +508,9 @@
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "None");
// However, a regular `requestStorageAccess` call should be granted;
// requesting on behalf of another domain is what is not acceptable.
@@ -511,6 +548,9 @@
EXPECT_FALSE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), GetURL(kHostD).spec()));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostD,
+ /*cors_enabled=*/true),
+ "None");
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is not sent.
@@ -518,6 +558,9 @@
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostD,
+ /*cors_enabled=*/true),
+ "None");
content::FetchHistogramsFromChildProcesses();
EXPECT_THAT(histogram_tester.GetBucketCount(
@@ -542,24 +585,37 @@
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "None");
EXPECT_TRUE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), GetURL(kHostB).spec()));
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
- // the cookie is sent:
+ // the cookie is sent for the cors-enabled subresource request.
NavigateFrameTo(kHostB, "/echoheader?cookie");
- EXPECT_EQ(GetFrameContent(), "cross-site=b.test");
- EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "cross-site=b.test");
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(GetFrameContent(), "None");
+ EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "cross-site=b.test");
+ // Subresource request with cors disabled does not have cookie access.
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/false),
+ "None");
NavigateToPageWithFrame(kHostASubdomain);
NavigateFrameTo(kHostB, "/echoheader?cookie");
// Storage access grants are scoped to the embedded origin on the top-level
- // site. Accordingly, the access should be granted.
- EXPECT_EQ(GetFrameContent(), "cross-site=b.test");
- EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "cross-site=b.test");
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ // site. Accordingly, the access is be granted for subresource request.
+ EXPECT_EQ(GetFrameContent(), "None");
+ EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "cross-site=b.test");
}
IN_PROC_BROWSER_TEST_F(
@@ -578,24 +634,37 @@
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "None");
EXPECT_TRUE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), GetURL(kHostB).spec()));
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
- // the cookie is sent:
+ // the cookie is sent for the cors-enabled subresource request.
NavigateFrameTo(kHostB, "/echoheader?cookie");
- EXPECT_EQ(GetFrameContent(), "cross-site=b.test");
- EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "cross-site=b.test");
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(GetFrameContent(), "None");
+ EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "cross-site=b.test");
+ // Subresource request with cors disabled does not have cookie access.
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/false),
+ "None");
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/echoheader?cookie");
// When top-level site scoping is enabled, the subdomain's grant counts for
// the less-specific domain; otherwise, it does not.
- EXPECT_EQ(GetFrameContent(), "cross-site=b.test");
- EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "cross-site=b.test");
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(GetFrameContent(), "None");
+ EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "cross-site=b.test");
}
// Tests to validate `requestStorageAccessForOrigin` behavior with FPS disabled.
@@ -737,22 +806,22 @@
EXPECT_EQ(GetFrameContent(), "cross-site=b.test(partitioned)");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "cross-site=b.test(partitioned)");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
+ "cross-site=b.test(partitioned)");
// kHostA can request storage access on behalf of kHostB, and it is granted
// (by an implicit grant):
EXPECT_TRUE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), GetURL(kHostB).spec()));
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
- // When the frame subsequently navigates to an endpoint on kHostB, kHostB's
- // unpartitioned and partitioned cookies are sent, and the iframe retains
- // storage access.
+ // When the frame makes a subresource request to an endpoint on kHostB,
+ // kHostB's unpartitioned and partitioned cookies are sent, and the iframe
+ // retains storage access.
NavigateFrameTo(kHostB, "/echoheader?cookie");
- EXPECT_EQ(GetFrameContent(),
+ EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+ EXPECT_EQ(CookiesFromFetchWithCredentials(GetFrame(), kHostB,
+ /*cors_enabled=*/true),
"cross-site=b.test; cross-site=b.test(partitioned)");
- EXPECT_EQ(ReadCookiesViaJS(GetFrame()),
- "cross-site=b.test; cross-site=b.test(partitioned)");
- EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
} // namespace
diff --git a/components/content_settings/core/browser/cookie_settings.cc b/components/content_settings/core/browser/cookie_settings.cc
index ffc05b6..9bb7496 100644
--- a/components/content_settings/core/browser/cookie_settings.cc
+++ b/components/content_settings/core/browser/cookie_settings.cc
@@ -222,7 +222,10 @@
}
}
- if (block && ShouldConsiderTopLevelStorageAccessGrants(query_reason)) {
+ if (block &&
+ overrides.Has(
+ net::CookieSettingOverride::kTopLevelStorageAccessGrantEligible) &&
+ ShouldConsiderTopLevelStorageAccessGrants(query_reason)) {
ContentSetting host_setting = host_content_settings_map_->GetContentSetting(
url, first_party_url, ContentSettingsType::TOP_LEVEL_STORAGE_ACCESS);
diff --git a/components/content_settings/core/browser/cookie_settings_unittest.cc b/components/content_settings/core/browser/cookie_settings_unittest.cc
index 9df434b..6ac216c1 100644
--- a/components/content_settings/core/browser/cookie_settings_unittest.cc
+++ b/components/content_settings/core/browser/cookie_settings_unittest.cc
@@ -76,6 +76,7 @@
struct TestCase {
std::string test_name;
bool storage_access_api_enabled;
+ bool top_level_storage_access_grant_eligible;
bool force_allow_third_party_cookies;
};
@@ -146,12 +147,20 @@
return GetParam().storage_access_api_enabled;
}
+ bool IsTopLevelStorageAccessGrantEligible() const {
+ return GetParam().top_level_storage_access_grant_eligible;
+ }
+
bool IsForceAllowThirdPartyCookies() const {
return GetParam().force_allow_third_party_cookies;
}
net::CookieSettingOverrides GetCookieSettingOverrides() const {
net::CookieSettingOverrides overrides;
+ if (IsTopLevelStorageAccessGrantEligible()) {
+ overrides.Put(
+ net::CookieSettingOverride::kTopLevelStorageAccessGrantEligible);
+ }
if (IsForceAllowThirdPartyCookies()) {
overrides.Put(net::CookieSettingOverride::kForceThirdPartyByUser);
}
@@ -166,11 +175,25 @@
: CONTENT_SETTING_BLOCK;
}
+ // A version of above that considers Top-Level Storage Access API grant
+ // instead of Storage Access API grant, and user force allow.
+ ContentSetting SettingWithEitherOverrideForTopLevel() const {
+ // TODO(crbug.com/1385156): Check TopLevelStorageAccessAPI instead after
+ // separating the feature flag.
+ return (IsStorageAccessAPIEnabled() &&
+ IsTopLevelStorageAccessGrantEligible()) ||
+ IsForceAllowThirdPartyCookies()
+ ? CONTENT_SETTING_ALLOW
+ : CONTENT_SETTING_BLOCK;
+ }
+
ContentSetting SettingWithForceAllowThirdPartyCookies() const {
return IsForceAllowThirdPartyCookies() ? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK;
}
+ // The cookie access result would be blocked if not for a Storage Access API
+ // grant or force allow.
net::cookie_util::StorageAccessResult
BlockedStorageAccessResultWithEitherOverride() const {
if (IsStorageAccessAPIEnabled()) {
@@ -183,9 +206,16 @@
return net::cookie_util::StorageAccessResult::ACCESS_BLOCKED;
}
+ // A version of above that considers Top-Level Storage Access API grant
+ // instead of Storage Access API grant, and user force allow to allow cookie
+ // access.
net::cookie_util::StorageAccessResult
BlockedStorageAccessResultWithTopLevelOverride() const {
- if (IsStorageAccessAPIEnabled()) {
+ // TODO(crbug.com/1385156): Check TopLevelStorageAccessAPI instead after
+ // separating the feature flag.
+ if (IsStorageAccessAPIEnabled() && IsTopLevelStorageAccessGrantEligible()) {
+ // TODO(crbug.com/1385156): Separate metrics between StorageAccessAPI
+ // and the page-level variant.
return net::cookie_util::StorageAccessResult::
ACCESS_ALLOWED_TOP_LEVEL_STORAGE_ACCESS_GRANT;
}
@@ -766,9 +796,9 @@
// grants. TODO(crbug.com/1385156): as requirements for the two APIs solidify,
// this will likely not continue to be true.
TEST_P(CookieSettingsTest, GetCookieSettingTopLevelStorageAccess) {
- const GURL top_level_url = GURL(kFirstPartySite);
- const GURL url = GURL(kAllowedSite);
- const GURL third_url = GURL(kBlockedSite);
+ const GURL top_level_url(kFirstPartySite);
+ const GURL url(kAllowedSite);
+ const GURL third_url(kBlockedSite);
base::HistogramTester histogram_tester;
histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 0);
@@ -784,7 +814,7 @@
EXPECT_EQ(cookie_settings_->GetCookieSetting(url, top_level_url,
GetCookieSettingOverrides(),
nullptr, QueryReason::kCookies),
- SettingWithEitherOverride());
+ SettingWithEitherOverrideForTopLevel());
histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 1);
histogram_tester.ExpectBucketCount(
kAllowedRequestsHistogram,
@@ -1107,10 +1137,14 @@
/* no prefix */,
CookieSettingsTest,
testing::ValuesIn<TestCase>({
- {"disable_SAA", false, false},
- {"enable_SAA", true, false},
- {"disable_SAA_force_3PCs", false, true},
- {"enable_SAA_force_3PCs", true, true},
+ {"disable_all", false, false, false},
+ {"disable_SAA_disable_TopLevel_force_3PCs", false, false, true},
+ {"disable_SAA_enable_TopLevel", false, true, false},
+ {"disable_SAA_enable_TopLevel_force_3PCs", false, true, true},
+ {"enable_SAA_disable_TopLevel", true, false, false},
+ {"enable_SAA_disable_TopLevel_force_3PCs", true, false, true},
+ {"enable_SAA_enable_TopLevel", true, true, false},
+ {"enable_all", true, true, true},
}),
[](const testing::TestParamInfo<CookieSettingsTest::ParamType>& info) {
return info.param.test_name;
@@ -1121,10 +1155,14 @@
/* no prefix */,
CookieSettingsTestSandboxV4Enabled,
testing::ValuesIn<TestCase>({
- {"disable_SAA", false, false},
- {"enable_SAA", true, false},
- {"disable_SAA_force_3PCs", false, true},
- {"enable_SAA_force_3PCs", true, true},
+ {"disable_all", false, false, false},
+ {"disable_SAA_disable_TopLevel_force_3PCs", false, false, true},
+ {"disable_SAA_enable_TopLevel", false, true, false},
+ {"disable_SAA_enable_TopLevel_force_3PCs", false, true, true},
+ {"enable_SAA_disable_TopLevel", true, false, false},
+ {"enable_SAA_disable_TopLevel_force_3PCs", true, false, true},
+ {"enable_SAA_enable_TopLevel", true, true, false},
+ {"enable_all", true, true, true},
}),
[](const testing::TestParamInfo<CookieSettingsTest::ParamType>& info) {
return info.param.test_name;
diff --git a/net/cookies/cookie_setting_override.h b/net/cookies/cookie_setting_override.h
index a475727f5..24bba57 100644
--- a/net/cookies/cookie_setting_override.h
+++ b/net/cookies/cookie_setting_override.h
@@ -17,12 +17,15 @@
// When specified, the user has indicated to force allowing third-party
// cookies.
kForceThirdPartyByUser = 1,
+ // When specified, third-party cookies may be allowed based on existence of
+ // TopLevelStorageAccess grants.
+ kTopLevelStorageAccessGrantEligible = 2,
+ kMaxValue = kTopLevelStorageAccessGrantEligible,
};
-using CookieSettingOverrides =
- base::EnumSet<CookieSettingOverride,
- CookieSettingOverride::kNone,
- CookieSettingOverride::kForceThirdPartyByUser>;
+using CookieSettingOverrides = base::EnumSet<CookieSettingOverride,
+ CookieSettingOverride::kNone,
+ CookieSettingOverride::kMaxValue>;
} // namespace net
diff --git a/net/url_request/url_request_test_util.cc b/net/url_request/url_request_test_util.cc
index d37a409..23d6267 100644
--- a/net/url_request/url_request_test_util.cc
+++ b/net/url_request/url_request_test_util.cc
@@ -341,6 +341,7 @@
kStageBeforeRedirect | // a delegate can trigger a redirection
kStageCompletedError; // request canceled by delegate
created_requests_++;
+ cookie_setting_overrides_ = request->cookie_setting_overrides();
return OK;
}
diff --git a/net/url_request/url_request_test_util.h b/net/url_request/url_request_test_util.h
index bcdc183e..0531e8c5 100644
--- a/net/url_request/url_request_test_util.h
+++ b/net/url_request/url_request_test_util.h
@@ -293,6 +293,10 @@
fps_cache_filter_ = std::move(cache_filter);
}
+ CookieSettingOverrides cookie_setting_overrides() const {
+ return cookie_setting_overrides_;
+ }
+
protected:
// NetworkDelegate:
int OnBeforeURLRequest(URLRequest* request,
@@ -377,6 +381,8 @@
int next_request_id_ = 0;
FirstPartySetsCacheFilter fps_cache_filter_;
+
+ CookieSettingOverrides cookie_setting_overrides_;
};
// ----------------------------------------------------------------------------
diff --git a/services/network/cookie_settings.cc b/services/network/cookie_settings.cc
index 98df360..c40b9e6 100644
--- a/services/network/cookie_settings.cc
+++ b/services/network/cookie_settings.cc
@@ -247,7 +247,9 @@
IsAllowedByStorageAccessGrant(url, first_party_url)) {
storage_access_result = net::cookie_util::StorageAccessResult::
ACCESS_ALLOWED_STORAGE_ACCESS_GRANT;
- } else if (ShouldConsiderTopLevelStorageAccessGrants(query_reason) &&
+ } else if (overrides.Has(net::CookieSettingOverride::
+ kTopLevelStorageAccessGrantEligible) &&
+ ShouldConsiderTopLevelStorageAccessGrants(query_reason) &&
IsAllowedByTopLevelStorageAccessGrant(url, first_party_url)) {
storage_access_result = net::cookie_util::StorageAccessResult::
ACCESS_ALLOWED_TOP_LEVEL_STORAGE_ACCESS_GRANT;
diff --git a/services/network/cookie_settings_unittest.cc b/services/network/cookie_settings_unittest.cc
index e4b6359..c030bab 100644
--- a/services/network/cookie_settings_unittest.cc
+++ b/services/network/cookie_settings_unittest.cc
@@ -62,6 +62,7 @@
struct TestCase {
std::string test_name;
bool storage_access_api_enabled;
+ bool top_level_storage_access_grant_eligible;
bool force_allow_third_party_cookies;
};
@@ -93,12 +94,20 @@
return GetParam().storage_access_api_enabled;
}
+ bool IsTopLevelStorageAccessGrantEligible() const {
+ return GetParam().top_level_storage_access_grant_eligible;
+ }
+
bool IsForceAllowThirdPartyCookies() const {
return GetParam().force_allow_third_party_cookies;
}
net::CookieSettingOverrides GetCookieSettingOverrides() const {
net::CookieSettingOverrides overrides;
+ if (IsTopLevelStorageAccessGrantEligible()) {
+ overrides.Put(
+ net::CookieSettingOverride::kTopLevelStorageAccessGrantEligible);
+ }
if (IsForceAllowThirdPartyCookies()) {
overrides.Put(net::CookieSettingOverride::kForceThirdPartyByUser);
}
@@ -116,6 +125,18 @@
: CONTENT_SETTING_BLOCK;
}
+ // A version of above that considers Top-Level Storage Access API grant
+ // instead of Storage Access API grant, and user force allow.
+ ContentSetting SettingWithEitherOverrideForTopLevel() const {
+ // TODO(crbug.com/1385156): Check TopLevelStorageAccessAPI instead after
+ // separating the feature flag.
+ return (IsStorageAccessAPIEnabled() &&
+ IsTopLevelStorageAccessGrantEligible()) ||
+ IsForceAllowThirdPartyCookies()
+ ? CONTENT_SETTING_ALLOW
+ : CONTENT_SETTING_BLOCK;
+ }
+
ContentSetting SettingWithForceAllowThirdPartyCookies() const {
return IsForceAllowThirdPartyCookies() ? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK;
@@ -128,6 +149,8 @@
: net::cookie_util::StorageAccessResult::ACCESS_BLOCKED;
}
+ // The cookie access result would be blocked if not for a Storage Access API
+ // grant or force allow.
net::cookie_util::StorageAccessResult
BlockedStorageAccessResultWithEitherOverride() const {
if (IsStorageAccessAPIEnabled()) {
@@ -140,6 +163,23 @@
return net::cookie_util::StorageAccessResult::ACCESS_BLOCKED;
}
+ // A version of above that considers Top-Level Storage Access API grant
+ // instead of Storage Access API grant, and user force allow to allow cookie
+ // access.
+ net::cookie_util::StorageAccessResult
+ BlockedStorageAccessResultWithEitherOverrideForTopLevel() const {
+ // TODO(crbug.com/1385156): Check TopLevelStorageAccessAPI instead after
+ // separating the feature flag.
+ if (IsStorageAccessAPIEnabled() && IsTopLevelStorageAccessGrantEligible()) {
+ return net::cookie_util::StorageAccessResult::
+ ACCESS_ALLOWED_TOP_LEVEL_STORAGE_ACCESS_GRANT;
+ }
+ if (IsForceAllowThirdPartyCookies()) {
+ return net::cookie_util::StorageAccessResult::ACCESS_ALLOWED_FORCED;
+ }
+ return net::cookie_util::StorageAccessResult::ACCESS_BLOCKED;
+ }
+
private:
base::test::ScopedFeatureList features_;
base::test::TaskEnvironment task_environment_;
@@ -339,6 +379,109 @@
}
}
+// The Top-Level Storage Access API should unblock storage access that would
+// otherwise be blocked.
+TEST_P(CookieSettingsTest, GetCookieSettingTopLevelStorageAccessUnblocks) {
+ const GURL top_level_url(kURL);
+ const GURL url(kOtherURL);
+ const GURL third_url(kDomainURL);
+
+ base::HistogramTester histogram_tester;
+ histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 0);
+
+ CookieSettings settings;
+ settings.set_content_settings(
+ {CreateSetting("*", "*", CONTENT_SETTING_ALLOW)});
+ settings.set_block_third_party_cookies(true);
+
+ // Only set the storage access granted by Top-Level Storage Access API.
+ settings.set_top_level_storage_access_grants(
+ {CreateSetting(url.host(), top_level_url.host(), CONTENT_SETTING_ALLOW)});
+
+ // When requesting our setting for the embedder/top-level combination our
+ // grant is for access should be allowed. For any other domain pairs access
+ // should still be blocked.
+ EXPECT_EQ(
+ settings.GetCookieSetting(url, top_level_url, GetCookieSettingOverrides(),
+ nullptr, QueryReason::kCookies),
+ SettingWithEitherOverrideForTopLevel());
+ histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 1);
+ histogram_tester.ExpectBucketCount(
+ kAllowedRequestsHistogram,
+ static_cast<int>(
+ BlockedStorageAccessResultWithEitherOverrideForTopLevel()),
+ 1);
+
+ // Check the cookie setting that does not match the top-level storage access
+ // grant--the |top_level_url| granting access to |url| is now being loaded
+ // under |url| as the top level url.
+ EXPECT_EQ(
+ settings.GetCookieSetting(top_level_url, url, GetCookieSettingOverrides(),
+ nullptr, QueryReason::kCookies),
+ SettingWithForceAllowThirdPartyCookies());
+ histogram_tester.ExpectTotalCount(kAllowedRequestsHistogram, 2);
+ // TODO(crbug.com/1385156): Separate metrics between StorageAccessAPI
+ // and the page-level variant.
+ histogram_tester.ExpectBucketCount(
+ kAllowedRequestsHistogram,
+ static_cast<int>(net::cookie_util::StorageAccessResult::
+ ACCESS_ALLOWED_TOP_LEVEL_STORAGE_ACCESS_GRANT),
+ IsStorageAccessAPIEnabled() && IsTopLevelStorageAccessGrantEligible()
+ ? 1
+ : 0);
+ histogram_tester.ExpectBucketCount(
+ kAllowedRequestsHistogram,
+ static_cast<int>(
+ BlockedStorageAccessResultWithEitherOverrideForTopLevel()),
+ IsStorageAccessAPIEnabled() && IsTopLevelStorageAccessGrantEligible()
+ ? 1
+ : 2);
+
+ // Check the cookie setting that does not match the top-level storage access
+ // grant where a |third_url| is used.
+ EXPECT_EQ(
+ settings.GetCookieSetting(url, third_url, GetCookieSettingOverrides(),
+ nullptr, QueryReason::kCookies),
+ SettingWithForceAllowThirdPartyCookies());
+ EXPECT_EQ(settings.GetCookieSetting(third_url, top_level_url,
+ GetCookieSettingOverrides(), nullptr,
+ QueryReason::kCookies),
+ SettingWithForceAllowThirdPartyCookies());
+
+ // If third-party cookies are blocked, Top-Level Storage Access grant takes
+ // precedence over possible override to force allow third-party cookies.
+ {
+ base::HistogramTester histogram_tester_2;
+ EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
+ GetCookieSettingOverrides(), nullptr,
+ QueryReason::kCookies),
+ SettingWithEitherOverrideForTopLevel());
+ histogram_tester_2.ExpectTotalCount(kAllowedRequestsHistogram, 1);
+ histogram_tester_2.ExpectBucketCount(
+ kAllowedRequestsHistogram,
+ static_cast<int>(
+ BlockedStorageAccessResultWithEitherOverrideForTopLevel()),
+ 1);
+ }
+
+ // If cookies are globally blocked, Top-Level Storage Access grants and 3PC
+ // override should both be ignored.
+ {
+ settings.set_content_settings(
+ {CreateSetting("*", "*", CONTENT_SETTING_BLOCK)});
+ base::HistogramTester histogram_tester_2;
+ EXPECT_EQ(settings.GetCookieSetting(url, top_level_url,
+ GetCookieSettingOverrides(), nullptr,
+ QueryReason::kCookies),
+ CONTENT_SETTING_BLOCK);
+ histogram_tester_2.ExpectTotalCount(kAllowedRequestsHistogram, 1);
+ histogram_tester_2.ExpectBucketCount(
+ kAllowedRequestsHistogram,
+ static_cast<int>(net::cookie_util::StorageAccessResult::ACCESS_BLOCKED),
+ 1);
+ }
+}
+
// Subdomains of the granted embedding url should not gain access if a valid
// grant exists.
TEST_P(CookieSettingsTest, GetCookieSettingSAAResourceWildcards) {
@@ -1055,10 +1198,14 @@
/* no prefix */,
CookieSettingsTest,
testing::ValuesIn<TestCase>({
- {"disable_SAA", false, false},
- {"enable_SAA", true, false},
- {"disable_SAA_force_3PCs", false, true},
- {"enable_SAA_force_3PCs", true, true},
+ {"disable_all", false, false, false},
+ {"disable_SAA_disable_TopLevel_force_3PCs", false, false, true},
+ {"disable_SAA_enable_TopLevel", false, true, false},
+ {"disable_SAA_enable_TopLevel_force_3PCs", false, true, true},
+ {"enable_SAA_disable_TopLevel", true, false, false},
+ {"enable_SAA_disable_TopLevel_force_3PCs", true, false, true},
+ {"enable_SAA_enable_TopLevel", true, true, false},
+ {"enable_all", true, true, true},
}),
[](const testing::TestParamInfo<CookieSettingsTest::ParamType>& info) {
return info.param.test_name;
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index dd848ae4..0f5ad68 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -46,6 +46,7 @@
#include "net/base/upload_file_element_reader.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_inclusion_status.h"
+#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_store.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/site_for_cookies.h"
@@ -728,6 +729,11 @@
request.net_log_reference_info.value());
}
+ if (network::cors::IsCorsEnabledRequestMode(request_mode_)) {
+ url_request_->set_cookie_setting_overrides(net::CookieSettingOverrides(
+ net::CookieSettingOverride::kTopLevelStorageAccessGrantEligible));
+ }
+
#if BUILDFLAG(IS_ANDROID)
if (base::FeatureList::IsEnabled(net::features::kRecordRadioWakeupTrigger)) {
MaybeRecordURLLoaderCreationForWakeupTrigger(request, traffic_annotation);
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index 7c1d147..8ed2cc78 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -56,6 +56,7 @@
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_change_dispatcher.h"
#include "net/cookies/cookie_inclusion_status.h"
+#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_network_session.h"
@@ -4745,6 +4746,77 @@
EXPECT_TRUE(url_loader->AllowCookies(third_party_url, site_for_cookies));
}
+TEST_F(URLLoaderTest, CookieSettingOverrides_NoCors) {
+ GURL url("http://www.example.com.test/");
+ base::RunLoop delete_run_loop;
+ // No-cors request should not have the `kTopLevelStorageAccessGrantEligible`
+ // override.
+ ResourceRequest request = CreateResourceRequest("GET", url);
+ // Request mode is `no-cors` by default.
+ ASSERT_EQ(request.mode, network::mojom::RequestMode::kNoCors);
+ mojo::PendingRemote<mojom::URLLoader> loader;
+ std::unique_ptr<URLLoader> url_loader;
+ context().mutable_factory_params().process_id = mojom::kBrowserProcessId;
+ url_loader = URLLoaderOptions().MakeURLLoader(
+ context(), DeleteLoaderCallback(&delete_run_loop, &url_loader),
+ loader.InitWithNewPipeAndPassReceiver(), request,
+ client()->CreateRemote());
+
+ client()->RunUntilComplete();
+ delete_run_loop.Run();
+
+ EXPECT_FALSE(test_network_delegate()->cookie_setting_overrides().Has(
+ net::CookieSettingOverride::kTopLevelStorageAccessGrantEligible));
+}
+
+TEST_F(URLLoaderTest, CookieSettingOverrides_Cors) {
+ GURL url("http://www.example.com.test/");
+ base::RunLoop delete_run_loop;
+ // Cors request should have the `kTopLevelStorageAccessGrantEligible`
+ // override.
+ ResourceRequest request = CreateResourceRequest("GET", url);
+ // Set request mode to `cors`.
+ request.mode = network::mojom::RequestMode::kCors;
+ mojo::PendingRemote<mojom::URLLoader> loader;
+ std::unique_ptr<URLLoader> url_loader;
+ context().mutable_factory_params().process_id = mojom::kBrowserProcessId;
+ url_loader = URLLoaderOptions().MakeURLLoader(
+ context(), DeleteLoaderCallback(&delete_run_loop, &url_loader),
+ loader.InitWithNewPipeAndPassReceiver(), request,
+ client()->CreateRemote());
+
+ client()->RunUntilComplete();
+ delete_run_loop.Run();
+
+ EXPECT_TRUE(test_network_delegate()->cookie_setting_overrides().Has(
+ net::CookieSettingOverride::kTopLevelStorageAccessGrantEligible));
+}
+
+TEST_F(URLLoaderTest, CookieSettingOverrides_Cors_UnchangedOnRedirects) {
+ GURL dest_url("http://www.example.com.test/");
+ GURL redirecting_url =
+ test_server()->GetURL("/server-redirect?" + dest_url.spec());
+
+ base::RunLoop delete_run_loop;
+ // Cors request should have the `kTopLevelStorageAccessGrantEligible`
+ // override.
+ ResourceRequest request = CreateResourceRequest("GET", redirecting_url);
+ // Set request mode to `cors`.
+ request.mode = network::mojom::RequestMode::kCors;
+ mojo::Remote<mojom::URLLoader> loader;
+ std::unique_ptr<URLLoader> url_loader;
+ context().mutable_factory_params().process_id = mojom::kBrowserProcessId;
+ url_loader = URLLoaderOptions().MakeURLLoader(
+ context(), DeleteLoaderCallback(&delete_run_loop, &url_loader),
+ loader.BindNewPipeAndPassReceiver(), request, client()->CreateRemote());
+
+ client()->RunUntilRedirectReceived();
+ loader->FollowRedirect({}, {}, {}, absl::nullopt);
+
+ EXPECT_TRUE(test_network_delegate()->cookie_setting_overrides().Has(
+ net::CookieSettingOverride::kTopLevelStorageAccessGrantEligible));
+}
+
namespace {
enum class TestMode {