| // 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/containers/adapters.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/path_service.h" |
| #include "base/strings/escape.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/policy/policy_test_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/storage_access_api/storage_access_grant_permission_context.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/permissions/permission_request_manager.h" |
| #include "components/permissions/request_type.h" |
| #include "components/permissions/test/mock_permission_prompt_factory.h" |
| #include "components/permissions/test/permission_request_observer.h" |
| #include "components/policy/policy_constants.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/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 kOriginA[] = "https://a.test"; |
| constexpr char kUrlA[] = "https://a.test/random.path"; |
| constexpr char kHostASubdomain[] = "subdomain.a.test"; |
| constexpr char kHostB[] = "b.test"; |
| constexpr char kHostBSubdomain[] = "subdomain.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 kGrantIsImplicitHistogram[] = |
| "API.StorageAccess.GrantIsImplicit"; |
| |
| // Path for URL of custom response |
| const char* kEchoCookiesWithCorsPath = "/echocookieswithcors"; |
| |
| constexpr char kQueryStorageAccessPermission[] = |
| "navigator.permissions.query({name: 'storage-access'}).then(" |
| " (permission) => permission.state);"; |
| |
| enum class TestType { kFrame, kWorker }; |
| |
| // Helpers to express expected |
| std::pair<std::string, std::string> CookieBundle(const std::string& cookies) { |
| DCHECK_NE(cookies, "None"); |
| DCHECK_NE(cookies, ""); |
| return {cookies, cookies}; |
| } |
| |
| std::tuple<std::string, std::string, std::string> CookieBundleWithContent( |
| const std::string& cookies) { |
| DCHECK_NE(cookies, "None"); |
| DCHECK_NE(cookies, ""); |
| return {cookies, cookies, cookies}; |
| } |
| |
| std::pair<std::string, std::string> NoCookies() { |
| return { |
| "", // cookie string via `document.cookie` |
| "None", // cookie string via `echoheader?cookie` |
| }; |
| } |
| |
| std::tuple<std::string, std::string, std::string> NoCookiesWithContent() { |
| return { |
| "", // cookie string via `document.cookie` |
| "None", // cookie string via `echoheader?cookie` |
| "None", // cookie string via frame content (also via `echoheader?cookie`) |
| }; |
| } |
| |
| // Responds to a request to /echocookieswithcors with the cookies that were sent |
| // with the request. We can't use the default handler /echoheader?Cookie here, |
| // because it doesn't send the appropriate Access-Control-Allow-Origin and |
| // Access-Control-Allow-Credentials headers (which are required for this to |
| // work for cross-origin requests in the tests). |
| std::unique_ptr<net::test_server::HttpResponse> |
| HandleEchoCookiesWithCorsRequest(const net::test_server::HttpRequest& request) { |
| if (request.relative_url != kEchoCookiesWithCorsPath) { |
| return nullptr; |
| } |
| auto http_response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| std::string content = "None"; |
| // Get the 'Cookie' header that was sent in the request. |
| if (auto it = request.headers.find(net::HttpRequestHeaders::kCookie); |
| it != request.headers.end()) { |
| content = it->second; |
| } |
| |
| http_response->set_code(net::HTTP_OK); |
| http_response->set_content_type("text/plain"); |
| // Set the cors enabled headers. |
| if (auto it = request.headers.find(net::HttpRequestHeaders::kOrigin); |
| it != request.headers.end()) { |
| http_response->AddCustomHeader("Access-Control-Allow-Origin", it->second); |
| http_response->AddCustomHeader("Vary", "origin"); |
| http_response->AddCustomHeader("Access-Control-Allow-Credentials", "true"); |
| } |
| http_response->set_content(content); |
| |
| return http_response; |
| } |
| |
| std::string QueryPermission(content::RenderFrameHost* render_frame_host) { |
| return content::EvalJs(render_frame_host, kQueryStorageAccessPermission) |
| .ExtractString(); |
| } |
| |
| class StorageAccessAPIBaseBrowserTest : public policy::PolicyTest { |
| protected: |
| explicit StorageAccessAPIBaseBrowserTest(bool is_storage_partitioned) |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS), |
| is_storage_partitioned_(is_storage_partitioned) {} |
| |
| void SetUp() override { |
| features_.InitWithFeaturesAndParameters(GetEnabledFeatures(), |
| GetDisabledFeatures()); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| virtual std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() { |
| std::vector<base::test::FeatureRefAndParams> enabled({ |
| {blink::features::kStorageAccessAPI, |
| { |
| { |
| blink::features::kStorageAccessAPIAutoGrantInFPS.name, |
| "false", |
| }, |
| { |
| blink::features::kStorageAccessAPIAutoDenyOutsideFPS.name, |
| "false", |
| }, |
| { |
| blink::features::kStorageAccessAPIImplicitGrantLimit.name, |
| "0", |
| }, |
| }}, |
| }); |
| if (is_storage_partitioned_) { |
| enabled.push_back({net::features::kThirdPartyStoragePartitioning, {}}); |
| } |
| return enabled; |
| } |
| |
| virtual std::vector<base::test::FeatureRef> GetDisabledFeatures() { |
| std::vector<base::test::FeatureRef> 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()); |
| https_server_.RegisterRequestHandler( |
| base::BindRepeating(&HandleEchoCookiesWithCorsRequest)); |
| ASSERT_TRUE(https_server_.Start()); |
| |
| // All the sites used during these tests should have a cookie. |
| SetCrossSiteCookieOnDomain(kHostA); |
| SetCrossSiteCookieOnDomain(kHostB); |
| SetCrossSiteCookieOnDomain(kHostC); |
| SetCrossSiteCookieOnDomain(kHostD); |
| |
| permissions::PermissionRequestManager* manager = |
| permissions::PermissionRequestManager::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| prompt_factory_ = |
| std::make_unique<permissions::MockPermissionPromptFactory>(manager); |
| |
| // Don't respond to the prompt at all, by default. This forces any test that |
| // assumes a particular response to the prompt to explicitly declare that |
| // assumption by setting up the auto-response themselves. |
| prompt_factory_->set_response_type( |
| permissions::PermissionRequestManager::NONE); |
| } |
| |
| void TearDownOnMainThread() override { prompt_factory_.reset(); } |
| |
| void SetCrossSiteCookieOnDomain(const std::string& domain) { |
| GURL domain_url = GetURL(domain); |
| std::string cookie = base::StrCat({"cross-site=", domain}); |
| content::SetCookie( |
| browser()->profile(), domain_url, |
| base::StrCat({cookie, ";SameSite=None;Secure;Domain=", domain})); |
| ASSERT_THAT(content::GetCookies(browser()->profile(), domain_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)); |
| } |
| |
| void BlockAllCookiesOnHost(const std::string& host) { |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(host), ContentSetting::CONTENT_SETTING_BLOCK); |
| } |
| |
| 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 NavigateToPage(const std::string& host, const std::string& path) { |
| GURL main_url(https_server_.GetURL(host, path)); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| } |
| |
| 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) { |
| NavigateFrameTo(https_server_.GetURL(host, path)); |
| } |
| |
| void NavigateFrameTo(const GURL& url) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", url)); |
| } |
| |
| void NavigateNestedFrameTo(const std::string& host, const std::string& path) { |
| NavigateNestedFrameTo(https_server_.GetURL(host, path)); |
| } |
| |
| // Navigates the innermost frame to the given URL. (The web_contents is |
| // assumed to be showing a page containing an iframe that contains another |
| // iframe.) The navigation's initiator is the middle iframe (not the leaf). |
| void NavigateNestedFrameTo(const GURL& url) { |
| 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(); |
| } |
| |
| void NavigateToPageWithTwoFrames(const std::string& host) { |
| GURL main_url(https_server_.GetURL(host, "/two_iframes_blank.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| } |
| |
| void NavigateFirstFrameTo(const GURL& url) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents, "iframe1", url)); |
| } |
| |
| void NavigateSecondFrameTo(const GURL& url) { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents, "iframe2", url)); |
| } |
| |
| GURL EchoCookiesURL(const std::string& host) { |
| return https_server().GetURL(host, "/echoheader?cookie"); |
| } |
| |
| GURL RedirectViaHosts(const std::vector<std::string>& hosts, |
| const GURL& destination) { |
| GURL url = destination; |
| |
| for (const auto& host : base::Reversed(hosts)) { |
| url = https_server().GetURL( |
| host, base::StrCat( |
| {"/server-redirect?", base::EscapeQueryParamValue( |
| url.spec(), /*use_plus=*/true)})); |
| } |
| return url; |
| } |
| |
| std::string CookiesFromFetch(content::RenderFrameHost* render_frame_host, |
| const std::string& subresource_host) { |
| return storage::test::FetchWithCredentials( |
| render_frame_host, |
| https_server_.GetURL(subresource_host, kEchoCookiesWithCorsPath), |
| /*cors_enabled=*/true); |
| } |
| |
| // Reads cookies via `document.cookie` in the provided RFH, and via a |
| // subresource request from the provided RFH to the given host. These are |
| // bundled together to ensure that tests always check both, and that they're |
| // consistent. |
| std::pair<std::string, std::string> ReadCookies( |
| content::RenderFrameHost* render_frame_host, |
| const std::string& subresource_host) { |
| return { |
| content::EvalJs(render_frame_host, "document.cookie").ExtractString(), |
| CookiesFromFetch(render_frame_host, subresource_host), |
| }; |
| } |
| |
| // Reads cookies via `document.cookie` in the provided RFH, and via a |
| // subresource request from the provided RFH to the given host, and also |
| // includes the content of the provided RFH. This is most useful to check that |
| // the cookies accessible during navigation (via `echoheader?cookie`), during |
| // load (via `echoheader?cookie` for subresources), and via script execution |
| // (via `document.cookie`) are consistent with each other. |
| std::tuple<std::string, std::string, std::string> ReadCookiesAndContent( |
| content::RenderFrameHost* render_frame_host, |
| const std::string& subresource_host) { |
| auto [js_cookies, subresource_cookies] = |
| ReadCookies(render_frame_host, subresource_host); |
| return { |
| js_cookies, |
| subresource_cookies, |
| storage::test::GetFrameContent(render_frame_host), |
| }; |
| } |
| |
| 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); |
| } |
| |
| content::RenderFrameHost* GetFirstFrame() { return GetFrame(); } |
| |
| content::RenderFrameHost* GetSecondFrame() { |
| return ChildFrameAt(GetPrimaryMainFrame(), 1); |
| } |
| |
| net::test_server::EmbeddedTestServer& https_server() { return https_server_; } |
| |
| bool IsStoragePartitioned() const { return is_storage_partitioned_; } |
| |
| permissions::MockPermissionPromptFactory* prompt_factory() { |
| return prompt_factory_.get(); |
| } |
| |
| private: |
| net::test_server::EmbeddedTestServer https_server_; |
| base::test::ScopedFeatureList features_; |
| bool is_storage_partitioned_; |
| std::unique_ptr<permissions::MockPermissionPromptFactory> prompt_factory_; |
| }; |
| |
| // Test fixture for core Storage Access API functionality, guaranteed by spec. |
| // This fixture should use the minimal set of features/params. |
| class StorageAccessAPIBrowserTest : public StorageAccessAPIBaseBrowserTest { |
| public: |
| StorageAccessAPIBrowserTest() |
| : StorageAccessAPIBaseBrowserTest(/*is_storage_partitioned=*/false) {} |
| }; |
| |
| // Check default values for permissions.query on storage-access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryDefault) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/echoheader?cookie"); |
| |
| EXPECT_EQ(QueryPermission(GetPrimaryMainFrame()), "granted"); |
| EXPECT_EQ(QueryPermission(GetFrame()), "prompt"); |
| } |
| |
| // Check default values for permissions.query on storage-access when 3p cookie |
| // is allowed. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| PermissionQueryDefault_AllowCrossSiteCookie) { |
| SetBlockThirdPartyCookies(false); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/echoheader?cookie"); |
| |
| EXPECT_EQ(QueryPermission(GetPrimaryMainFrame()), "granted"); |
| EXPECT_EQ(QueryPermission(GetFrame()), "prompt"); |
| } |
| |
| // Test that permissions.query changes to "granted" when a storage access |
| // request was successful. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryGranted) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/echoheader?cookie"); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(QueryPermission(GetFrame()), "prompt"); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| // Grant initial permission. |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(QueryPermission(GetFrame()), "granted"); |
| |
| // Ensure that after a navigation the permission state is preserved. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/echoheader?cookie"); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(QueryPermission(GetFrame()), "granted"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryCrossSite) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostB); |
| NavigateFrameTo(kHostA, "/echoheader?cookie"); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(QueryPermission(GetFrame()), "prompt"); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| // Grant initial permission. |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(QueryPermission(GetFrame()), "granted"); |
| |
| // Ensure that the scope of the permission grant is for the entire site. |
| NavigateFrameTo(kHostASubdomain, "/echoheader?cookie"); |
| EXPECT_EQ(QueryPermission(GetFrame()), "granted"); |
| |
| // The permission should not be available cross-site. |
| NavigateFrameTo(kHostC, "/echoheader?cookie"); |
| EXPECT_EQ(QueryPermission(GetFrame()), "prompt"); |
| } |
| |
| // When 3p cookie is allowed, check that in a A(B) frame tree, the embedded |
| // B iframe can access cookie without requesting, but the prompt is still shown |
| // if the iframe makes the request. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesAccess_CrossSiteIframe_AllowCrossSiteCookie) { |
| SetBlockThirdPartyCookies(false); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| // The cross-site iframe has cookie access since 3p cookies are allowed. |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), |
| CookieBundleWithContent("cross-site=b.test")); |
| |
| // TODO(https://crbug.com/1441133): We should either make sure there is way to |
| // let developer check whether they need to call rSA(), or no prompt is shown |
| // when 3p cookie is allowed. |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(1, prompt_factory()->TotalRequestCount()); |
| } |
| |
| // Validate that a cross-site iframe can bypass third-party cookie blocking via |
| // the Storage Access API. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameRequestsAccess_CrossSiteIframe) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test")); |
| } |
| |
| // Validate that if an iframe obtains access, then cookies become unblocked for |
| // just that top-level/third-party combination and are still blocked for other |
| // combinations. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameRequestsAccess_CrossSiteIframe_UnrelatedSites) { |
| SetBlockThirdPartyCookies(true); |
| base::HistogramTester histogram_tester; |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostC)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| 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 a nested A(B(B)) iframe can obtain cookie access, and that that |
| // access is not shared with the "middle" B iframe. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_InnerRequestsAccess) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), NoCookies()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB), |
| CookieBundle("cross-site=b.test")); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), NoCookies()); |
| } |
| |
| // Validate that in a A(B(B)) frame tree, the middle B iframe can obtain access, |
| // and that access is not shared with the leaf B iframe. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_MiddleRequestsAccess) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), NoCookies()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test")); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB), NoCookies()); |
| |
| // Subresource request from the cross-site iframe to an end point that's |
| // same-origin with the top-level does not enable cookie access. |
| EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostA), "None"); |
| EXPECT_EQ(CookiesFromFetch(GetNestedFrame(), kHostA), "None"); |
| } |
| |
| // Validate that in a A(B(C)) frame tree, the C leaf iframe can obtain cookie |
| // access. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_DistinctSites) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostC), |
| NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostC), |
| CookieBundle("cross-site=c.test")); |
| } |
| |
| // Validate that cross-site sibling iframes cannot take advantage of each |
| // other's granted permission. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesCrossSiteSiblingIFrameRequestsAccess) { |
| NavigateToPageWithTwoFrames(kHostA); |
| NavigateFirstFrameTo(EchoCookiesURL(kHostB)); |
| NavigateSecondFrameTo(EchoCookiesURL(kHostC)); |
| |
| // Verify that both same-origin subresource request and cross-origin |
| // subresource request can access cookies for the kHostB iframe. |
| ASSERT_EQ(CookiesFromFetch(GetFirstFrame(), kHostB), "cross-site=b.test"); |
| ASSERT_EQ(CookiesFromFetch(GetFirstFrame(), kHostC), "cross-site=c.test"); |
| |
| // Verify that both same-origin subresource request and cross-origin |
| // subresource request can access cookies for the kHostC iframe. |
| ASSERT_EQ(CookiesFromFetch(GetSecondFrame(), kHostC), "cross-site=c.test"); |
| ASSERT_EQ(CookiesFromFetch(GetSecondFrame(), kHostB), "cross-site=b.test"); |
| |
| SetBlockThirdPartyCookies(true); |
| // Navigate the first iframe to kHostB and grant Storage Access. |
| NavigateFirstFrameTo(EchoCookiesURL(kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFirstFrame(), kHostB), |
| NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFirstFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetFirstFrame())); |
| EXPECT_EQ(ReadCookies(GetFirstFrame(), kHostB), |
| CookieBundle("cross-site=b.test")); |
| |
| // Navigate the second iframe to kHostC and grant Storage Access. |
| NavigateSecondFrameTo(EchoCookiesURL(kHostC)); |
| EXPECT_EQ(ReadCookiesAndContent(GetSecondFrame(), kHostC), |
| NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetSecondFrame())); |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetSecondFrame())); |
| EXPECT_EQ(ReadCookies(GetSecondFrame(), kHostC), |
| CookieBundle("cross-site=c.test")); |
| |
| // Verify same-origin subresource request has cookie access whereas the |
| // cross-origin subresource request does not for the kHostB iframe. |
| EXPECT_EQ(CookiesFromFetch(GetFirstFrame(), kHostB), "cross-site=b.test"); |
| EXPECT_EQ(CookiesFromFetch(GetFirstFrame(), kHostC), "None"); |
| |
| // Verify same-origin subresource request has cookie access whereas the |
| // cross-origin subresource request does not for the kHostC iframe. |
| EXPECT_EQ(CookiesFromFetch(GetSecondFrame(), kHostC), "cross-site=c.test"); |
| EXPECT_EQ(CookiesFromFetch(GetSecondFrame(), kHostB), "None"); |
| } |
| |
| // Validate that the Storage Access API does not override any explicit user |
| // settings to block storage access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameThirdPartyExceptions) { |
| SetBlockThirdPartyCookies(true); |
| BlockAllCookiesOnHost(kHostB); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), NoCookies()); |
| } |
| |
| // Validate that user settings take precedence for the leaf in a A(B(B)) frame |
| // tree. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameThirdPartyExceptions_NestedSameSite) { |
| SetBlockThirdPartyCookies(true); |
| BlockAllCookiesOnHost(kHostB); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB), NoCookies()); |
| } |
| |
| // Validate that user settings take precedence for the leaf in a A(B(C)) frame |
| // tree. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameThirdPartyExceptions_NestedCrossSite) { |
| SetBlockThirdPartyCookies(true); |
| BlockAllCookiesOnHost(kHostC); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostC), |
| NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostC), NoCookies()); |
| } |
| |
| // Validates that once a grant is removed access is also removed. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| ThirdPartyGrantsDeletedAccess) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test")); |
| |
| // Manually delete all our grants. |
| HostContentSettingsMap* settings_map = |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()); |
| settings_map->ClearSettingsForOneType(ContentSettingsType::STORAGE_ACCESS); |
| // Verify cookie cannot be accessed. |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), NoCookies()); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| // Validate that if the iframe's origin is opaque, it cannot obtain storage |
| // access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, OpaqueOriginRejects) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| ASSERT_TRUE(ExecuteScript( |
| GetPrimaryMainFrame(), |
| "document.querySelector('iframe').sandbox='allow-scripts';")); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_FALSE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| // Validate that if the iframe is sandboxed and allows scripts but is missing |
| // the Storage Access sandbox tag, the iframe cannot obtain storage access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| MissingSandboxTokenRejects) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| ASSERT_TRUE(ExecuteScript(GetPrimaryMainFrame(), |
| "document.querySelector('iframe').sandbox='allow-" |
| "scripts allow-same-origin';")); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_FALSE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| // Validate that if the iframe is sandboxed and has the Storage Access sandbox |
| // tag, the iframe can obtain storage access. |
| IN_PROC_BROWSER_TEST_F(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(EchoCookiesURL(kHostB)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| } |
| |
| // Validates that expired grants don't get reused. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, ThirdPartyGrantsExpiry) { |
| base::HistogramTester histogram_tester; |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostC)); |
| |
| // Manually create a pre-expired grant and ensure it doesn't grant access for |
| // HostB. |
| 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); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| // The iframe should request for new grant since the existing one is expired. |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Validate that only one permission was newly granted. |
| histogram_tester.ExpectUniqueSample(kRequestOutcomeHistogram, |
| RequestOutcome::kGrantedByUser, 1); |
| |
| // The nested iframe reuses the existing grant without requesting. |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostC), |
| CookieBundle("cross-site=c.test")); |
| |
| // We don't get to record a sample for the "reuse" case, so that histogram |
| // still only has 1 sample in total. |
| histogram_tester.ExpectTotalCount(kRequestOutcomeHistogram, 1); |
| |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostC)); |
| // Only when the initiator is the frame that's been navigated can inherit |
| // per-frame storage access. |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostC), |
| NoCookiesWithContent()); |
| } |
| |
| // Validate that if an iframe navigates itself to a same-origin endpoint, and |
| // that navigation does not include any cross-origin redirects, the new document |
| // can inherit storage access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| Navigation_SelfInitiated_SameOrigin_Preserves) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE( |
| content::NavigateToURLFromRenderer(GetFrame(), EchoCookiesURL(kHostB))); |
| |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), |
| CookieBundleWithContent("cross-site=b.test")); |
| } |
| |
| // Validate that if an iframe is navigated (by a cross-site initiator) to a |
| // same-origin endpoint, and that navigation does not include any cross-origin |
| // redirects, the new document cannot inherit storage access. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| Navigation_NonSelfInitiated_SameOriginDestination_CrossSiteInitiator) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| // The navigation for this frame does not include cookies, since the initiator |
| // is cross-site from the destination, and the initiator did not have storage |
| // access. |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| } |
| |
| // Validate that if an iframe is navigated (by a same-site initiator) to a |
| // same-origin endpoint (even if the navigation does not include any |
| // cross-origin redirects), the new document cannot inherit storage access. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| Navigation_NonSelfInitiated_SameOriginDestination_SameSiteInitiator) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain)); |
| |
| ASSERT_EQ(ReadCookies(GetFrame(), kHostB), NoCookies()); |
| ASSERT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| // The navigation itself carried cookies due to the initiator's storage |
| // access, but the new document did not inherit storage access, since the |
| // navigation was not self-initiated. |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| std::make_tuple("", "None", "cross-site=b.test")); |
| } |
| |
| // Validate that if an iframe is navigated (by a same-site initiator) to a |
| // same-origin endpoint (even if the navigation does not include any |
| // cross-origin redirects, and the navigated frame has obtained storage access |
| // already), the new document cannot inherit storage access. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| Navigation_NonSelfInitiated_SameOriginDestination_SameSiteInitiator_TargetHasStorageAccess) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain)); |
| |
| ASSERT_EQ(ReadCookies(GetFrame(), kHostB), NoCookies()); |
| ASSERT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| |
| NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| // The navigation itself carried cookies due to the initiator's storage |
| // access, but the new document did not inherit storage access, since the |
| // navigation was not self-initiated. |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| std::make_tuple("", "None", "cross-site=b.test")); |
| } |
| |
| // Validate that if an iframe navigates itself to a same-site cross-origin |
| // endpoint, and that navigation does not include any cross-origin redirects, |
| // the new document cannot inherit storage access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| Navigation_SelfInitiated_SameSiteCrossOrigin) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(content::NavigateToURLFromRenderer( |
| GetFrame(), EchoCookiesURL(kHostBSubdomain))); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| // The navigation itself carried cookies from the previous document's storage |
| // access, but the new document did not inherit storage access. |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), |
| std::make_tuple("", "None", "cross-site=b.test")); |
| } |
| |
| // Validate that if an iframe navigates itself to a cross-site endpoint, and |
| // that navigation does not include any cross-origin redirects, the new document |
| // cannot inherit storage access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| Navigation_SelfInitiated_CrossSite) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE( |
| content::NavigateToURLFromRenderer(GetFrame(), EchoCookiesURL(kHostC))); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), NoCookiesWithContent()); |
| } |
| |
| // Validate that if an iframe navigates itself to a same-origin endpoint, but |
| // that navigation include a cross-origin redirect, the new document |
| // cannot inherit storage access. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| Navigation_SelfInitiated_SameOrigin_CrossOriginRedirect) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| GURL dest = EchoCookiesURL(kHostB); |
| EXPECT_TRUE(content::NavigateToURLFromRenderer( |
| GetFrame(), |
| /*url=*/ |
| RedirectViaHosts({kHostBSubdomain}, dest), |
| /*expected_commit_url=*/dest)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| // The navigation itself carried cookies from the previous document's storage |
| // access, but the new document did not inherit storage access. |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostBSubdomain), |
| std::make_tuple("", "None", "cross-site=b.test")); |
| } |
| |
| // Validate that if an iframe navigates itself to a same-origin endpoint, and |
| // that navigation includes a cross-origin redirect (even if there's a |
| // subsequent same-origin redirect), the new document cannot inherit storage |
| // access. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| Navigation_SelfInitiated_SameOrigin_CrossSiteAndSameSiteRedirects) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| GURL dest = EchoCookiesURL(kHostB); |
| EXPECT_TRUE(content::NavigateToURLFromRenderer( |
| GetFrame(), |
| /*url=*/ |
| RedirectViaHosts({kHostBSubdomain, kHostB}, dest), |
| /*expected_commit_url=*/dest)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| // The navigation itself carried cookies from the previous document's storage |
| // access, but the new document did not inherit storage access. |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostBSubdomain), |
| std::make_tuple("", "None", "cross-site=b.test")); |
| } |
| |
| // Validate that in a A(B) frame tree, the embedded B iframe can obtain cookie |
| // access if requested and got accepted. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| EmbeddedCrossSiteCookieAccess_Accept) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(1, prompt_factory()->TotalRequestCount()); |
| EXPECT_EQ(1, prompt_factory()->RequestTypeSeen( |
| permissions::RequestType::kStorageAccess)); |
| } |
| |
| // Validate that in a A(B) frame tree, the embedded B iframe can not obtain |
| // cookie access if requested and got denied. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| EmbeddedCrossSiteCookieAccess_Deny) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| EXPECT_FALSE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(1, prompt_factory()->TotalRequestCount()); |
| EXPECT_EQ(1, prompt_factory()->RequestTypeSeen( |
| permissions::RequestType::kStorageAccess)); |
| } |
| |
| // Validate that in a A(A) frame tree, the inner A iframe can obtain cookie |
| // access by default. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| EmbeddedSameOriginCookieAccess) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(0, prompt_factory()->TotalRequestCount()); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), |
| CookieBundleWithContent("cross-site=a.test")); |
| } |
| |
| // Validate that in a A(sub.A) frame tree, the inner A iframe can obtain cookie |
| // access by default. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| EmbeddedSameSiteCookieAccess) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostASubdomain)); |
| |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_EQ(0, prompt_factory()->TotalRequestCount()); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), |
| CookieBundleWithContent("cross-site=a.test")); |
| } |
| |
| // Validate that in a A(B(A)) frame tree, the inner A iframe can obtain cookie |
| // access after requesting access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| NestedSameOriginCookieAccess_CrossSiteAncestorChain) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(kHostA, "/empty.html"); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostA), NoCookies()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(0, prompt_factory()->TotalRequestCount()); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostA), |
| CookieBundle("cross-site=a.test")); |
| } |
| |
| // Validate that in a A(B(sub.A)) frame tree, the inner iframe can obtain cookie |
| // access after requesting access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| NestedSameSiteCookieAccess_CrossSiteAncestorChain) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(kHostASubdomain, "/empty.html"); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostA), NoCookies()); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(0, prompt_factory()->TotalRequestCount()); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostASubdomain), |
| CookieBundle("cross-site=a.test")); |
| } |
| |
| class StorageAccessAPIStorageBrowserTest |
| : public StorageAccessAPIBaseBrowserTest, |
| public testing::WithParamInterface<std::tuple<TestType, bool>> { |
| public: |
| StorageAccessAPIStorageBrowserTest() |
| : StorageAccessAPIBaseBrowserTest(std::get<1>(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(); } |
| |
| 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())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/browsing_data/site_data.html"); |
| |
| ExpectStorage(GetFrame(), DoesPermissionGrantStorage()); |
| EXPECT_FALSE(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())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html"); |
| |
| ExpectStorage(GetNestedFrame(), DoesPermissionGrantStorage()); |
| EXPECT_FALSE(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_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| // Create a second tab to test communication between tabs. |
| NavigateToNewTabWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/browsing_data/site_data.html"); |
| |
| permissions::PermissionRequestManager::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()) |
| ->set_auto_response_for_test( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true); |
| EXPECT_FALSE(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())); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/browsing_data/site_data.html"); |
| |
| storage::test::ExpectCrossTabInfoForFrame(GetFrame(), |
| DoesPermissionGrantStorage()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(/*no prefix*/, |
| StorageAccessAPIStorageBrowserTest, |
| testing::Combine(testing::Values(TestType::kFrame, |
| TestType::kWorker), |
| testing::Bool())); |
| |
| class StorageAccessAPIWithFirstPartySetsBrowserTest |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| StorageAccessAPIWithFirstPartySetsBrowserTest() |
| : StorageAccessAPIBaseBrowserTest(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"("])", |
| R"(, "serviceSites": ["https://)", kHostD, R"("]})"})); |
| } |
| |
| protected: |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| return { |
| {blink::features::kStorageAccessAPI, |
| { |
| { |
| blink::features::kStorageAccessAPIAutoGrantInFPS.name, |
| "true", |
| }, |
| { |
| blink::features::kStorageAccessAPIAutoDenyOutsideFPS.name, |
| "true", |
| }, |
| { |
| blink::features::kStorageAccessAPIImplicitGrantLimit.name, |
| "0", |
| }, |
| }}, |
| }; |
| } |
| }; |
| |
| 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); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| |
| EXPECT_THAT(histogram_tester.GetBucketCount( |
| kRequestOutcomeHistogram, |
| 0 /*RequestOutcome::kGrantedByFirstPartySet*/), |
| Gt(0)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest, |
| Permission_AutodeniedForServiceDomain) { |
| SetBlockThirdPartyCookies(true); |
| base::HistogramTester histogram_tester; |
| |
| NavigateToPageWithFrame(kHostD); |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| // The promise should be rejected; `khostD` is a service domain. |
| EXPECT_FALSE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| EXPECT_THAT(histogram_tester.GetBucketCount( |
| kRequestOutcomeHistogram, |
| 5 /*RequestOutcome::kDeniedByPrerequisites*/), |
| Gt(0)); |
| // Ensure that the denied state is not exposed to developers, per the spec. |
| EXPECT_EQ(QueryPermission(GetFrame()), "prompt"); |
| } |
| |
| 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); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| // kHostC cannot request storage access. |
| EXPECT_FALSE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), NoCookiesWithContent()); |
| 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) {} |
| |
| protected: |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| return { |
| {blink::features::kStorageAccessAPI, |
| { |
| { |
| blink::features::kStorageAccessAPIAutoGrantInFPS.name, |
| "true", |
| }, |
| { |
| blink::features::kStorageAccessAPIAutoDenyOutsideFPS.name, |
| "false", |
| }, |
| { |
| blink::features::kStorageAccessAPIImplicitGrantLimit.name, |
| "5", |
| }, |
| }}, |
| }; |
| } |
| }; |
| |
| // Validate that when auto-deny-outside-fps is disabled (but auto-grant is |
| // enabled), implicit grants still work. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest, |
| ImplicitGrants) { |
| // Note: kHostA and kHostC are considered cross-party, since kHostA's set does |
| // not include kHostC. |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| // kHostC can request storage access, due to implicit grants. |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateToPageWithFrame(kHostB); |
| NavigateFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), NoCookiesWithContent()); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| } |
| |
| class StorageAccessAPIWithCHIPSBrowserTest |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| StorageAccessAPIWithCHIPSBrowserTest() |
| : StorageAccessAPIBaseBrowserTest( |
| /*is_storage_partitioned=*/false) {} |
| |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| std::vector<base::test::FeatureRefAndParams> enabled = |
| StorageAccessAPIBaseBrowserTest::GetEnabledFeatures(); |
| enabled.push_back({net::features::kPartitionedCookies, {}}); |
| return enabled; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithCHIPSBrowserTest, |
| RequestStorageAccess_CoexistsWithCHIPS) { |
| SetBlockThirdPartyCookies(true); |
| |
| SetPartitionedCookieInContext(/*top_level_host=*/kHostA, |
| /*embedded_host=*/kHostB); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), |
| CookieBundleWithContent("cross-site=b.test(partitioned)")); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), |
| CookieBundle("cross-site=b.test; cross-site=b.test(partitioned)")); |
| } |
| |
| class StorageAccessAPIEnterprisePolicyBrowserTest |
| : public StorageAccessAPIBaseBrowserTest, |
| public testing::WithParamInterface< |
| /* (origin, content_setting, is_storage_partitioned) */ |
| std::tuple<const char*, ContentSetting, bool>> { |
| public: |
| StorageAccessAPIEnterprisePolicyBrowserTest() |
| : StorageAccessAPIBaseBrowserTest(std::get<2>(GetParam())) {} |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| policy::PolicyTest::SetUpInProcessBrowserTestFixture(); |
| policy::PolicyMap policies; |
| SetPolicy(&policies, |
| policy::key::kDefaultThirdPartyStoragePartitioningSetting, |
| base::Value(GetContentSetting())); |
| base::Value::List origins; |
| origins.Append(base::Value(GetContentOrigin())); |
| SetPolicy(&policies, |
| policy::key::kThirdPartyStoragePartitioningBlockedForOrigins, |
| base::Value(std::move(origins))); |
| UpdateProviderPolicy(policies); |
| } |
| |
| bool ExpectPartitionedStorage() const { |
| // We only expect storage to be partitioned if the base::Feature is enabled |
| // and the default content setting isn't BLOCK and the origin block list |
| // doesn't match a.test (paths are ignored) |
| return IsStoragePartitioned() && |
| GetContentSetting() != CONTENT_SETTING_BLOCK && |
| GetContentOrigin() != kHostA && GetContentOrigin() != kOriginA && |
| GetContentOrigin() != kUrlA; |
| } |
| |
| // Derive a test name from parameter information. |
| static std::string TestName(const ::testing::TestParamInfo<ParamType>& info) { |
| const char* origin = std::get<0>(info.param); |
| ContentSetting content_setting = std::get<1>(info.param); |
| bool is_storage_partitioned = std::get<2>(info.param); |
| return base::JoinString( |
| { |
| origin == kHostA ? "kHostA" |
| : origin == kOriginA ? "kOriginA" |
| : origin == kUrlA ? "kUrlA" |
| : origin == kHostASubdomain ? "kHostASubdomain" |
| : origin == kHostB ? "kHostB" |
| : "empty", |
| content_setting == CONTENT_SETTING_DEFAULT ? "DEFAULT" |
| : content_setting == CONTENT_SETTING_ALLOW ? "ALLOW" |
| : "BLOCK", |
| is_storage_partitioned ? "Partitioned" : "Unpartitioned", |
| }, |
| "_"); |
| } |
| |
| private: |
| ContentSetting GetContentSetting() const { return std::get<1>(GetParam()); } |
| const char* GetContentOrigin() const { return std::get<0>(GetParam()); } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /*no prefix*/, |
| StorageAccessAPIEnterprisePolicyBrowserTest, |
| testing::Combine( |
| testing::Values(kHostA, kOriginA, kUrlA, kHostASubdomain, kHostB, ""), |
| testing::Values(CONTENT_SETTING_DEFAULT, |
| CONTENT_SETTING_ALLOW, |
| CONTENT_SETTING_BLOCK), |
| testing::Bool()), |
| StorageAccessAPIEnterprisePolicyBrowserTest::TestName); |
| |
| IN_PROC_BROWSER_TEST_P(StorageAccessAPIEnterprisePolicyBrowserTest, |
| PartitionedStorage) { |
| // Navigate to Origin B, setup storage, and expect storage. |
| NavigateToPage(kHostB, "/browsing_data/site_data.html"); |
| storage::test::ExpectStorageForFrame(GetPrimaryMainFrame(), |
| /*include_cookies=*/false, |
| /*expected=*/false); |
| storage::test::SetStorageForFrame(GetPrimaryMainFrame(), |
| /*include_cookies=*/false); |
| storage::test::ExpectStorageForFrame(GetPrimaryMainFrame(), |
| /*include_cookies=*/false, |
| /*expected=*/true); |
| |
| // Navigate to Origin A w/ Frame B and expect storage if not partitioned. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/browsing_data/site_data.html"); |
| storage::test::ExpectStorageForFrame(GetFrame(), /*include_cookies=*/false, |
| !ExpectPartitionedStorage()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| EnsureOnePromptDenialSuffices) { |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| { |
| // The first request should show a prompt, which is denied. |
| permissions::PermissionRequestObserver pre_observer( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| ASSERT_FALSE(pre_observer.request_shown()); |
| ASSERT_FALSE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| ASSERT_TRUE(pre_observer.request_shown()); |
| ASSERT_EQ(prompt_factory()->TotalRequestCount(), 1); |
| } |
| { |
| // However, subsequent requests should not re-prompt. |
| permissions::PermissionRequestObserver post_observer( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| // Validate that there's no stale data. |
| ASSERT_FALSE(post_observer.request_shown()); |
| |
| EXPECT_FALSE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| // Verify no prompt was shown after the first one was already denied. |
| EXPECT_FALSE(post_observer.request_shown()); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| DismissalAllowsFuturePrompts) { |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DISMISS); |
| |
| { |
| // The first request should show a prompt, which is dismissed. |
| permissions::PermissionRequestObserver observer( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| EXPECT_FALSE( |
| content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| ASSERT_TRUE(observer.request_shown()); |
| EXPECT_EQ(false, |
| content::EvalJs(GetFrame(), "document.hasStorageAccess()")); |
| ASSERT_EQ(prompt_factory()->TotalRequestCount(), 1); |
| } |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| { |
| // However, subsequent requests should be able to re-prompt. |
| permissions::PermissionRequestObserver observer( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Verify a prompt was shown. |
| EXPECT_TRUE(observer.request_shown()); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 2); |
| } |
| } |
| |
| class StorageAccessAPIWithImplicitGrantsBrowserTest |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| StorageAccessAPIWithImplicitGrantsBrowserTest() |
| : StorageAccessAPIBaseBrowserTest(/*is_storage_partitioned=*/false) {} |
| |
| protected: |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| return { |
| {blink::features::kStorageAccessAPI, |
| { |
| { |
| blink::features::kStorageAccessAPIAutoGrantInFPS.name, |
| "false", |
| }, |
| { |
| blink::features::kStorageAccessAPIAutoDenyOutsideFPS.name, |
| "false", |
| }, |
| { |
| // We use a low, but nonzero, number for the limit so that we |
| // can verify that implicit grants are usable, and verify what |
| // happens when we exceed the limit. |
| blink::features::kStorageAccessAPIImplicitGrantLimit.name, |
| "2", |
| }, |
| }}, |
| }; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithImplicitGrantsBrowserTest, |
| ImplicitGrantsAllowAccess) { |
| base::HistogramTester histogram_tester; |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| // Access should be allowed by the first implicit grant. |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| histogram_tester.ExpectUniqueSample(kGrantIsImplicitHistogram, |
| /*sample=*/true, 1); |
| |
| NavigateToPageWithFrame(kHostC); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| // Access should be allowed by the second implicit grant. |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| histogram_tester.ExpectUniqueSample(kGrantIsImplicitHistogram, |
| /*sample=*/true, 2); |
| |
| NavigateToPageWithFrame(kHostD); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| // Access is denied since kHostB has already exhausted both of its implicit |
| // grants, and we've set the prompt_factory to always deny. |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| histogram_tester.ExpectBucketCount( |
| kRequestOutcomeHistogram, /*sample=*/RequestOutcome::kDeniedByUser, 1); |
| |
| NavigateToPageWithFrame(kHostB); |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| // Other embeds can still obtain access via their own implicit grants. |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| histogram_tester.ExpectUniqueSample(kGrantIsImplicitHistogram, |
| /*sample=*/true, 3); |
| } |
| |
| } // namespace |