blob: f8da94ac8ee969b159c7e679ba2bcf33c6cda534 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/net/storage_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/features.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/base/features.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_partition_key_collection.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-forward.h"
#include "ui/base/window_open_disposition.h"
using content::BrowserThread;
using testing::Gt;
namespace {
constexpr char kHostA[] = "a.test";
constexpr char kHostB[] = "b.test";
constexpr char kHostC[] = "c.test";
constexpr char kHostD[] = "d.test";
constexpr char kUseCounterHistogram[] = "Blink.UseCounter.Features";
constexpr char kRequestOutcomeHistogram[] = "API.StorageAccess.RequestOutcome";
constexpr char kRequestForOriginResultHistogram[] =
"API.StorageAccess.RequestStorageAccessForOrigin";
enum class TestType { kFrame, kWorker };
std::string BoolToString(bool b) {
return b ? "true" : "false";
}
class StorageAccessAPIBaseBrowserTest : public InProcessBrowserTest {
protected:
StorageAccessAPIBaseBrowserTest(bool permission_grants_unpartitioned_storage,
bool is_storage_partitioned)
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
permission_grants_unpartitioned_storage_(
permission_grants_unpartitioned_storage),
is_storage_partitioned_(is_storage_partitioned) {}
void SetUp() override {
features_.InitWithFeaturesAndParameters(GetEnabledFeatures(),
GetDisabledFeatures());
InProcessBrowserTest::SetUp();
}
virtual std::vector<base::test::ScopedFeatureList::FeatureAndParams>
GetEnabledFeatures() {
std::vector<base::test::ScopedFeatureList::FeatureAndParams> enabled({
{net::features::kStorageAccessAPI,
{
{
"storage-access-api-grants-unpartitioned-storage",
BoolToString(permission_grants_unpartitioned_storage_),
},
{
"storage_access_api_auto_grant_within_fps",
"false",
},
{
"storage_access_api_auto_deny_outside_fps",
"false",
},
}},
});
if (is_storage_partitioned_) {
enabled.push_back({net::features::kThirdPartyStoragePartitioning, {}});
}
return enabled;
}
virtual std::vector<base::Feature> GetDisabledFeatures() {
std::vector<base::Feature> disabled;
if (!is_storage_partitioned_) {
disabled.push_back(net::features::kThirdPartyStoragePartitioning);
}
return disabled;
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
base::FilePath path;
base::PathService::Get(content::DIR_TEST_DATA, &path);
https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_server_.ServeFilesFromDirectory(path);
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server_.Start());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// TODO(fivedots): Remove this switch once Storage Foundation is enabled
// by default.
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
"StorageFoundationAPI");
}
void SetCrossSiteCookieOnHost(const std::string& host) {
GURL host_url = GetURL(host);
std::string cookie = base::StrCat({"cross-site=", host});
content::SetCookie(browser()->profile(), host_url,
base::StrCat({cookie, ";SameSite=None;Secure"}));
ASSERT_THAT(content::GetCookies(browser()->profile(), host_url),
testing::HasSubstr(cookie));
}
void SetPartitionedCookieInContext(const std::string& top_level_host,
const std::string& embedded_host) {
GURL host_url = GetURL(embedded_host);
std::string cookie =
base::StrCat({"cross-site=", embedded_host, "(partitioned)"});
net::CookiePartitionKey partition_key =
net::CookiePartitionKey::FromURLForTesting(GetURL(top_level_host));
content::SetPartitionedCookie(
browser()->profile(), host_url,
base::StrCat({cookie, ";SameSite=None;Secure;Partitioned"}),
partition_key);
ASSERT_THAT(content::GetCookies(
browser()->profile(), host_url,
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKeyCollection(partition_key)),
testing::HasSubstr(cookie));
}
GURL GetURL(const std::string& host) {
return https_server_.GetURL(host, "/");
}
void SetBlockThirdPartyCookies(bool value) {
browser()->profile()->GetPrefs()->SetInteger(
prefs::kCookieControlsMode,
static_cast<int>(
value ? content_settings::CookieControlsMode::kBlockThirdParty
: content_settings::CookieControlsMode::kOff));
}
void NavigateToPageWithFrame(const std::string& host) {
GURL main_url(https_server_.GetURL(host, "/iframe.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
}
void NavigateToNewTabWithFrame(const std::string& host) {
GURL main_url(https_server_.GetURL(host, "/iframe.html"));
ui_test_utils::NavigateToURLWithDisposition(
browser(), main_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
}
void NavigateFrameTo(const std::string& host, const std::string& path) {
GURL page = https_server_.GetURL(host, path);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", page));
}
std::string GetFrameContent() {
return storage::test::GetFrameContent(GetFrame());
}
void NavigateNestedFrameTo(const std::string& host, const std::string& path) {
GURL url(https_server_.GetURL(host, path));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver load_observer(web_contents);
ASSERT_TRUE(ExecuteScript(
GetFrame(),
base::StringPrintf("document.body.querySelector('iframe').src = '%s';",
url.spec().c_str())));
load_observer.Wait();
}
std::string GetNestedFrameContent() {
return storage::test::GetFrameContent(GetNestedFrame());
}
std::string ReadCookiesViaJS(content::RenderFrameHost* render_frame_host) {
return content::EvalJs(render_frame_host, "document.cookie")
.ExtractString();
}
content::RenderFrameHost* GetPrimaryMainFrame() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
return web_contents->GetPrimaryMainFrame();
}
content::RenderFrameHost* GetFrame() {
return ChildFrameAt(GetPrimaryMainFrame(), 0);
}
content::RenderFrameHost* GetNestedFrame() {
return ChildFrameAt(GetFrame(), 0);
}
net::test_server::EmbeddedTestServer& https_server() { return https_server_; }
bool PermissionGrantsUnpartitionedStorage() const {
return permission_grants_unpartitioned_storage_;
}
bool IsStoragePartitioned() const { return is_storage_partitioned_; }
private:
net::test_server::EmbeddedTestServer https_server_;
base::test::ScopedFeatureList features_;
bool permission_grants_unpartitioned_storage_;
bool is_storage_partitioned_;
};
class StorageAccessAPIBrowserTest
: public StorageAccessAPIBaseBrowserTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
StorageAccessAPIBrowserTest()
: StorageAccessAPIBaseBrowserTest(std::get<0>(GetParam()),
std::get<1>(GetParam())) {}
};
// Validate that if an iframe requests access that cookies become unblocked for
// just that top-level/third-party combination.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
ThirdPartyCookiesIFrameRequestsAccess) {
SetBlockThirdPartyCookies(true);
base::HistogramTester histogram_tester;
// Set cross-site cookies on all hosts.
SetCrossSiteCookieOnHost(kHostA);
SetCrossSiteCookieOnHost(kHostB);
SetCrossSiteCookieOnHost(kHostC);
SetCrossSiteCookieOnHost(kHostD);
NavigateToPageWithFrame(kHostA);
// Allow all requests for kHostB to have cookie access from a.test.
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is sent:
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()));
// Navigate iframe to c.test and verify that the cookie is not sent.
NavigateFrameTo(kHostC, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site frame with a frame, and navigate _that_
// frame to a cross-site page that echos the cookie header, and verify that
// the cookie is sent:
NavigateFrameTo(kHostB, "/iframe.html");
NavigateNestedFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetNestedFrameContent(), "cross-site=b.test");
EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "cross-site=b.test");
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
// Navigate nested iframe to c.test and verify that the cookie is not
// sent.
NavigateNestedFrameTo(kHostC, "/echoheader?cookie");
EXPECT_EQ(GetNestedFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
// Navigate iframe to a cross-site frame with a frame, and navigate _that_
// frame to a distinct cross-site page that echos the cookie header, and
// verify that the cookie is sent:
NavigateFrameTo(kHostC, "/iframe.html");
NavigateNestedFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetNestedFrameContent(), "cross-site=b.test");
EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "cross-site=b.test");
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
// Navigate nested iframe to c.test and verify that the cookie is not
// sent.
NavigateNestedFrameTo(kHostC, "/echoheader?cookie");
EXPECT_EQ(GetNestedFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
// Navigate our top level to kHostD and verify that all requests for kHostB
// are now blocked in that context.
NavigateToPageWithFrame(kHostD);
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is blocked:
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site frame with a frame, and navigate _that_
// frame to a cross-site page that echos the cookie header, and verify that
// the cookie is blocked:
NavigateFrameTo(kHostB, "/iframe.html");
NavigateNestedFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetNestedFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
// Navigate iframe to a cross-site frame with a frame, and navigate _that_
// frame to a distinct cross-site page that echos the cookie header, and
// verify that the cookie is blocked:
NavigateFrameTo(kHostC, "/iframe.html");
NavigateNestedFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetNestedFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
content::FetchHistogramsFromChildProcesses();
EXPECT_THAT(
histogram_tester.GetBucketCount(
kUseCounterHistogram,
blink::mojom::WebFeature::kStorageAccessAPI_HasStorageAccess_Method),
Gt(0));
EXPECT_THAT(histogram_tester.GetBucketCount(
kUseCounterHistogram,
blink::mojom::WebFeature::
kStorageAccessAPI_requestStorageAccess_Method),
Gt(0));
}
// Validate that the Storage Access API does not override any explicit user
// settings to block storage access.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
ThirdPartyCookiesIFrameThirdPartyExceptions) {
SetBlockThirdPartyCookies(true);
// Set a cookie on `kHostB`.
content::SetCookie(browser()->profile(), GetURL(kHostB),
"thirdparty=1;SameSite=None;Secure");
ASSERT_EQ(content::GetCookies(browser()->profile(), GetURL(kHostB)),
"thirdparty=1");
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Block all cookies with a user setting for kHostB.
CookieSettingsFactory::GetForProfile(browser()->profile())
->SetCookieSetting(GetURL(kHostB), ContentSetting::CONTENT_SETTING_BLOCK);
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is blocked:
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site frame with a frame, and navigate _that_
// frame to a cross-site page that echos the cookie header, and verify that
// the cookie is blocked:
NavigateFrameTo(kHostB, "/iframe.html");
NavigateNestedFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetNestedFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
// Navigate iframe to a cross-site frame with a frame, and navigate _that_
// frame to a distinct cross-site page that echos the cookie header, and
// verify that the cookie is blocked:
NavigateFrameTo(kHostC, "/iframe.html");
NavigateNestedFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetNestedFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
}
// Validates that once a grant is removed access is also removed.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
ThirdPartyGrantsDeletedAccess) {
SetBlockThirdPartyCookies(true);
// Set a cookie on `kHostB`.
content::SetCookie(browser()->profile(), GetURL(kHostB),
"thirdparty=1;SameSite=None;Secure");
ASSERT_EQ(content::GetCookies(browser()->profile(), GetURL(kHostB)),
"thirdparty=1");
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is sent:
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "thirdparty=1");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "thirdparty=1");
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Manually delete all our grants.
HostContentSettingsMap* settings_map =
HostContentSettingsMapFactory::GetForProfile(browser()->profile());
settings_map->ClearSettingsForOneType(ContentSettingsType::STORAGE_ACCESS);
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest, OpaqueOriginRejects) {
SetBlockThirdPartyCookies(true);
NavigateToPageWithFrame(kHostA);
ASSERT_TRUE(ExecuteScript(
GetPrimaryMainFrame(),
"document.querySelector('iframe').sandbox='allow-scripts';"));
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_FALSE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
MissingSandboxTokenRejects) {
SetBlockThirdPartyCookies(true);
NavigateToPageWithFrame(kHostA);
ASSERT_TRUE(ExecuteScript(GetPrimaryMainFrame(),
"document.querySelector('iframe').sandbox='allow-"
"scripts allow-same-origin';"));
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_FALSE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest, SandboxTokenResolves) {
SetBlockThirdPartyCookies(true);
NavigateToPageWithFrame(kHostA);
ASSERT_TRUE(ExecuteScript(
GetPrimaryMainFrame(),
"document.querySelector('iframe').sandbox='allow-scripts "
"allow-same-origin allow-storage-access-by-user-activation';"));
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
// Validates that expiry data is transferred over IPC to the Network Service.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
ThirdPartyGrantsExpireOverIPC) {
SetBlockThirdPartyCookies(true);
// Set a cookie on `kHostB` and `kHostC`.
content::SetCookie(browser()->profile(), GetURL(kHostB),
"thirdparty=b;SameSite=None;Secure");
ASSERT_EQ(content::GetCookies(browser()->profile(), GetURL(kHostB)),
"thirdparty=b");
content::SetCookie(browser()->profile(), GetURL(kHostC),
"thirdparty=c;SameSite=None;Secure");
ASSERT_EQ(content::GetCookies(browser()->profile(), GetURL(kHostC)),
"thirdparty=c");
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/iframe.html");
NavigateNestedFrameTo(kHostC, "/echoheader?cookie");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
// Manually create a pre-expired grant and ensure it doesn't grant access.
base::Time expiration_time = base::Time::Now() - base::Minutes(5);
HostContentSettingsMap* settings_map =
HostContentSettingsMapFactory::GetForProfile(browser()->profile());
settings_map->SetContentSettingDefaultScope(
GetURL(kHostB), GetURL(kHostA), ContentSettingsType::STORAGE_ACCESS,
CONTENT_SETTING_ALLOW,
{expiration_time, content_settings::SessionModel::UserSession});
settings_map->SetContentSettingDefaultScope(
GetURL(kHostC), GetURL(kHostA), ContentSettingsType::STORAGE_ACCESS,
CONTENT_SETTING_ALLOW,
{expiration_time, content_settings::SessionModel::UserSession});
// Manually send our expired setting. This needs to be done manually because
// normally this expired value would be filtered out before sending and time
// cannot be properly mocked in a browser test.
ContentSettingsForOneType settings;
settings.push_back(ContentSettingPatternSource(
ContentSettingsPattern::FromURLNoWildcard(GetURL(kHostB)),
ContentSettingsPattern::FromURLNoWildcard(GetURL(kHostA)),
base::Value(CONTENT_SETTING_ALLOW), "preference",
/*incognito=*/false, {.expiration = expiration_time}));
settings.emplace_back(
ContentSettingsPattern::FromURLNoWildcard(GetURL(kHostC)),
ContentSettingsPattern::FromURLNoWildcard(GetURL(kHostA)),
base::Value(CONTENT_SETTING_ALLOW), "preference",
/*incognito=*/false);
browser()
->profile()
->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess()
->SetStorageAccessGrantSettings(settings, base::DoNothing());
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(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");
}
IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
RsaForOriginDisabledByDefault) {
NavigateToPageWithFrame(kHostA);
// Ensure that the proposed extension is not available unless explicitly
// enabled.
EXPECT_TRUE(EvalJs(GetPrimaryMainFrame(),
"\"requestStorageAccessForOrigin\" in document === false")
.ExtractBool());
}
INSTANTIATE_TEST_CASE_P(/* no prefix */,
StorageAccessAPIBrowserTest,
testing::Combine(testing::Bool(), testing::Bool()));
class StorageAccessAPIStorageBrowserTest
: public StorageAccessAPIBaseBrowserTest,
public testing::WithParamInterface<std::tuple<TestType, bool, bool>> {
public:
StorageAccessAPIStorageBrowserTest()
: StorageAccessAPIBaseBrowserTest(std::get<1>(GetParam()),
std::get<2>(GetParam())) {}
void ExpectStorage(content::RenderFrameHost* frame, bool expected) {
switch (GetTestType()) {
case TestType::kFrame:
storage::test::ExpectStorageForFrame(frame, /*include_cookies=*/false,
expected);
return;
case TestType::kWorker:
storage::test::ExpectStorageForWorker(frame, expected);
return;
}
}
void SetStorage(content::RenderFrameHost* frame) {
switch (GetTestType()) {
case TestType::kFrame:
storage::test::SetStorageForFrame(frame, /*include_cookies=*/false);
return;
case TestType::kWorker:
storage::test::SetStorageForWorker(frame);
return;
}
}
bool DoesPermissionGrantStorage() const {
return IsStoragePartitioned() || PermissionGrantsUnpartitionedStorage();
}
private:
TestType GetTestType() const { return std::get<0>(GetParam()); }
};
// Validate that the Storage Access API will unblock other types of storage
// access when a grant is given and that it only applies to the top-level/third
// party pair requested on.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest,
ThirdPartyIFrameStorageRequestsAccess) {
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
ExpectStorage(GetFrame(), false);
SetStorage(GetFrame());
ExpectStorage(GetFrame(), true);
SetBlockThirdPartyCookies(true);
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
ExpectStorage(GetFrame(), false);
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Allow all requests to kHostB on kHostA to access storage.
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
ExpectStorage(GetFrame(), DoesPermissionGrantStorage());
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest,
NestedThirdPartyIFrameStorage) {
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/iframe.html");
NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html");
ExpectStorage(GetNestedFrame(), false);
SetStorage(GetNestedFrame());
ExpectStorage(GetNestedFrame(), true);
SetBlockThirdPartyCookies(true);
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/iframe.html");
NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html");
ExpectStorage(GetNestedFrame(), false);
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
// Allow all requests to kHostB on kHostA to access storage.
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetNestedFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/iframe.html");
NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html");
ExpectStorage(GetNestedFrame(), DoesPermissionGrantStorage());
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
}
// Test third-party cookie blocking of features that allow to communicate
// between tabs such as SharedWorkers.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest, MultiTabTest) {
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
storage::test::ExpectCrossTabInfoForFrame(GetFrame(), false);
storage::test::SetCrossTabInfoForFrame(GetFrame());
storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Create a second tab to test communication between tabs.
NavigateToNewTabWithFrame(kHostA);
NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
SetBlockThirdPartyCookies(true);
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
storage::test::ExpectCrossTabInfoForFrame(GetFrame(), false);
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Allow all requests to kHostB to access cookies.
// Allow all requests to kHostB on kHostA to access storage.
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/browsing_data/site_data.html");
storage::test::ExpectCrossTabInfoForFrame(GetFrame(),
DoesPermissionGrantStorage());
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
StorageAccessAPIStorageBrowserTest,
testing::Combine(testing::Values(TestType::kFrame,
TestType::kWorker),
testing::Bool(),
testing::Bool()));
class StorageAccessAPIForOriginBrowserTest
: public StorageAccessAPIBaseBrowserTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
StorageAccessAPIForOriginBrowserTest()
: StorageAccessAPIBaseBrowserTest(std::get<0>(GetParam()),
std::get<1>(GetParam())) {}
protected:
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
GetEnabledFeatures() override {
std::vector<base::test::ScopedFeatureList::FeatureAndParams> enabled =
StorageAccessAPIBaseBrowserTest::GetEnabledFeatures();
enabled.push_back(
{blink::features::kStorageAccessAPIForOriginExtension, {}});
return enabled;
}
};
IN_PROC_BROWSER_TEST_P(StorageAccessAPIForOriginBrowserTest,
SameOriginGrantedByDefault) {
SetBlockThirdPartyCookies(true);
base::HistogramTester histogram_tester;
NavigateToPageWithFrame(kHostA);
EXPECT_FALSE(storage::test::RequestStorageAccessForOrigin(
GetFrame(), "https://asdf.example"));
EXPECT_FALSE(
storage::test::RequestStorageAccessForOrigin(GetFrame(), "mattwashere"));
EXPECT_TRUE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), base::StrCat({"https://", kHostA})));
EXPECT_FALSE(storage::test::RequestStorageAccessForOrigin(
GetFrame(), base::StrCat({"https://", kHostA})));
}
IN_PROC_BROWSER_TEST_P(StorageAccessAPIForOriginBrowserTest,
TopLevelOpaqueOriginRejected) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
GURL("data:,Hello%2C%20World%21")));
EXPECT_FALSE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(), base::StrCat({"https://", kHostA})));
}
// Validate that if an iframe requests access that cookies become unblocked for
// just that top-level/third-party combination.
IN_PROC_BROWSER_TEST_P(StorageAccessAPIForOriginBrowserTest,
// TODO(crbug.com/1370096): Re-enable this test
DISABLED_GrantGivesCrossSiteCookieAccess) {
SetBlockThirdPartyCookies(true);
base::HistogramTester histogram_tester;
// Set cross-site cookies on all hosts.
SetCrossSiteCookieOnHost(kHostA);
SetCrossSiteCookieOnHost(kHostB);
NavigateToPageWithFrame(kHostA);
// Allow all requests for kHostB to have cookie access from a.test.
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(),
base::StrCat({"https://", kHostB, ":",
base::NumberToString(https_server().port())})));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is sent:
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()));
content::FetchHistogramsFromChildProcesses();
metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
EXPECT_THAT(histogram_tester.GetBucketCount(
kRequestForOriginResultHistogram,
1 /*RequestStorageResult::APPROVED_NEW_GRANT*/),
Gt(0));
EXPECT_THAT(histogram_tester.GetBucketCount(
kUseCounterHistogram,
blink::mojom::WebFeature::
kStorageAccessAPI_requestStorageAccessForOrigin_Method),
Gt(0));
}
INSTANTIATE_TEST_CASE_P(/* no prefix */,
StorageAccessAPIForOriginBrowserTest,
testing::Combine(testing::Bool(), testing::Bool()));
// Tests to validate First-Party Set use with `requestStorageAccessForOrigin`.
class StorageAccessAPIForOriginWithFirstPartySetsBrowserTest
: public StorageAccessAPIBaseBrowserTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
StorageAccessAPIForOriginWithFirstPartySetsBrowserTest()
: StorageAccessAPIBaseBrowserTest(false, false) {}
void SetUpCommandLine(base::CommandLine* command_line) override {
StorageAccessAPIBaseBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
network::switches::kUseFirstPartySet,
base::StrCat({R"({"primary": "https://)", kHostA,
R"(", "associatedSites": ["https://)", kHostC, R"("])",
R"(, "serviceSites": ["https://)", kHostB, R"("]})"}));
}
protected:
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
GetEnabledFeatures() override {
return {{blink::features::kStorageAccessAPIForOriginExtension, {}},
{net::features::kStorageAccessAPI,
{
{
net::features::kStorageAccessAPIAutoGrantInFPS.name,
"true",
},
{
net::features::kStorageAccessAPIAutoDenyOutsideFPS.name,
"true",
},
// Setting implicit grants to a non-zero number here
// demonstrates that when the auto-deny param is enabled, the
// implicit grants param doesn't matter, since the auto-deny
// param takes precedence.
{
"storage-access-api-implicit-grant-limit",
"5",
},
}}};
}
};
IN_PROC_BROWSER_TEST_F(StorageAccessAPIForOriginWithFirstPartySetsBrowserTest,
Permission_AutograntedWithinFirstPartySet) {
SetBlockThirdPartyCookies(true);
base::HistogramTester histogram_tester;
// Set cross-site cookies on all hosts.
SetCrossSiteCookieOnHost(kHostA);
SetCrossSiteCookieOnHost(kHostB);
SetCrossSiteCookieOnHost(kHostC);
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// The request comes from `kHostA`, which is in a First-Party Set with
// `khostB`. Note that `kHostB` would not be auto-granted access if it were
// the requestor, because it is a service domain.
EXPECT_TRUE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(),
base::StrCat({"https://", kHostB, ":",
base::NumberToString(https_server().port())})));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is sent.
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()));
content::FetchHistogramsFromChildProcesses();
EXPECT_THAT(histogram_tester.GetBucketCount(
kRequestOutcomeHistogram,
0 /*RequestOutcome::kGrantedByFirstPartySet*/),
Gt(0));
}
IN_PROC_BROWSER_TEST_F(StorageAccessAPIForOriginWithFirstPartySetsBrowserTest,
Permission_AutodeniedForServiceDomain) {
SetBlockThirdPartyCookies(true);
base::HistogramTester histogram_tester;
// Set cross-site cookies on all hosts.
SetCrossSiteCookieOnHost(kHostA);
SetCrossSiteCookieOnHost(kHostB);
NavigateToPageWithFrame(kHostB);
NavigateFrameTo(kHostA, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// The promise should be rejected; `khostB` is a service domain.
EXPECT_FALSE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(),
base::StrCat({"https://", kHostA, ":",
base::NumberToString(https_server().port())})));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Re-navigate iframe to a cross-site, cookie-reading endpoint, and verify
// that the cookie is not sent.
NavigateFrameTo(kHostA, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
content::FetchHistogramsFromChildProcesses();
EXPECT_THAT(histogram_tester.GetBucketCount(
kRequestOutcomeHistogram,
5 /*RequestOutcome::kDeniedByPrerequisites*/),
Gt(0));
}
IN_PROC_BROWSER_TEST_F(StorageAccessAPIForOriginWithFirstPartySetsBrowserTest,
Permission_AutodeniedForServiceDomainInIframe) {
SetBlockThirdPartyCookies(true);
base::HistogramTester histogram_tester;
// Set cross-site cookies on all hosts.
SetCrossSiteCookieOnHost(kHostA);
SetCrossSiteCookieOnHost(kHostB);
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// `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(), base::StrCat({"https://", kHostA, ":",
base::NumberToString(https_server().port())})));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is not sent.
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// However, a regular `requestStorageAccess` call should be granted;
// requesting on behalf of another domain is what is not acceptable.
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// When the frame subsequently navigates to an endpoint on kHostB,
// kHostB's cookies are sent, and the iframe retains storage
// access.
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()));
}
IN_PROC_BROWSER_TEST_F(StorageAccessAPIForOriginWithFirstPartySetsBrowserTest,
Permission_AutodeniedOutsideFirstPartySet) {
SetBlockThirdPartyCookies(true);
base::HistogramTester histogram_tester;
// Set cross-site cookies on all hosts.
SetCrossSiteCookieOnHost(kHostA);
SetCrossSiteCookieOnHost(kHostD);
NavigateToPageWithFrame(kHostA);
NavigateFrameTo(kHostD, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// `kHostD` cannot be granted access via `RequestStorageAccessForOrigin` in
// this configuration, because the requesting site (`kHostA`) is not in the
// same First-Party Set as the requested site (`kHostD`).
EXPECT_FALSE(storage::test::RequestStorageAccessForOrigin(
GetPrimaryMainFrame(),
base::StrCat({"https://", kHostD, ":",
base::NumberToString(https_server().port())})));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// Navigate iframe to a cross-site, cookie-reading endpoint, and verify that
// the cookie is not sent.
NavigateFrameTo(kHostD, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
content::FetchHistogramsFromChildProcesses();
EXPECT_THAT(histogram_tester.GetBucketCount(
kRequestOutcomeHistogram,
3 /*RequestOutcome::kDeniedByFirstPartySet*/),
Gt(0));
}
// Tests to validate that, when the `requestStorageAccessForOrigin` extension is
// explicitly disabled, or if the larger Storage Access API is disabled, it does
// not leak onto the document object.
class StorageAccessAPIForOriginExplicitlyDisabledBrowserTest
: public StorageAccessAPIBaseBrowserTest,
public testing::WithParamInterface<bool> {
public:
StorageAccessAPIForOriginExplicitlyDisabledBrowserTest()
: StorageAccessAPIBaseBrowserTest(true, true),
enable_standard_storage_access_api_(GetParam()) {}
protected:
std::vector<base::Feature> GetDisabledFeatures() override {
// The test should validate that either flag alone disables the API.
// Note that enabling the extension and not the standard API means both are
// disabled.
if (enable_standard_storage_access_api_) {
return {blink::features::kStorageAccessAPIForOriginExtension};
}
return {net::features::kStorageAccessAPI};
}
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
GetEnabledFeatures() override {
// When the standard API is enabled, return the parent class's enabled
// feature list. Otherwise, enable only the extension; this should not take
// effect.
if (enable_standard_storage_access_api_) {
return StorageAccessAPIBaseBrowserTest::GetEnabledFeatures();
}
return {{blink::features::kStorageAccessAPIForOriginExtension, {}}};
}
private:
bool enable_standard_storage_access_api_;
};
IN_PROC_BROWSER_TEST_P(StorageAccessAPIForOriginExplicitlyDisabledBrowserTest,
RsaForOriginNotPresentOnDocumentWhenExplicitlyDisabled) {
NavigateToPageWithFrame(kHostA);
// Ensure that the proposed extension is not available unless explicitly
// enabled.
EXPECT_TRUE(EvalJs(GetPrimaryMainFrame(),
"\"requestStorageAccessForOrigin\" in document === false")
.ExtractBool());
}
INSTANTIATE_TEST_CASE_P(
/* no prefix */,
StorageAccessAPIForOriginExplicitlyDisabledBrowserTest,
testing::Bool());
class StorageAccessAPIWithFirstPartySetsBrowserTest
: public StorageAccessAPIBaseBrowserTest {
public:
StorageAccessAPIWithFirstPartySetsBrowserTest()
: StorageAccessAPIBaseBrowserTest(false, false) {}
void SetUpCommandLine(base::CommandLine* command_line) override {
StorageAccessAPIBaseBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
network::switches::kUseFirstPartySet,
base::StrCat({R"({"primary": "https://)", kHostA,
R"(", "associatedSites": ["https://)", kHostB,
R"("]})"}));
}
protected:
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
GetEnabledFeatures() override {
return {
{net::features::kStorageAccessAPI,
{
{
net::features::kStorageAccessAPIAutoGrantInFPS.name,
"true",
},
{
net::features::kStorageAccessAPIAutoDenyOutsideFPS.name,
"true",
},
// Setting implicit grants to a non-zero number here demonstrates
// that when the auto-deny param is enabled, the implicit grants
// param doesn't matter, since the auto-deny param takes
// precedence.
{
"storage-access-api-implicit-grant-limit",
"5",
},
}},
};
}
};
IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
Permission_AutograntedWithinFirstPartySet) {
base::HistogramTester histogram_tester;
// Note: kHostA and kHostB are considered same-party due to the use of
// `network::switches::kUseFirstPartySet`.
SetBlockThirdPartyCookies(true);
SetCrossSiteCookieOnHost(kHostB);
NavigateToPageWithFrame(kHostA);
// kHostB starts without access:
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// kHostB can request storage access, and it is granted:
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
// When the frame subsequently navigates to an endpoint on kHostB,
// kHostB's cookies are sent, and the iframe retains storage
// access.
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()));
content::FetchHistogramsFromChildProcesses();
EXPECT_THAT(histogram_tester.GetBucketCount(
kRequestOutcomeHistogram,
0 /*RequestOutcome::kGrantedByFirstPartySet*/),
Gt(0));
}
IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
Permission_AutodeniedOutsideFirstPartySet) {
base::HistogramTester histogram_tester;
// Note: kHostA and kHostC are considered cross-party, since kHostA's set does
// not include kHostC.
SetBlockThirdPartyCookies(true);
SetCrossSiteCookieOnHost(kHostC);
NavigateToPageWithFrame(kHostA);
// Navigate iframe to kHostC and verify that the cookie is not sent.
NavigateFrameTo(kHostC, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// kHostC cannot request storage access.
EXPECT_FALSE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
NavigateFrameTo(kHostC, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
content::FetchHistogramsFromChildProcesses();
EXPECT_THAT(histogram_tester.GetBucketCount(
kRequestOutcomeHistogram,
3 /*RequestOutcome::kDeniedByFirstPartySet*/),
Gt(0));
}
class StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest
: public StorageAccessAPIBaseBrowserTest {
public:
StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest()
: StorageAccessAPIBaseBrowserTest(false, false) {}
protected:
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
GetEnabledFeatures() override {
return {
{net::features::kStorageAccessAPI,
{
{
net::features::kStorageAccessAPIAutoGrantInFPS.name,
"true",
},
{
net::features::kStorageAccessAPIAutoDenyOutsideFPS.name,
"false",
},
{
"storage-access-api-implicit-grant-limit",
"5",
},
}},
};
}
};
IN_PROC_BROWSER_TEST_F(
StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest,
ImplicitGrants) {
// When auto-deny is disabled (but auto-grant is enabled), implicit grants
// still work.
// Note: kHostA and kHostC are considered cross-party, since kHostA's set does
// not include kHostC.
SetBlockThirdPartyCookies(true);
SetCrossSiteCookieOnHost(kHostC);
NavigateToPageWithFrame(kHostA);
// Navigate iframe to kHostC and verify that the cookie is not sent.
NavigateFrameTo(kHostC, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// kHostC can request storage access, due to implicit grants.
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
NavigateToPageWithFrame(kHostB);
// Navigate iframe to kHostC and verify that the cookie is not sent.
NavigateFrameTo(kHostC, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "None");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// kHostC can request storage access here too, again due to
// implicit grants.
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
}
class StorageAccessAPIWithCHIPSBrowserTest
: public StorageAccessAPIBaseBrowserTest {
public:
StorageAccessAPIWithCHIPSBrowserTest()
: StorageAccessAPIBaseBrowserTest(
/*permission_grants_unpartitioned_storage=*/false,
/*is_storage_partitioned=*/false) {}
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
GetEnabledFeatures() override {
std::vector<base::test::ScopedFeatureList::FeatureAndParams> enabled =
StorageAccessAPIBaseBrowserTest::GetEnabledFeatures();
enabled.push_back({net::features::kPartitionedCookies, {}});
enabled.push_back(
{net::features::kPartitionedCookiesBypassOriginTrial, {}});
enabled.push_back(
{blink::features::kStorageAccessAPIForOriginExtension, {}});
return enabled;
}
};
IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithCHIPSBrowserTest,
RequestStorageAccess_CoexistsWithCHIPS) {
SetBlockThirdPartyCookies(true);
SetCrossSiteCookieOnHost(kHostB);
SetPartitionedCookieInContext(/*top_level_host=*/kHostA,
/*embedded_host=*/kHostB);
NavigateToPageWithFrame(kHostA);
// kHostB starts without unpartitioned cookies:
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "cross-site=b.test(partitioned)");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "cross-site=b.test(partitioned)");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// kHostB can request storage access, and it is granted (by an implicit
// grant):
EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
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.
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(),
"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()));
}
IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithCHIPSBrowserTest,
RequestStorageAccessForOrigin_CoexistsWithCHIPS) {
SetBlockThirdPartyCookies(true);
SetCrossSiteCookieOnHost(kHostB);
SetPartitionedCookieInContext(/*top_level_host=*/kHostA,
/*embedded_host=*/kHostB);
NavigateToPageWithFrame(kHostA);
// kHostB starts without unpartitioned cookies:
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(), "cross-site=b.test(partitioned)");
EXPECT_EQ(ReadCookiesViaJS(GetFrame()), "cross-site=b.test(partitioned)");
EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
// 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.
NavigateFrameTo(kHostB, "/echoheader?cookie");
EXPECT_EQ(GetFrameContent(),
"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