| // 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 <initializer_list> |
| #include <memory> |
| #include <string_view> |
| |
| #include "base/containers/adapters.h" |
| #include "base/containers/map_util.h" |
| #include "base/containers/span.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/path_service.h" |
| #include "base/strings/escape.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/types/optional_util.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/browser/webid/federated_identity_permission_context.h" |
| #include "chrome/browser/webid/federated_identity_permission_context_factory.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/content_settings/browser/page_specific_content_settings.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/content_settings_types.h" |
| #include "components/content_settings/core/common/content_settings_utils.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/features.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/navigation_handle.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_paths.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 "content/public/test/url_loader_interceptor.h" |
| #include "net/base/features.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/cookies/canonical_cookie_test_helpers.h" |
| #include "net/cookies/cookie_partition_key_collection.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/install_default_websocket_handlers.h" |
| #include "net/test/embedded_test_server/request_handler_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/features_generated.h" |
| #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-forward.h" |
| #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "url/origin.h" |
| |
| using content::BrowserThread; |
| using testing::_; |
| using testing::AllOf; |
| using testing::Contains; |
| using testing::Each; |
| using testing::Gt; |
| using testing::IsEmpty; |
| using testing::IsSupersetOf; |
| using testing::Key; |
| using testing::Not; |
| using testing::Pair; |
| using testing::StartsWith; |
| using testing::UnorderedElementsAre; |
| |
| using PathAndHeaderMapMatchers = std::initializer_list<testing::Matcher< |
| std::pair<std::string, net::test_server::HttpRequest::HeaderMap>>>; |
| using HeaderMapMatchers = std::initializer_list< |
| testing::Matcher<std::pair<std::string, std::string>>>; |
| |
| namespace { |
| |
| constexpr std::string_view kHostA = "a.test"; |
| constexpr std::string_view kOriginA = "https://a.test"; |
| constexpr std::string_view kOriginB = "https://b.test"; |
| constexpr std::string_view kUrlA = "https://a.test/random.path"; |
| constexpr std::string_view kHostASubdomain = "subdomain.a.test"; |
| constexpr std::string_view kHostB = "b.test"; |
| constexpr std::string_view kHostBSubdomain = "subdomain.b.test"; |
| constexpr std::string_view kHostBSubdomain2 = "subdomain2.b.test"; |
| constexpr std::string_view kHostC = "c.test"; |
| constexpr std::string_view kHostD = "d.test"; |
| |
| constexpr std::string_view kUseCounterHistogram = "Blink.UseCounter.Features"; |
| constexpr std::string_view kRequestOutcomeHistogram = |
| "API.StorageAccess.RequestOutcome"; |
| constexpr std::string_view kGrantIsImplicitHistogram = |
| "API.StorageAccess.GrantIsImplicit"; |
| constexpr std::string_view kNetRequestHistogram = |
| "Net.HttpJob.StorageAccessNetRequest2"; |
| |
| // Path for URL of custom response |
| const char* kEchoCookiesWithCorsPath = "/echocookieswithcors"; |
| constexpr char kRetryPath[] = "/retry-with-storage-access"; |
| |
| constexpr char kQueryStorageAccessPermission[] = |
| "navigator.permissions.query({name: 'storage-access'}).then(" |
| " (permission) => permission.state);"; |
| |
| constexpr char kHeaderNotProvidedSentinel[] = "HEADER_NOT_PROVIDED"; |
| |
| constexpr char kSecFetchStorageAccess[] = "Sec-Fetch-Storage-Access"; |
| |
| const ContentSettingPatternSource kExpectedSettingDefault( |
| ContentSettingsPattern::Wildcard(), |
| ContentSettingsPattern::Wildcard(), |
| content_settings::ContentSettingToValue(CONTENT_SETTING_ASK), |
| content_settings::ProviderType::kDefaultProvider, |
| /*incognito=*/false); |
| |
| enum class TestType { kFrame, kWorker }; |
| |
| // Helpers to express expected |
| std::pair<std::string, std::string> CookieBundle(std::string_view cookies) { |
| DCHECK_NE(cookies, "None"); |
| DCHECK_NE(cookies, ""); |
| return {std::string(cookies), std::string(cookies)}; |
| } |
| |
| std::tuple<std::string, std::string, std::string> CookieBundleWithContent( |
| std::string_view cookies) { |
| DCHECK_NE(cookies, "None"); |
| DCHECK_NE(cookies, ""); |
| return {std::string(cookies), std::string(cookies), std::string(cookies)}; |
| } |
| |
| constexpr std::pair<const char*, const char*> kNoCookies = |
| std::make_pair("", // cookie string via `document.cookie` |
| "None" // cookie string via `echoheader?cookie` |
| ); |
| |
| constexpr std::tuple<const char*, const char*, const char*> |
| kNoCookiesWithContent = |
| std::make_tuple("", // cookie string via `document.cookie` |
| "None", // cookie string via `echoheader?cookie` |
| "None" // cookie string via frame content (also via |
| // `echoheader?cookie`) |
| ); |
| |
| // Executes the inner_matcher on the string arg after it's transformed into a |
| // vector of pairs of strings. This assumes that the argument is a string, whose |
| // value is a '\n' delimited list of name/value pairs (delimited by ':'). |
| MATCHER_P(HeadersAre, inner_matcher, "") { |
| return testing::ExplainMatchResult(net::WhenKVSplit('\n', ':', inner_matcher), |
| arg, result_listener); |
| } |
| |
| void SetCORSHeaders(const net::test_server::HttpRequest& request, |
| net::test_server::BasicHttpResponse& response) { |
| if (auto it = request.headers.find(net::HttpRequestHeaders::kOrigin); |
| it != request.headers.end()) { |
| response.AddCustomHeader("Access-Control-Allow-Origin", it->second); |
| response.AddCustomHeader("Vary", "origin"); |
| response.AddCustomHeader("Access-Control-Allow-Credentials", "true"); |
| } |
| } |
| |
| // 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"); |
| SetCORSHeaders(request, *http_response); |
| http_response->set_content(content); |
| |
| return http_response; |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> HandleRetryRequest( |
| int& fetch_count, |
| std::string_view allowed_origin, |
| const net::test_server::HttpRequest& request) { |
| if (request.relative_url != kRetryPath) { |
| return nullptr; |
| } |
| |
| fetch_count++; |
| auto http_response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| http_response->set_code(net::HTTP_OK); |
| http_response->set_content_type("text/plain"); |
| http_response->AddCustomHeader( |
| "Activate-Storage-Access", |
| base::StrCat({"retry; allowed-origin=", allowed_origin})); |
| SetCORSHeaders(request, *http_response); |
| |
| std::optional<std::string> storage_access_header = base::OptionalFromPtr( |
| base::FindOrNull(request.headers, kSecFetchStorageAccess)); |
| if (storage_access_header == "inactive") { |
| std::optional<std::string> origin_header = base::OptionalFromPtr( |
| base::FindOrNull(request.headers, net::HttpRequestHeaders::kOrigin)); |
| CHECK(origin_header); |
| if (allowed_origin != "*" && origin_header) { |
| std::string trimmed_allowed_origin; |
| base::TrimString(allowed_origin, "\"", &trimmed_allowed_origin); |
| EXPECT_EQ(trimmed_allowed_origin, origin_header); |
| } |
| } |
| |
| auto serialize_header_name_and_value = |
| [&](std::string_view header_name) -> std::string { |
| std::string value = |
| base::OptionalFromPtr(base::FindOrNull(request.headers, header_name)) |
| .value_or(kHeaderNotProvidedSentinel); |
| return base::JoinString({header_name, value}, ":"); |
| }; |
| |
| http_response->set_content(base::JoinString( |
| { |
| serialize_header_name_and_value(net::HttpRequestHeaders::kCookie), |
| serialize_header_name_and_value(kSecFetchStorageAccess), |
| }, |
| "\n")); |
| |
| return http_response; |
| } |
| |
| // Intercepts requests and sets the 'Popin-Policy' response header to * if |
| // the query param `allow_popins` is set. |
| std::unique_ptr<net::test_server::HttpResponse> PopinRequestHandler( |
| const net::test_server::HttpRequest& request) { |
| net::test_server::RequestQuery query = |
| net::test_server::ParseQuery(request.GetURL()); |
| if (query.find("allow_popins") == query.end()) { |
| return nullptr; |
| } |
| auto http_response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| http_response->set_content_type("text/html"); |
| http_response->AddCustomHeader("Popin-Policy", "partitioned=*"); |
| return http_response; |
| } |
| |
| std::string QueryPermission(content::RenderFrameHost* render_frame_host) { |
| return content::EvalJs(render_frame_host, kQueryStorageAccessPermission) |
| .ExtractString(); |
| } |
| |
| bool ThirdPartyPartitionedStorageAllowedByDefault() { |
| return base::FeatureList::IsEnabled( |
| net::features::kThirdPartyPartitionedStorageAllowedByDefault) && |
| base::FeatureList::IsEnabled( |
| net::features::kThirdPartyStoragePartitioning); |
| } |
| |
| std::string CookieAttributes(std::string_view domain) { |
| return base::StrCat({";SameSite=None;Secure;Domain=", domain, ";Path=/"}); |
| } |
| |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeaturesForStorage( |
| bool is_storage_partitioned) { |
| std::vector<base::test::FeatureRefAndParams> enabled; |
| if (is_storage_partitioned) { |
| enabled.push_back({net::features::kThirdPartyStoragePartitioning, {}}); |
| } |
| return enabled; |
| } |
| |
| std::vector<base::test::FeatureRef> GetDisabledFeaturesForStorage( |
| bool is_storage_partitioned) { |
| std::vector<base::test::FeatureRef> disabled; |
| if (!is_storage_partitioned) { |
| disabled.push_back(net::features::kThirdPartyStoragePartitioning); |
| } |
| return disabled; |
| } |
| |
| class StorageAccessAPIBaseBrowserTest : public policy::PolicyTest { |
| protected: |
| StorageAccessAPIBaseBrowserTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {} |
| |
| void SetUp() override { |
| features_.InitWithFeaturesAndParameters(GetEnabledFeatures(), |
| GetDisabledFeatures()); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| virtual std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() { |
| return {}; |
| } |
| |
| virtual std::vector<base::test::FeatureRef> GetDisabledFeatures() { |
| return {}; |
| } |
| |
| 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_.ServeFilesFromSourceDirectory( |
| net::GetWebSocketTestDataDirectory()); |
| https_server_.AddDefaultHandlers(GetChromeTestDataDir()); |
| https_server_.RegisterRequestHandler( |
| base::BindRepeating(&HandleEchoCookiesWithCorsRequest)); |
| https_server_.RegisterRequestHandler(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) { |
| return HandleRetryRequest(retry_path_fetch_count_, |
| retry_allowed_origin_, request); |
| })); |
| https_server_.RegisterRequestHandler( |
| base::BindRepeating(&PopinRequestHandler)); |
| https_server_.RegisterRequestMonitor(base::BindLambdaForTesting( |
| [&](const net::test_server::HttpRequest& request) { |
| base::AutoLock lock(lock_); |
| observed_request_headers_.emplace_back(request.relative_url, |
| request.headers); |
| })); |
| net::test_server::InstallDefaultWebSocketHandlers(&https_server_); |
| ASSERT_TRUE(https_server_.Start()); |
| |
| // All the sites used during these tests should have a cookie. |
| SetCrossSiteCookieOnDomain(kHostA); |
| SetCrossSiteCookieOnDomain(kHostB); |
| SetCrossSiteCookieOnDomain(kHostC); |
| SetCrossSiteCookieOnDomain(kHostD); |
| |
| prompt_factory_ = MakePromptFactory(browser()); |
| |
| // 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); |
| |
| // Most of these tests invoke document.requestStorageAccess from a kHostB |
| // iframe. We pre-seed that site with user interaction, to avoid being |
| // blocked by the top-level user interaction heuristic. |
| EnsureUserInteractionOn(kHostB); |
| } |
| |
| std::unique_ptr<permissions::MockPermissionPromptFactory> MakePromptFactory( |
| Browser* browser_ptr) { |
| CHECK(browser_ptr); |
| return std::make_unique<permissions::MockPermissionPromptFactory>( |
| permissions::PermissionRequestManager::FromWebContents( |
| browser_ptr->tab_strip_model()->GetActiveWebContents())); |
| } |
| |
| void TearDownOnMainThread() override { prompt_factory_.reset(); } |
| |
| void SetCrossSiteCookieOnDomain(std::string_view domain) { |
| GURL domain_url = GetURL(domain); |
| std::string cookie = base::StrCat({"cross-site=", domain}); |
| ASSERT_TRUE( |
| content::SetCookie(browser()->profile(), domain_url, |
| base::StrCat({cookie, CookieAttributes(domain)}))); |
| ASSERT_THAT(content::GetCookies(browser()->profile(), domain_url), |
| testing::HasSubstr(cookie)); |
| } |
| |
| void SetPartitionedCookieInContext(std::string_view top_level_host, |
| std::string_view 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)); |
| ASSERT_TRUE(content::SetCookie( |
| browser()->profile(), host_url, |
| base::StrCat({cookie, CookieAttributes(/*domain=*/embedded_host), |
| ";Partitioned"}), |
| net::CookieOptions::SameSiteCookieContext::MakeInclusive(), |
| &partition_key)); |
| ASSERT_THAT(content::GetCookies( |
| browser()->profile(), host_url, |
| net::CookieOptions::SameSiteCookieContext::MakeInclusive(), |
| net::CookiePartitionKeyCollection(partition_key)), |
| testing::HasSubstr(cookie)); |
| } |
| |
| void BlockAllCookiesOnHost(std::string_view host) { |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(host), ContentSetting::CONTENT_SETTING_BLOCK); |
| } |
| |
| GURL GetURL(std::string_view host, std::string_view path = "/") { |
| return https_server_.GetURL(host, path); |
| } |
| |
| // TODO(crbug.com/381856829): Update SetBlockThirdPartyCookies to use sync |
| // interface once implemented. |
| void SetBlockThirdPartyCookies(bool value) { |
| browser()->profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>( |
| value ? content_settings::CookieControlsMode::kBlockThirdParty |
| : content_settings::CookieControlsMode::kOff)); |
| } |
| |
| void NavigateToPage(std::string_view host, std::string_view path) { |
| GURL main_url(https_server_.GetURL(host, path)); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| } |
| |
| void NavigateToPageWithFrame(std::string_view host, |
| Browser* browser_ptr = nullptr, |
| bool credentialless = false) { |
| GURL main_url(https_server_.GetURL( |
| host, credentialless ? "/iframe_credentialless.html" : "/iframe.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser_ptr ? browser_ptr : browser(), main_url)); |
| } |
| |
| void NavigateToPageWithFrameBlockingStorageAccessPermissionsPolicy( |
| std::string_view host) { |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| https_server_.GetURL(host, base::StrCat({ |
| "/cross_site_iframe_factory.html?", |
| host, |
| "(", |
| host, |
| "{disallow-storage-access})", |
| })))); |
| } |
| |
| void NavigateToNewTabWithFrame(std::string_view 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(std::string_view host, std::string_view path) { |
| NavigateFrameTo(https_server_.GetURL(host, path)); |
| } |
| |
| void NavigateFrameTo(const GURL& url, |
| Browser* browser_ptr = nullptr, |
| std::string_view iframe_id = "test") { |
| content::WebContents* web_contents = (browser_ptr ? browser_ptr : browser()) |
| ->tab_strip_model() |
| ->GetActiveWebContents(); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents, iframe_id, url)); |
| } |
| |
| void NavigateNestedFrameTo(std::string_view host, std::string_view 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(ExecJs( |
| GetFrame(), |
| base::StringPrintf("document.body.querySelector('iframe').src = '%s';", |
| url.spec().c_str()))); |
| load_observer.Wait(); |
| } |
| |
| void NavigateToPageWithTwoFrames(std::string_view 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(std::string_view host) { |
| return https_server().GetURL(host, "/echoheader?cookie"); |
| } |
| |
| std::string ServerRedirectPath(const GURL& url) { |
| return base::StrCat( |
| {"/server-redirect?", |
| base::EscapeQueryParamValue(url.spec(), /*use_plus=*/true)}); |
| } |
| |
| GURL RedirectViaHosts(const std::vector<std::string_view>& hosts, |
| const GURL& destination) { |
| GURL url = destination; |
| |
| for (const auto& host : base::Reversed(hosts)) { |
| url = https_server().GetURL(host, ServerRedirectPath(url)); |
| } |
| return url; |
| } |
| |
| std::string CookiesFromFetch(content::RenderFrameHost* render_frame_host, |
| std::string_view subresource_host) { |
| return ContentFromFetch(render_frame_host, subresource_host, |
| kEchoCookiesWithCorsPath); |
| } |
| |
| std::string ContentFromFetch(content::RenderFrameHost* render_frame_host, |
| std::string_view subresource_host, |
| std::string_view path) { |
| return storage::test::FetchWithCredentials( |
| render_frame_host, https_server_.GetURL(subresource_host, path), |
| /*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, |
| std::string_view subresource_host) { |
| return { |
| content::EvalJs(render_frame_host, "document.cookie", |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE) |
| .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, |
| std::string_view 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( |
| Browser* browser_ptr = nullptr) { |
| content::WebContents* web_contents = (browser_ptr ? browser_ptr : browser()) |
| ->tab_strip_model() |
| ->GetActiveWebContents(); |
| return web_contents->GetPrimaryMainFrame(); |
| } |
| |
| content::RenderFrameHost* GetFrame(Browser* browser_ptr = nullptr) { |
| return ChildFrameAt(GetPrimaryMainFrame(browser_ptr), 0); |
| } |
| |
| content::RenderFrameHost* GetNestedFrame(Browser* browser_ptr = nullptr) { |
| return ChildFrameAt(GetFrame(browser_ptr), 0); |
| } |
| |
| content::RenderFrameHost* GetFirstFrame() { return GetFrame(); } |
| |
| content::RenderFrameHost* GetSecondFrame() { |
| return ChildFrameAt(GetPrimaryMainFrame(), 1); |
| } |
| |
| void EnsureUserInteractionOn(std::string_view host, |
| Browser* browser_ptr = nullptr) { |
| if (browser_ptr == nullptr) { |
| browser_ptr = browser(); |
| } |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser_ptr, https_server_.GetURL(host, "/empty.html"))); |
| // ExecJs runs with a synthetic user interaction (by default), which is all |
| // we need, so our script is a no-op. |
| ASSERT_TRUE(content::ExecJs( |
| browser_ptr->tab_strip_model()->GetActiveWebContents(), "")); |
| } |
| |
| void OpenConnectToPage(content::RenderFrameHost* frame) { |
| std::string query = base::StrCat( |
| {"url=", net::test_server::GetWebSocketURL(https_server_, kHostB, |
| "/echo-request-headers") |
| .spec()}); |
| GURL::Replacements replacements; |
| replacements.SetQueryStr(query); |
| |
| ASSERT_TRUE(content::NavigateToURLFromRenderer( |
| frame, https_server() |
| .GetURL(kHostB, "/connect_to.html") |
| .ReplaceComponents(replacements))); |
| } |
| |
| net::test_server::EmbeddedTestServer& https_server() { return https_server_; } |
| |
| permissions::MockPermissionPromptFactory* prompt_factory() { |
| return prompt_factory_.get(); |
| } |
| |
| content_settings::PageSpecificContentSettings* content_settings() { |
| return content_settings::PageSpecificContentSettings::GetForFrame( |
| GetPrimaryMainFrame()); |
| } |
| |
| std::vector<std::pair<std::string, net::test_server::HttpRequest::HeaderMap>> |
| ObservedRequestHeaders() const { |
| base::AutoLock lock(lock_); |
| return observed_request_headers_; |
| } |
| |
| void SetRetryAllowedOriginFromHost(std::string_view host) { |
| set_retry_allowed_origin(base::StrCat({ |
| "\"", |
| url::Origin::Create(GetURL(host)).Serialize(), |
| "\"", |
| })); |
| } |
| |
| void set_retry_allowed_origin(std::string_view allowed_origin) { |
| retry_allowed_origin_ = allowed_origin; |
| } |
| |
| int retry_path_fetch_count_ = 0; |
| |
| private: |
| net::test_server::EmbeddedTestServer https_server_; |
| base::test::ScopedFeatureList features_; |
| std::unique_ptr<permissions::MockPermissionPromptFactory> prompt_factory_; |
| mutable base::Lock lock_; |
| std::vector<std::pair<std::string, net::test_server::HttpRequest::HeaderMap>> |
| observed_request_headers_ GUARDED_BY(lock_); |
| std::string retry_allowed_origin_ = ""; |
| }; |
| |
| // 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 {}; |
| |
| // Test fixture for tests whose behavior could be affected by Origin Isolation |
| // (kOriginKeyedProcessesByDefault). This is a parameterised version of |
| // StorageAccessAPIBrowserTest that runs each test with Origin Isolation |
| // enabled and disabled. |
| class StorageAccessAPIOriginIsolationBrowserTest |
| : public StorageAccessAPIBaseBrowserTest, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| StorageAccessAPIOriginIsolationBrowserTest() = default; |
| ~StorageAccessAPIOriginIsolationBrowserTest() = default; |
| |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| if (GetParam()) { |
| return { |
| {features::kOriginKeyedProcessesByDefault, {}}, |
| }; |
| } |
| return {}; |
| } |
| |
| std::vector<base::test::FeatureRef> GetDisabledFeatures() override { |
| if (!GetParam()) { |
| return {features::kOriginKeyedProcessesByDefault}; |
| } |
| return {}; |
| } |
| }; |
| |
| // 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); |
| |
| EXPECT_THAT(content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| |
| // Grant initial permission. |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(QueryPermission(GetFrame()), "granted"); |
| |
| EXPECT_THAT( |
| content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), true))); |
| |
| // 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"); |
| |
| EXPECT_THAT(content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| |
| // And the permission is regranted without prompt. |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::NONE); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_THAT( |
| content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), true))); |
| } |
| |
| // Test that permissions.query changes to "denied" when a storage access |
| // request was denied. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryDenied) { |
| SetBlockThirdPartyCookies(true); |
| EnsureUserInteractionOn(kHostB); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/echoheader?cookie"); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(QueryPermission(GetFrame()), "prompt"); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| EXPECT_THAT(content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| |
| // Deny initial permission. |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(QueryPermission(GetFrame()), "prompt"); |
| |
| EXPECT_THAT( |
| content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), false))); |
| |
| // 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()), "prompt"); |
| |
| EXPECT_THAT(content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| |
| // And the permission is denied without prompt. |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::NONE); |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_THAT( |
| content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), false))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryCrossSite) { |
| SetBlockThirdPartyCookies(true); |
| |
| EnsureUserInteractionOn(kHostA); |
| |
| 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"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| Permission_Denied_WithoutInteraction) { |
| base::HistogramTester histogram_tester; |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::NONE); |
| |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()", |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| EXPECT_THAT( |
| histogram_tester.GetBucketCount(kRequestOutcomeHistogram, |
| RequestOutcome::kDeniedByPrerequisites), |
| Gt(0)); |
| } |
| |
| // 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); |
| base::HistogramTester histogram_tester; |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| 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")); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| histogram_tester.ExpectBucketCount( |
| kUseCounterHistogram, |
| blink::mojom::WebFeature:: |
| kCrossOriginSameSiteCookieAccessViaStorageAccessAPI, |
| 0); |
| |
| histogram_tester.ExpectUniqueSample(kNetRequestHistogram, |
| /*kSameOrigin*/ 0, |
| /*expected_bucket_count=*/1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| AccessGranted_DoesNotConsumeUserInteraction) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_EQ(content::EvalJs(GetFrame(), "navigator.userActivation.isActive", |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE), |
| true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| AccessGranted_NoSubsequentUserInteraction) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->ResetCounts(); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame( |
| GetFrame(), /*omit_user_gesture=*/true)); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 0); |
| } |
| |
| // 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), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| 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), |
| kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| |
| 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), kNoCookies); |
| } |
| |
| // Validate that in a A(B) frame tree, the iframe can make credentialed |
| // same-site requests, even if the requests are cross-origin. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameRequestsAccess_CrossOriginFetch) { |
| SetBlockThirdPartyCookies(true); |
| base::HistogramTester histogram_tester; |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostBSubdomain)); |
| |
| ASSERT_EQ(ReadCookies(GetFrame(), kHostBSubdomain), kNoCookies); |
| ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostBSubdomain2), |
| "cross-site=b.test"); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| histogram_tester.ExpectBucketCount( |
| kUseCounterHistogram, |
| blink::mojom::WebFeature:: |
| kCrossOriginSameSiteCookieAccessViaStorageAccessAPI, |
| 1); |
| histogram_tester.ExpectUniqueSample( |
| kNetRequestHistogram, |
| /*kCrossOriginSameSiteCredentialsIncluded*/ 3, |
| /*expected_bucket_count=*/1); |
| } |
| |
| // Validate that in a A(B) frame tree, the iframe can make uncredentialed |
| // same-site requests, even if the requests are cross-origin, and the correct |
| // metrics buckets are sampled to. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameRequestsAccess_CrossOriginFetch_Uncredentialed) { |
| SetBlockThirdPartyCookies(true); |
| base::HistogramTester histogram_tester; |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostBSubdomain)); |
| |
| ASSERT_EQ(ReadCookies(GetFrame(), kHostBSubdomain), kNoCookies); |
| ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_EQ(content::EvalJs(GetFrame(), |
| content::JsReplace(R"( |
| fetch($1, {method: 'GET', mode: 'cors', credentials: 'omit'}) |
| .then((result) => result.text()); |
| )", |
| https_server().GetURL( |
| kHostBSubdomain2, |
| kEchoCookiesWithCorsPath))), |
| "None"); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| histogram_tester.ExpectBucketCount( |
| kUseCounterHistogram, |
| blink::mojom::WebFeature:: |
| kCrossOriginSameSiteCookieAccessViaStorageAccessAPI, |
| 0); |
| histogram_tester.ExpectUniqueSample( |
| kNetRequestHistogram, |
| /*kCrossOriginSameSiteCredentialsNotIncluded*/ 4, |
| /*expected_bucket_count=*/1); |
| } |
| |
| // Validate that in a A(B) frame tree, the iframe cannot make credentialed |
| // cross-site requests. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameRequestsAccess_CrossSiteFetch) { |
| SetBlockThirdPartyCookies(true); |
| base::HistogramTester histogram_tester; |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostC), "None"); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| histogram_tester.ExpectBucketCount( |
| kUseCounterHistogram, |
| blink::mojom::WebFeature:: |
| kCrossOriginSameSiteCookieAccessViaStorageAccessAPI, |
| 0); |
| histogram_tester.ExpectUniqueSample(kNetRequestHistogram, |
| /*kCrossSite*/ 2, |
| /*expected_bucket_count=*/1); |
| } |
| |
| // 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), kNoCookies); |
| 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), kNoCookies); |
| |
| // 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); |
| EnsureUserInteractionOn(kHostC); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostC), |
| kNoCookiesWithContent); |
| 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) { |
| EnsureUserInteractionOn(kHostC); |
| |
| NavigateToPageWithTwoFrames(kHostA); |
| NavigateFirstFrameTo(EchoCookiesURL(kHostB)); |
| NavigateSecondFrameTo(EchoCookiesURL(kHostC)); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(kHostC), CONTENT_SETTING_ALLOW); |
| |
| // 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); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->ResetCookieSetting(GetURL(kHostB)); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->ResetCookieSetting(GetURL(kHostC)); |
| // Navigate the first iframe to kHostB and grant Storage Access. |
| NavigateFirstFrameTo(EchoCookiesURL(kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFirstFrame(), kHostB), |
| kNoCookiesWithContent); |
| 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), |
| kNoCookiesWithContent); |
| 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_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_EQ(content::EvalJs(GetFrame(), "navigator.userActivation.isActive", |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE), |
| false); |
| |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| } |
| |
| // 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), |
| kNoCookiesWithContent); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_FALSE( |
| content::ExecJs(GetNestedFrame(), "document.requestStorageAccess()")); |
| |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB), kNoCookies); |
| } |
| |
| // Validate that user settings take precedence for the leaf in a A(B(C)) frame |
| // tree. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameThirdPartyExceptions_NestedCrossSite) { |
| EnsureUserInteractionOn(kHostC); |
| |
| SetBlockThirdPartyCookies(true); |
| BlockAllCookiesOnHost(kHostC); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostC), |
| kNoCookiesWithContent); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_FALSE( |
| content::ExecJs(GetNestedFrame(), "document.requestStorageAccess()")); |
| |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostC), kNoCookies); |
| } |
| |
| // Validate that user settings take precedence for the leaf in a A(B(A)) frame |
| // tree. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameThirdPartyExceptions_CrossSiteAncestorChain) { |
| EnsureUserInteractionOn(kHostA); |
| SetBlockThirdPartyCookies(true); |
| BlockAllCookiesOnHost(kHostA); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostA)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetPrimaryMainFrame())); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_FALSE( |
| content::ExecJs(GetNestedFrame(), "document.requestStorageAccess()")); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostA), |
| kNoCookiesWithContent); |
| } |
| |
| // Validate that user settings take precedence for the leaf in a A(A) frame |
| // tree. |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIBrowserTest, |
| ThirdPartyCookiesIFrameThirdPartyExceptions_SameSiteAncestorChain) { |
| EnsureUserInteractionOn(kHostA); |
| SetBlockThirdPartyCookies(true); |
| BlockAllCookiesOnHost(kHostA); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetPrimaryMainFrame())); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_EQ(0, prompt_factory()->TotalRequestCount()); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), kNoCookiesWithContent); |
| } |
| |
| // 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), kNoCookiesWithContent); |
| |
| 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); |
| |
| // Try to ensure that the pref observer is triggered and the updated settings |
| // are propagated to the network service. |
| base::RunLoop().RunUntilIdle(); |
| browser() |
| ->profile() |
| ->GetDefaultStoragePartition() |
| ->FlushNetworkInterfaceForTesting(); |
| |
| // Verify cookie cannot be accessed. |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| |
| // Access change should be reflected immediately. |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| // Validates that if the user explicitly blocks cookies, cookie access is |
| // blocked even with the existing grant. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| ExplicitUserSettingsBlockThirdPartyGrantsAccess) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| |
| 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")); |
| |
| BlockAllCookiesOnHost(kHostB); |
| // Try to ensure that the pref observer is triggered and the updated settings |
| // are propagated to the network service. |
| base::RunLoop().RunUntilIdle(); |
| browser() |
| ->profile() |
| ->GetDefaultStoragePartition() |
| ->FlushNetworkInterfaceForTesting(); |
| |
| // Access change should be reflected immediately. |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| // TODO(crbug.com/40266432): should EXPECT_FALSE here since user |
| // explicitly blocks cookies for hostB |
| EXPECT_TRUE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| } |
| |
| // 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( |
| ExecJs(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(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| 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(ExecJs(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(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| } |
| |
| // 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( |
| ExecJs(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())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test")); |
| } |
| |
| // 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. |
| const base::TimeDelta lifetime = base::Days(30); |
| const base::Time creation_time = |
| base::Time::Now() - base::Minutes(5) - lifetime; |
| HostContentSettingsMap* settings_map = |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()); |
| content_settings::ContentSettingConstraints constraints(creation_time); |
| constraints.set_lifetime(lifetime); |
| constraints.set_session_model( |
| content_settings::mojom::SessionModel::USER_SESSION); |
| settings_map->SetContentSettingDefaultScope( |
| GetURL(kHostB), GetURL(kHostA), ContentSettingsType::STORAGE_ACCESS, |
| CONTENT_SETTING_ALLOW, constraints); |
| 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 prompting. |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostC), |
| CookieBundle("cross-site=c.test")); |
| |
| histogram_tester.ExpectTotalCount(kRequestOutcomeHistogram, 2); |
| histogram_tester.ExpectBucketCount( |
| kRequestOutcomeHistogram, RequestOutcome::kReusedPreviousDecision, 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), |
| kNoCookiesWithContent); |
| } |
| |
| // 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), kNoCookiesWithContent); |
| |
| 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), kNoCookiesWithContent); |
| |
| 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), kNoCookiesWithContent); |
| } |
| |
| // 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_P( |
| StorageAccessAPIOriginIsolationBrowserTest, |
| Navigation_NonSelfInitiated_SameOriginDestination_SameSiteInitiator) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain)); |
| |
| ASSERT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| ASSERT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| kNoCookiesWithContent); |
| |
| 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_P( |
| StorageAccessAPIOriginIsolationBrowserTest, |
| Navigation_NonSelfInitiated_SameOriginDestination_SameSiteInitiator_TargetHasStorageAccess) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostBSubdomain)); |
| |
| ASSERT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| ASSERT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| kNoCookiesWithContent); |
| |
| 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), kNoCookiesWithContent); |
| |
| 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), kNoCookiesWithContent); |
| |
| 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), kNoCookiesWithContent); |
| } |
| |
| // 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), kNoCookiesWithContent); |
| |
| 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), kNoCookiesWithContent); |
| |
| 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")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| Navigation_ViaFormSubmission_SameOriginRedirect) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(https_server().GetURL(kHostB, "/form.html")); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(content::ExecJs( |
| GetFrame(), |
| content::JsReplace("document.getElementById('form').action = $1", |
| RedirectViaHosts({kHostB}, EchoCookiesURL(kHostB))))); |
| |
| { |
| content::TestNavigationObserver load_observer( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| EXPECT_TRUE(content::ExecJs(GetFrame(), |
| "document.getElementById('form').submit()")); |
| load_observer.Wait(); |
| } |
| |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| // The navigation itself carried cookies from the previous document's storage |
| // access, and the new document inherited storage access. |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), |
| CookieBundleWithContent("cross-site=b.test")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| Navigation_ViaFormSubmission_CrossOriginRedirect) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(https_server().GetURL(kHostB, "/form.html")); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(content::ExecJs( |
| GetFrame(), |
| content::JsReplace( |
| "document.getElementById('form').action = $1", |
| RedirectViaHosts({kHostBSubdomain}, EchoCookiesURL(kHostB))))); |
| |
| { |
| content::TestNavigationObserver load_observer( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| EXPECT_TRUE(content::ExecJs(GetFrame(), |
| "document.getElementById('form').submit()")); |
| load_observer.Wait(); |
| } |
| |
| 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 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) { |
| base::HistogramTester histogram_tester; |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(kHostA, "/empty.html"); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostA), kNoCookies); |
| |
| 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")); |
| histogram_tester.ExpectTotalCount(kRequestOutcomeHistogram, 1); |
| histogram_tester.ExpectBucketCount(kRequestOutcomeHistogram, |
| RequestOutcome::kAllowedBySameSite, 1); |
| } |
| |
| // 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), kNoCookies); |
| |
| 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")); |
| |
| // Subsequent permission requests are no-ops. |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(0, prompt_factory()->TotalRequestCount()); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostASubdomain), |
| CookieBundle("cross-site=a.test")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| DedicatedWorker_InheritsStorageAccessFromDocument) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| // Get storage access and then do a self-initiated same-site navigation to the |
| // fetch_from_worker.html, so that fetch_from_worker.html has storage access |
| // upon load (and therefore has storage access when it creates the Worker). |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| ASSERT_TRUE(content::NavigateToURLFromRenderer( |
| GetFrame(), |
| https_server().GetURL(kHostB, "/workers/fetch_from_worker.html"))); |
| ASSERT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| // When the worker's parent document has storage access at the time the worker |
| // is created, the worker should inherit that access and be able to use it. |
| EXPECT_EQ( |
| content::EvalJs(GetFrame(), "fetch_from_worker('/echoheader?cookie');"), |
| "cross-site=b.test"); |
| } |
| |
| // Regression test for https://crbug.com/409838513. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| DedicatedWorker_ABA_InheritsStorageAccessFromDocument) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(EchoCookiesURL(kHostA)); |
| ASSERT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| ASSERT_TRUE(content::NavigateToURLFromRenderer( |
| GetNestedFrame(), |
| https_server().GetURL(kHostA, "/workers/fetch_from_worker.html"))); |
| ASSERT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| |
| // When the worker's parent document has storage access at the time the worker |
| // is created, the worker should inherit that access and be able to use it. |
| // |
| // This should work despite the fact that this is an ABA context, and |
| // therefore there is no explicit permission grant. |
| EXPECT_EQ(content::EvalJs(GetNestedFrame(), |
| "fetch_from_worker('/echoheader?cookie');"), |
| "cross-site=a.test"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, DedicatedWorker_ABA) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(https_server().GetURL( |
| kHostA, "/workers/fetch_from_worker.html?start_worker_manually")); |
| |
| ASSERT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| ASSERT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| |
| // When the worker's parent document has storage access at the time the |
| // worker is created, the worker should inherit that access and be able to |
| // use it. |
| // |
| // This should work despite the fact that this is an ABA context, and |
| // therefore there is no explicit permission grant. |
| EXPECT_TRUE(content::ExecJs(GetNestedFrame(), "start_worker()")); |
| EXPECT_EQ(content::EvalJs(GetNestedFrame(), |
| "fetch_from_worker('/echoheader?cookie');"), |
| "cross-site=a.test"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| WebsocketRequestsUseStorageAccessGrants) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| content::DOMMessageQueue message_queue( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| // Before the document opts into Storage Access, the WebSocket should not send |
| // unpartitioned cookies during the connection. |
| { |
| OpenConnectToPage(GetFrame()); |
| |
| std::string message; |
| EXPECT_TRUE(message_queue.WaitForMessage(&message)); |
| EXPECT_THAT(message, testing::Not(testing::HasSubstr("cross-site=b.test"))); |
| } |
| |
| // Get storage access and then do a self-initiated same-site navigation to the |
| // connect_to.html, so that the websocket's frame has storage access |
| // upon load (and therefore has storage access when it creates the websocket). |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // After the document opts into Storage Access, the WebSocket *should* send |
| // unpartitioned cookies during the connection. |
| { |
| OpenConnectToPage(GetFrame()); |
| ASSERT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| std::string message; |
| EXPECT_TRUE(message_queue.WaitForMessage(&message)); |
| EXPECT_THAT(message, testing::HasSubstr("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(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_EQ(1, prompt_factory()->TotalRequestCount()); |
| EXPECT_EQ(1, prompt_factory()->RequestTypeSeen( |
| permissions::RequestType::kStorageAccess)); |
| } |
| |
| // Validate that if third-party cookies are blocked and the permission is |
| // denied, requestStorageAccess beyond cookies fails. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| BeyondCookies_WithoutCookiesWithoutPermission) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_FALSE(storage::test::RequestAndCheckStorageAccessBeyondCookiesForFrame( |
| GetFrame())); |
| } |
| |
| // Validate that if third-party cookies are blocked but the permission is |
| // allowed, requestStorageAccess beyond cookies succeeds. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| BeyondCookies_WithoutCookiesWithPermission) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessBeyondCookiesForFrame( |
| GetFrame())); |
| } |
| |
| class StorageAccessAPIStorageBrowserTest |
| : public StorageAccessAPIBaseBrowserTest, |
| public testing::WithParamInterface<std::tuple<TestType, bool>> { |
| public: |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| return GetEnabledFeaturesForStorage(IsStoragePartitioned()); |
| } |
| |
| std::vector<base::test::FeatureRef> GetDisabledFeatures() override { |
| return GetDisabledFeaturesForStorage(IsStoragePartitioned()); |
| } |
| |
| void ExpectStorage(content::RenderFrameHost* frame, bool expected) { |
| switch (GetTestType()) { |
| case TestType::kFrame: |
| storage::test::ExpectStorageForFrame(frame, 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()); } |
| bool IsStoragePartitioned() const { return std::get<1>(GetParam()); } |
| }; |
| |
| #if BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER) |
| // TODO(crbug.com/404576878): Test is flaky on Windows ASAN builds. |
| #define MAYBE_ThirdPartyIFrameStorageRequestsAccess \ |
| DISABLED_ThirdPartyIFrameStorageRequestsAccess |
| #else |
| #define MAYBE_ThirdPartyIFrameStorageRequestsAccess \ |
| ThirdPartyIFrameStorageRequestsAccess |
| #endif |
| // 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, |
| MAYBE_ThirdPartyIFrameStorageRequestsAccess) { |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/browsing_data/site_data.html"); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW); |
| |
| ExpectStorage(GetFrame(), false); |
| SetStorage(GetFrame()); |
| ExpectStorage(GetFrame(), true); |
| |
| SetBlockThirdPartyCookies(true); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->ResetCookieSetting(GetURL(kHostB)); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/browsing_data/site_data.html"); |
| |
| ExpectStorage(GetFrame(), ThirdPartyPartitionedStorageAllowedByDefault()); |
| 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())); |
| } |
| // TODO(crbug.com/430495897): Test is flaky on Windows ASAN builds. |
| #if BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER) |
| #define MAYBE_NestedThirdPartyIFrameStorage DISABLED_NestedThirdPartyIFrameStorage |
| #else |
| #define MAYBE_NestedThirdPartyIFrameStorage NestedThirdPartyIFrameStorage |
| #endif |
| IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest, |
| NestedThirdPartyIFrameStorage) { |
| EnsureUserInteractionOn(kHostC); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html"); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(kHostC), CONTENT_SETTING_ALLOW); |
| |
| ExpectStorage(GetNestedFrame(), false); |
| SetStorage(GetNestedFrame()); |
| ExpectStorage(GetNestedFrame(), true); |
| |
| SetBlockThirdPartyCookies(true); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->ResetCookieSetting(GetURL(kHostB)); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->ResetCookieSetting(GetURL(kHostC)); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/iframe.html"); |
| NavigateNestedFrameTo(kHostC, "/browsing_data/site_data.html"); |
| |
| ExpectStorage(GetNestedFrame(), |
| ThirdPartyPartitionedStorageAllowedByDefault()); |
| 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_Storage) { |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/browsing_data/site_data.html"); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW); |
| |
| 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"); |
| |
| permissions::PermissionRequestManager::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()) |
| ->set_auto_response_for_test( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true); |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| SetBlockThirdPartyCookies(true); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->ResetCookieSetting(GetURL(kHostB)); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/browsing_data/site_data.html"); |
| |
| storage::test::ExpectCrossTabInfoForFrame( |
| GetFrame(), ThirdPartyPartitionedStorageAllowedByDefault()); |
| 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())); |
| } |
| |
| // Verifies that one tab can reuse the permission granted to another tab. |
| IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest, MultiTabTest) { |
| SetBlockThirdPartyCookies(true); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/empty.html"); |
| |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateToNewTabWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/empty.html"); |
| |
| permissions::PermissionRequestManager::FromWebContents( |
| browser()->tab_strip_model()->GetActiveWebContents()) |
| ->set_auto_response_for_test( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(/*no prefix*/, |
| StorageAccessAPIOriginIsolationBrowserTest, |
| testing::Bool(), |
| [](const testing::TestParamInfo<bool>& info) { |
| return info.param ? "origin_keyed" : "site_keyed"; |
| }); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /*no prefix*/, |
| StorageAccessAPIStorageBrowserTest, |
| testing::Combine(testing::Values(TestType::kFrame, TestType::kWorker), |
| testing::Bool() // IsStoragePartitioned |
| ), |
| /*name_generator=*/ |
| [](const testing::TestParamInfo<std::tuple<TestType, bool>>& info) { |
| std::string name = base::NumberToString(info.index); |
| switch (std::get<0>(info.param)) { |
| case TestType::kFrame: |
| name += "_frame"; |
| break; |
| case TestType::kWorker: |
| name += "_worker"; |
| break; |
| } |
| if (std::get<1>(info.param)) { |
| name += "_partitioned"; |
| } else { |
| name += "_unpartitioned"; |
| } |
| return name; |
| }); |
| |
| class StorageAccessAPIWithFirstPartySetsBrowserTest |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| StorageAccessAPIBaseBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII( |
| network::switches::kUseRelatedWebsiteSet, |
| base::StrCat({R"({"primary": "https://)", kHostA, |
| R"(", "associatedSites": ["https://)", kHostB, R"("])", |
| R"(, "serviceSites": ["https://)", kHostD, R"("]})"})); |
| } |
| }; |
| |
| 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), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| EXPECT_THAT( |
| histogram_tester.GetBucketCount(kRequestOutcomeHistogram, |
| RequestOutcome::kGrantedByFirstPartySet), |
| Gt(0)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest, |
| Permission_PromptOrDenyUnderServiceDomain) { |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| EnsureUserInteractionOn(kHostA); |
| SetBlockThirdPartyCookies(true); |
| |
| base::HistogramTester histogram_tester; |
| |
| NavigateToPageWithFrame(kHostD); |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| // The promise should be rejected; `kHostD` is a service domain. |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| EXPECT_THAT(histogram_tester.GetBucketCount(kRequestOutcomeHistogram, |
| RequestOutcome::kDeniedByUser), |
| 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_AutograntedForServiceDomainWithExistingGrant) { |
| SetBlockThirdPartyCookies(true); |
| |
| // Manually create a grant for the service site. Other test cases show that |
| // the service site cannot create this grant on its own, but such a grant can |
| // be created via other APIs (namely `document.requestStorageAccessFor`). |
| content_settings::ContentSettingConstraints constraints; |
| constraints.set_lifetime(base::Days(30)); |
| constraints.set_session_model(content_settings::mojom::SessionModel::DURABLE); |
| constraints.set_decided_by_related_website_sets(true); |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->SetContentSettingDefaultScope(GetURL(kHostD), GetURL(kHostA), |
| ContentSettingsType::STORAGE_ACCESS, |
| CONTENT_SETTING_ALLOW, constraints); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostD)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostD), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostD), CookieBundle("cross-site=d.test")); |
| |
| EXPECT_EQ(QueryPermission(GetFrame()), "granted"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest, |
| Permission_AutodeniedOutsideFirstPartySet_Overridden) { |
| base::HistogramTester histogram_tester; |
| // Note: kHostA and kHostC are considered cross-party, since kHostA's set does |
| // not include kHostC. |
| SetBlockThirdPartyCookies(true); |
| EnsureUserInteractionOn(kHostC); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostC)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), kNoCookiesWithContent); |
| 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(), kHostC), CookieBundle("cross-site=c.test")); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| EXPECT_THAT(histogram_tester.GetBucketCount(kRequestOutcomeHistogram, |
| RequestOutcome::kGrantedByUser), |
| Gt(0)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| StorageAccessAPIWithFirstPartySetsBrowserTest, |
| Permission_AutodeniedInsideFirstPartySet_WithoutInteraction) { |
| base::HistogramTester histogram_tester; |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()", |
| content::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| |
| EXPECT_THAT( |
| histogram_tester.GetBucketCount(kRequestOutcomeHistogram, |
| RequestOutcome::kDeniedByPrerequisites), |
| Gt(0)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest, |
| PRE_PermissionGrantsRestoredAfterRestart) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| ASSERT_EQ("granted", QueryPermission(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest, |
| PermissionGrantsRestoredAfterRestart) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_EQ("granted", QueryPermission(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest, |
| Permission_GrantedForServiceDomain) { |
| SetBlockThirdPartyCookies(true); |
| base::HistogramTester histogram_tester; |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostD)); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostD), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| // `kHostD` is a service domain, but is not the top-level site, so the request |
| // should be auto-granted. |
| EXPECT_TRUE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| EXPECT_THAT( |
| histogram_tester.GetBucketCount(kRequestOutcomeHistogram, |
| RequestOutcome::kGrantedByFirstPartySet), |
| Gt(0)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest, |
| SameSite_NoRegression) { |
| // Note: kHostA and kHostB are considered same-party due to the use of |
| // `network::switches::kUseFirstPartySet`. But they should not be "same-site", |
| // so SameSite=Lax and SameSite=Strict should still block cookie access. |
| ASSERT_TRUE( |
| SetCookie(browser()->profile(), GetURL(kHostB), |
| "samesitelax=1; SameSite=Lax; Secure", |
| net::CookieOptions::SameSiteCookieContext::MakeInclusive())); |
| ASSERT_TRUE( |
| SetCookie(browser()->profile(), GetURL(kHostB), |
| "samesitestrict=1; SameSite=Strict; Secure", |
| net::CookieOptions::SameSiteCookieContext::MakeInclusive())); |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostB), "cross-site=b.test"); |
| } |
| |
| class StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest() { |
| StorageAccessGrantPermissionContext::SetImplicitGrantLimitForTesting(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), kNoCookiesWithContent); |
| 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), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| RequestStorageAccess_CoexistsWithPartitionedCookies) { |
| 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<std::string_view, ContentSetting, bool>> { |
| public: |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| return GetEnabledFeaturesForStorage(IsStoragePartitioned()); |
| } |
| |
| std::vector<base::test::FeatureRef> GetDisabledFeatures() override { |
| return GetDisabledFeaturesForStorage(IsStoragePartitioned()); |
| } |
| |
| 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) { |
| std::string_view 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()); } |
| std::string_view GetContentOrigin() const { return std::get<0>(GetParam()); } |
| bool IsStoragePartitioned() const { return std::get<2>(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"); |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSetting(GetURL(kHostB), CONTENT_SETTING_ALLOW); |
| storage::test::ExpectStorageForFrame(GetPrimaryMainFrame(), |
| /*expected=*/false); |
| storage::test::SetStorageForFrame(GetPrimaryMainFrame(), |
| /*include_cookies=*/false); |
| storage::test::ExpectStorageForFrame(GetPrimaryMainFrame(), |
| /*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(), !ExpectPartitionedStorage()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| EnsureOnePromptDenialSuffices) { |
| SetBlockThirdPartyCookies(true); |
| 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( |
| content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| 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( |
| content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| // 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) { |
| SetBlockThirdPartyCookies(true); |
| 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); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| TopLevelUserInteractionRequired) { |
| SetBlockThirdPartyCookies(true); |
| |
| // The test fixture pre-seeds kHostB with top-level user interaction, but not |
| // the other hosts. We intentionally use kHostA as the embed, since it has not |
| // been seeded with a top-level user interaction. |
| |
| NavigateToPageWithFrame(kHostB); |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| |
| ASSERT_EQ(ReadCookiesAndContent(GetFrame(), kHostA), kNoCookiesWithContent); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostA), kNoCookies); |
| |
| // If kHostA has a top-level interaction, it can request storage access. The |
| // user interaction should be tracked by site, not origin. |
| EnsureUserInteractionOn(kHostASubdomain); |
| NavigateToPageWithFrame(kHostB); |
| NavigateFrameTo(EchoCookiesURL(kHostA)); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostA), CookieBundle("cross-site=a.test")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, |
| IncognitoDoesntUseRegularInteractionsOrPermission) { |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Even though there was previous interaction in the regular profile, requests |
| // made by incognito profiles should be denied, due to the top-level user |
| // interaction requirement. |
| Browser* incognito_browser = Browser::Create(Browser::CreateParams( |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true), |
| /*user_gesture=*/true)); |
| |
| NavigateToURLWithDisposition(incognito_browser, |
| https_server().GetURL(kHostA, "/iframe.html"), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| NavigateFrameTo(EchoCookiesURL(kHostB), incognito_browser); |
| |
| EXPECT_FALSE(content::ExecJs(GetFrame(incognito_browser), |
| "document.requestStorageAccess()")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, IncognitoCanUseAPI) { |
| Browser* incognito_browser = Browser::Create(Browser::CreateParams( |
| browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true), |
| /*user_gesture=*/true)); |
| |
| NavigateToURLWithDisposition(incognito_browser, |
| https_server().GetURL(kHostA, "/empty.html"), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| |
| // Even in an incognito profile, it's possible to use the Storage Access API |
| // if all the requirements are satisfied. |
| |
| EnsureUserInteractionOn(kHostB, incognito_browser); |
| |
| NavigateToPageWithFrame(kHostA, incognito_browser); |
| NavigateFrameTo(EchoCookiesURL(kHostB), incognito_browser); |
| |
| std::unique_ptr<permissions::MockPermissionPromptFactory> |
| incognito_prompt_factory = MakePromptFactory(incognito_browser); |
| incognito_prompt_factory->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame( |
| GetFrame(incognito_browser))); |
| } |
| |
| class StorageAccessAPIWithImplicitGrantsBrowserTest |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| StorageAccessAPIWithImplicitGrantsBrowserTest() { |
| StorageAccessGrantPermissionContext::SetImplicitGrantLimitForTesting(2); |
| } |
| |
| protected: |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| return {}; |
| } |
| }; |
| |
| 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); |
| } |
| |
| // Tests to verify that when 3p cookie is allowed, the embedded iframe can |
| // access cookie without requesting, and no prompt is shown if the iframe makes |
| // the request. |
| class StorageAccessAPIWith3PCEnabledBrowserTest |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| std::vector<base::test::FeatureRef> GetDisabledFeatures() override { |
| return {content_settings::features::kTrackingProtection3pcd}; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWith3PCEnabledBrowserTest, |
| AllowedWhenUnblocked) { |
| SetBlockThirdPartyCookies(false); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), |
| CookieBundleWithContent("cross-site=b.test")); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DISMISS); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(0, prompt_factory()->TotalRequestCount()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWith3PCEnabledBrowserTest, |
| AllowedByUserBypass) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| // Enable UserBypass on hostA as top-level. |
| CookieSettingsFactory::GetForProfile(browser()->profile()) |
| ->SetCookieSettingForUserBypass(GetURL(kHostA)); |
| |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), |
| CookieBundleWithContent("cross-site=b.test")); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DISMISS); |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(0, prompt_factory()->TotalRequestCount()); |
| } |
| |
| // Validate that if third-party cookies are allowed but the permission is |
| // denied, requestStorageAccess beyond cookies succeeds. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWith3PCEnabledBrowserTest, |
| BeyondCookies_WithCookiesWithoutPermission) { |
| SetBlockThirdPartyCookies(false); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessBeyondCookiesForFrame( |
| GetFrame())); |
| } |
| |
| // Validate that if third-party cookies are allowed and the permission is |
| // allowed, requestStorageAccess beyond cookies succeeds. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWith3PCEnabledBrowserTest, |
| BeyondCookies_WithCookiesWithPermission) { |
| SetBlockThirdPartyCookies(false); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessBeyondCookiesForFrame( |
| GetFrame())); |
| } |
| |
| // Validate that if third-party cookies are allowed but the permission is |
| // denied, requestStorageAccess does grant local storage access. |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIWith3PCEnabledBrowserTest, |
| BeyondCookies_LocalStorageWith3PCAndNoPermission) { |
| // Allow 3PC and deny storage access requests. |
| SetBlockThirdPartyCookies(false); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::DENY_ALL); |
| |
| // Set first-party local storage on site b. |
| NavigateToPageWithFrame(kHostB); |
| EXPECT_TRUE(content::ExecJs(GetPrimaryMainFrame(), |
| "window.localStorage.setItem('test', 'a');" |
| "window.sessionStorage.setItem('test', 'b');")); |
| |
| // Test if first-party data is available on site a. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(kHostB, "/empty.html"); |
| EXPECT_EQ(true, content::EvalJs( |
| GetFrame(), |
| "document.requestStorageAccess(" |
| " {localStorage: true, sessionStorage: true}" |
| ").then(" |
| " (handle) => {" |
| " return handle.localStorage.getItem('test') == 'a' &&" |
| " handle.sessionStorage.getItem('test') == 'b';" |
| " }," |
| " () => false" |
| ");")); |
| } |
| |
| class StorageAccessAPIAutograntsWithFedCMBrowserTest |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| void GrantFedCMPermission() { |
| const url::Origin rp_embedder = |
| url::Origin::Create(GetURL(kHostASubdomain)); |
| const url::Origin rp_requester = url::Origin::Create(GetURL(kHostC)); |
| const url::Origin idp = url::Origin::Create(GetURL(kHostB)); |
| constexpr char account_id[] = "my account"; |
| |
| FederatedIdentityPermissionContextFactory::GetForProfile( |
| browser()->profile()) |
| ->GrantSharingPermission(rp_requester, rp_embedder, idp, account_id); |
| } |
| |
| void NavigateToPageWithPermissionsPolicyIframes( |
| std::initializer_list<const std::string_view> hosts_list) { |
| base::span hosts(hosts_list); |
| ASSERT_FALSE(hosts.empty()); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), https_server().GetURL( |
| hosts[0], base::StrCat({ |
| "/cross_site_iframe_factory.html?", |
| hosts[0], |
| "(", |
| MakeNonRootFrameNodes(hosts.subspan<1>()), |
| ")", |
| })))); |
| } |
| |
| private: |
| std::string MakeNonRootFrameNodes(base::span<const std::string_view> hosts) { |
| std::string tree; |
| for (const auto& host : hosts) { |
| base::StrAppend(&tree, {host, "{allow-identity-credentials-get}("}); |
| } |
| for (const auto& host : hosts) { |
| (void)host; |
| base::StrAppend(&tree, {")"}); |
| } |
| return tree; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest, |
| FedCMGrants_RequiresPermissionPolicy) { |
| SetBlockThirdPartyCookies(true); |
| GrantFedCMPermission(); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::AutoResponseType::DENY_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest, |
| FedCMGrants_PreventSilentAccess) { |
| SetBlockThirdPartyCookies(true); |
| GrantFedCMPermission(); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::AutoResponseType::DENY_ALL); |
| |
| NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB}); |
| NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0"); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(content::ExecJs(GetPrimaryMainFrame(), |
| "navigator.credentials.preventSilentAccess()")); |
| |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest, |
| FedCMGrants_PreventSilentAccess_AfterAutogrant) { |
| SetBlockThirdPartyCookies(true); |
| GrantFedCMPermission(); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::AutoResponseType::DENY_ALL); |
| |
| NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB}); |
| NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0"); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame( |
| GetFrame(), /*omit_user_gesture=*/true)); |
| EXPECT_TRUE(content::ExecJs(GetPrimaryMainFrame(), |
| "navigator.credentials.preventSilentAccess()")); |
| |
| NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0"); |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest, |
| FedCMGrants_PermissionPolicyHeaderIgnored) { |
| SetBlockThirdPartyCookies(true); |
| GrantFedCMPermission(); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::AutoResponseType::DENY_ALL); |
| |
| constexpr char kPageWithPermissionPolicyHeader[] = "/page_with_header.html"; |
| content::URLLoaderInterceptor interceptor(base::BindLambdaForTesting( |
| [&](content::URLLoaderInterceptor::RequestParams* params) { |
| if (params->url_request.url.path() != kPageWithPermissionPolicyHeader) { |
| return false; |
| } |
| |
| CHECK_EQ(params->url_request.url.host_piece(), kHostB); |
| content::URLLoaderInterceptor::WriteResponse( |
| "HTTP/1.1 200 OK\n" |
| "Content-type: text/html\n" |
| "Permissions-Policy: identity-credentials-get=(self " |
| "\"https://b.com\")\n" |
| "\n", |
| /*body=*/"", params->client.get()); |
| return true; |
| })); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo( |
| GURL(base::StrCat({"https://", kHostB, kPageWithPermissionPolicyHeader})), |
| browser()); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()")); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), kNoCookies); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest, |
| FedCMGrantsAllowCookieAccessViaSAA) { |
| SetBlockThirdPartyCookies(true); |
| GrantFedCMPermission(); |
| |
| NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB}); |
| NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0"); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame( |
| GetFrame(), /*omit_user_gesture=*/true)); |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 0); |
| EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test")); |
| |
| NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB}); |
| NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0"); |
| EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest, |
| FedCMGrantsAllowCookieAccess_NestedFrame) { |
| SetBlockThirdPartyCookies(true); |
| GrantFedCMPermission(); |
| |
| NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostC, kHostB}); |
| EXPECT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture( |
| GetNestedFrame(), EchoCookiesURL(kHostB))); |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| |
| EXPECT_TRUE( |
| storage::test::RequestAndCheckStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| EXPECT_EQ(prompt_factory()->TotalRequestCount(), 0); |
| EXPECT_EQ(ReadCookies(GetNestedFrame(), kHostB), |
| CookieBundle("cross-site=b.test")); |
| |
| NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostC, kHostB}); |
| EXPECT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture( |
| GetNestedFrame(), EchoCookiesURL(kHostB))); |
| EXPECT_EQ(ReadCookiesAndContent(GetNestedFrame(), kHostB), |
| kNoCookiesWithContent); |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame())); |
| } |
| |
| class StorageAccessHeadersBrowserTest : public StorageAccessAPIBrowserTest { |
| public: |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| return { |
| // TODO(crbug.com/382291442): Remove below two once permissions policies |
| // are launched. |
| {{network::features::kPopulatePermissionsPolicyOnRequest}, {}}, |
| {{network::features::kStorageAccessHeadersRespectPermissionsPolicy}, |
| {}}, |
| }; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, RetryHeader) { |
| SetBlockThirdPartyCookies(true); |
| SetRetryAllowedOriginFromHost(kHostA); |
| |
| // Pre-seed with a <A, B> permission grant. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Now attempt to use that permission grant for a B subresource fetched by an |
| // A document, without invoking the Storage Access API. |
| NavigateToPage(kHostA, "/empty.html"); |
| EXPECT_THAT(ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"), |
| Pair(kSecFetchStorageAccess, "active")))); |
| EXPECT_EQ(retry_path_fetch_count_, 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RetryHeader_WithBlockingPermissionsPolicy) { |
| SetBlockThirdPartyCookies(true); |
| SetRetryAllowedOriginFromHost(kHostA); |
| |
| // Pre-seed with a <A, B> permission grant. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Now attempt to use that permission grant for a B subresource fetched by an |
| // A document, without invoking the Storage Access API. |
| NavigateToPageWithFrameBlockingStorageAccessPermissionsPolicy(kHostA); |
| EXPECT_THAT( |
| ContentFromFetch(ChildFrameAt(GetPrimaryMainFrame(), 0), kHostB, |
| kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, kHeaderNotProvidedSentinel), |
| Pair(kSecFetchStorageAccess, "none")))); |
| EXPECT_EQ(retry_path_fetch_count_, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, RetryHeader_Wildcard) { |
| SetBlockThirdPartyCookies(true); |
| set_retry_allowed_origin("*"); |
| |
| // Pre-seed with a <A, B> permission grant. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Now attempt to use that permission grant for a B subresource fetched by an |
| // A document, without invoking the Storage Access API. |
| NavigateToPage(kHostA, "/empty.html"); |
| EXPECT_THAT(ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"), |
| Pair(kSecFetchStorageAccess, "active")))); |
| EXPECT_EQ(retry_path_fetch_count_, 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RetryHeader_NoopWithoutGrant) { |
| SetBlockThirdPartyCookies(true); |
| SetRetryAllowedOriginFromHost(kHostA); |
| |
| // Note: we do *not* pre-seed with a <A, B> permission grant. |
| |
| // Now attempt to use the `retry` header for a B subresource fetched by an A |
| // document, without invoking the Storage Access API. |
| NavigateToPage(kHostA, "/empty.html"); |
| EXPECT_THAT( |
| ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, kHeaderNotProvidedSentinel), |
| Pair(kSecFetchStorageAccess, "none")))); |
| EXPECT_EQ(retry_path_fetch_count_, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RetryHeader_ABAContext) { |
| SetBlockThirdPartyCookies(true); |
| SetRetryAllowedOriginFromHost(kHostB); |
| |
| // Attempt to get Storage Access for an A subresource fetched by a B document |
| // (embedded under an A top-level document), without invoking the Storage |
| // Access API. There's no privacy boundary here (since the top-level site and |
| // the subresource fetch are for the same site); there's just a security |
| // boundary, which the header opt-in disables. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(GetURL(kHostB, "/empty.html")); |
| EXPECT_THAT(ContentFromFetch(GetFrame(), kHostA, kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, "cross-site=a.test"), |
| Pair(kSecFetchStorageAccess, "active")))); |
| EXPECT_EQ(retry_path_fetch_count_, 2); |
| |
| EXPECT_THAT( |
| ObservedRequestHeaders(), |
| IsSupersetOf<PathAndHeaderMapMatchers>({ |
| // The top-level page and the `fetch` call both omit the header. |
| Pair("/iframe.html", |
| AllOf(Contains(Pair("Host", StartsWith(kHostA))), |
| Not(Contains(Key(kSecFetchStorageAccess))))), |
| Pair("/empty.html", |
| AllOf(Contains(Pair("Host", StartsWith(kHostB))), |
| Not(Contains(Key(kSecFetchStorageAccess))))), |
| // The iframe's subresource fetch includes the header. |
| Pair(kRetryPath, IsSupersetOf<HeaderMapMatchers>({ |
| Pair("Host", StartsWith(kHostA)), |
| Pair(kSecFetchStorageAccess, "inactive"), |
| })), |
| Pair(kRetryPath, IsSupersetOf<HeaderMapMatchers>({ |
| Pair("Host", StartsWith(kHostA)), |
| Pair(kSecFetchStorageAccess, "active"), |
| })), |
| })); |
| } |
| |
| // Regression test for https://crbug.com/352722603. Same as |
| // `RetryHeader_ABAContext`, except that the iframe calls |
| // `document.requestStorageAccess()` before issuing the fetch. |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RetryHeader_ABAContext_WithIrrelevantApiCall) { |
| SetBlockThirdPartyCookies(true); |
| SetRetryAllowedOriginFromHost(kHostB); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(GetURL(kHostB, "/empty.html")); |
| |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| EXPECT_THAT(ContentFromFetch(GetFrame(), kHostA, kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, "cross-site=a.test"), |
| Pair(kSecFetchStorageAccess, "active")))); |
| EXPECT_EQ(retry_path_fetch_count_, 2); |
| |
| EXPECT_THAT( |
| ObservedRequestHeaders(), |
| IsSupersetOf<PathAndHeaderMapMatchers>({ |
| // The top-level page and the `fetch` call both omit the header. |
| Pair("/iframe.html", |
| AllOf(Contains(Pair("Host", StartsWith(kHostA))), |
| Not(Contains(Key(kSecFetchStorageAccess))))), |
| Pair("/empty.html", |
| AllOf(Contains(Pair("Host", StartsWith(kHostB))), |
| Not(Contains(Key(kSecFetchStorageAccess))))), |
| // The iframe's subresource fetch includes the header. |
| Pair(kRetryPath, IsSupersetOf<HeaderMapMatchers>({ |
| Pair("Host", StartsWith(kHostA)), |
| Pair(kSecFetchStorageAccess, "inactive"), |
| })), |
| Pair(kRetryPath, IsSupersetOf<HeaderMapMatchers>({ |
| Pair("Host", StartsWith(kHostA)), |
| Pair(kSecFetchStorageAccess, "active"), |
| })), |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, LoadHeader) { |
| SetBlockThirdPartyCookies(true); |
| |
| // Pre-seed with a <A, B> permission grant. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Now attempt to use that permission grant for a cross-site iframe, without |
| // invoking the Storage Access API. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(GetURL(kHostB, "/set-header?Activate-Storage-Access: load")); |
| // No need to request storage access, because we already have it. |
| EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| LoadHeader_WithBlockingPermissionsPolicy) { |
| SetBlockThirdPartyCookies(true); |
| |
| // Pre-seed with a <A, B> permission grant. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(EchoCookiesURL(kHostB)); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Now attempt to use that permission grant for a cross-site iframe, without |
| // invoking the Storage Access API. |
| NavigateToPageWithFrameBlockingStorageAccessPermissionsPolicy(kHostA); |
| NavigateFrameTo(GetURL(kHostB, "/set-header?Activate-Storage-Access: load"), |
| nullptr, "child-0"); |
| // No storage access was activated because permissions policy blocked it. |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| LoadHeader_NoopWithoutGrant) { |
| SetBlockThirdPartyCookies(true); |
| |
| // Note: we do *not* pre-seed with a <A, B> permission grant. |
| |
| // Now attempt to get storage access in a cross-site iframe, without invoking |
| // the Storage Access API. |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(GetURL(kHostB, "/set-header?Activate-Storage-Access: load")); |
| // Permission was never granted, so we don't have storage access. |
| EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RequestHeadersFirstParty) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(GetURL(kHostA)); |
| EXPECT_THAT(ObservedRequestHeaders(), |
| Each(Pair(_, Not(Contains(Key(kSecFetchStorageAccess)))))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RequestHeadersCredentialsBlocked) { |
| SetBlockThirdPartyCookies(true); |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(GetURL(kHostB)); |
| ASSERT_TRUE(content::ExecJs( |
| GetFrame(), content::JsReplace("fetch($1, {'credentials': 'omit'})", |
| GetURL(kHostB)))); |
| EXPECT_THAT( |
| ObservedRequestHeaders(), |
| IsSupersetOf<PathAndHeaderMapMatchers>({ |
| // The top-level page and the `fetch` call both omit the header. |
| Pair("/iframe.html", Not(Contains(Key(kSecFetchStorageAccess)))), |
| Pair("/", AllOf(Contains(Pair("Host", StartsWith(kHostB))), |
| Not(Contains(Key(kSecFetchStorageAccess))))), |
| // The iframe subresource fetch includes the header. |
| Pair("/", IsSupersetOf<HeaderMapMatchers>({ |
| Pair("Host", StartsWith(kHostB)), |
| Pair(kSecFetchStorageAccess, "none"), |
| })), |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, RequestHeadersNone) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA); |
| NavigateFrameTo(GetURL(kHostB)); |
| EXPECT_THAT(ObservedRequestHeaders(), |
| Contains(Pair("/", IsSupersetOf<HeaderMapMatchers>({ |
| Pair("Host", StartsWith(kHostB)), |
| Pair(kSecFetchStorageAccess, "none"), |
| })))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RequestHeadersCredentiallessFrame) { |
| SetBlockThirdPartyCookies(true); |
| |
| NavigateToPageWithFrame(kHostA, /*browser_ptr=*/nullptr, |
| /*credentialless=*/true); |
| NavigateFrameTo(GetURL(kHostB)); |
| EXPECT_THAT(ObservedRequestHeaders(), |
| Contains(Pair("/", IsSupersetOf<HeaderMapMatchers>({ |
| Pair("Host", StartsWith(kHostB)), |
| Pair(kSecFetchStorageAccess, "none"), |
| })))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RequestHeadersInactive) { |
| SetBlockThirdPartyCookies(true); |
| EnsureUserInteractionOn(kHostB); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| // Header will be 'none' first time we navigate to `kHostB` since the |
| // permission grant does not exist yet. |
| NavigateFrameTo(GetURL(kHostB)); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Top-level subresource fetches also include the "inactive" header. |
| EXPECT_EQ(CookiesFromFetch(GetPrimaryMainFrame(), kHostB), "None"); |
| EXPECT_THAT(ObservedRequestHeaders(), |
| IsSupersetOf<PathAndHeaderMapMatchers>({ |
| Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))), |
| Pair("/echocookieswithcors", |
| Contains(Pair(kSecFetchStorageAccess, "inactive"))), |
| })); |
| |
| // Subsequent navigation should be `inactive`. |
| NavigateFrameTo(GetURL(kHostB)); |
| |
| EXPECT_THAT( |
| ObservedRequestHeaders(), |
| Contains(Pair("/", Contains(Pair(kSecFetchStorageAccess, "inactive"))))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| NonCookieStorage_Subresource) { |
| SetBlockThirdPartyCookies(true); |
| EnsureUserInteractionOn(kHostB); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| // Header will be 'none' first time we navigate to `kHostB` since the |
| // permission grant does not exist yet. |
| NavigateFrameTo(GetURL(kHostB)); |
| ASSERT_TRUE(content::ExecJs( |
| GetFrame(), "document.requestStorageAccess({'localStorage': true})")); |
| |
| // Subresource fetches from the embed include the "inactive" header. |
| EXPECT_EQ(CookiesFromFetch(GetFrame(), kHostB), "None"); |
| EXPECT_THAT(ObservedRequestHeaders(), |
| IsSupersetOf<PathAndHeaderMapMatchers>({ |
| Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))), |
| Pair("/echocookieswithcors", |
| Contains(Pair(kSecFetchStorageAccess, "inactive"))), |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RequestHeaderRetryToActive) { |
| SetBlockThirdPartyCookies(true); |
| SetRetryAllowedOriginFromHost(kHostA); |
| EnsureUserInteractionOn(kHostB); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| // Header will be 'none' first time we navigate to `kHostB` since the |
| // permission grant does not exist yet. |
| NavigateFrameTo(GetURL(kHostB)); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Top-level subresource fetches can benefit from the permission. |
| EXPECT_THAT(ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"), |
| Pair(kSecFetchStorageAccess, "active")))); |
| |
| // Next navigation would be inactive, but with the `kRetryPath` we end up |
| // opting into `storage-access`, making it `active`. |
| NavigateFrameTo(GetURL(kHostB, kRetryPath)); |
| // The `fetch` call and the navigation both sent the "active" header, |
| // eventually. |
| EXPECT_THAT(ObservedRequestHeaders(), |
| Contains(Pair(kRetryPath, |
| Contains(Pair(kSecFetchStorageAccess, "active")))) |
| .Times(2)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RequestHeaderSameOriginRedirect) { |
| SetBlockThirdPartyCookies(true); |
| EnsureUserInteractionOn(kHostB); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| // Header will be 'none' first time we navigate to `kHostB` since the |
| // permission grant does not exist yet. |
| GURL dest_url = GetURL(kHostB); |
| NavigateFrameTo(dest_url); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Cause the frame to navigate itself via a same-origin redirect. |
| EXPECT_TRUE( |
| content::NavigateToURLFromRenderer(GetFrame(), |
| /*url=*/ |
| RedirectViaHosts({kHostB}, dest_url), |
| /*expected_commit_url=*/dest_url)); |
| // Since the frame navigated via a same-origin redirect, expect |
| // `Sec-Fetch-Storage-Access` to reflect a preserved `active` storage access |
| // status. |
| EXPECT_THAT(ObservedRequestHeaders(), |
| IsSupersetOf<PathAndHeaderMapMatchers>({ |
| Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))), |
| Pair(ServerRedirectPath(dest_url), |
| Contains(Pair(kSecFetchStorageAccess, "active"))), |
| Pair("/", Contains(Pair(kSecFetchStorageAccess, "active"))), |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| RequestHeaderCrossOriginRedirect) { |
| SetBlockThirdPartyCookies(true); |
| EnsureUserInteractionOn(kHostB); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| // Header will be 'none' first time we navigate to `kHostB` since the |
| // permission grant does not exist yet. |
| GURL dest_url = GetURL(kHostB); |
| NavigateFrameTo(dest_url); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| // Cause the frame to navigate itself via a cross-origin redirect. |
| EXPECT_TRUE( |
| content::NavigateToURLFromRenderer(GetFrame(), |
| /*url=*/ |
| RedirectViaHosts({kHostA}, dest_url), |
| /*expected_commit_url=*/dest_url)); |
| // Since the frame navigated via a cross-origin redirect, expect |
| // `Sec-Fetch-Storage-Access` to no longer be `active`. |
| EXPECT_THAT(ObservedRequestHeaders(), |
| IsSupersetOf<PathAndHeaderMapMatchers>({ |
| Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))), |
| Pair(ServerRedirectPath(dest_url), |
| Not(Contains(Key(kSecFetchStorageAccess)))), |
| Pair("/", Contains(Pair(kSecFetchStorageAccess, "inactive"))), |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersBrowserTest, |
| TopFrameRedirectToFirstPartyPage) { |
| SetBlockThirdPartyCookies(true); |
| EnsureUserInteractionOn(kHostB); |
| prompt_factory()->set_response_type( |
| permissions::PermissionRequestManager::ACCEPT_ALL); |
| |
| NavigateToPageWithFrame(kHostA); |
| // Header will be 'none' first time we navigate to `kHostB` since the |
| // permission grant does not exist yet. |
| NavigateFrameTo(GetURL(kHostB)); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame())); |
| |
| GURL redirect_dest_url = GetURL(kHostB, "/empty.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), RedirectViaHosts({kHostB}, redirect_dest_url))); |
| |
| // Since kHostB is top-level for the redirect, expect the |
| // `Sec-Fetch-Storage-Access` header is not included in the request. |
| EXPECT_THAT( |
| ObservedRequestHeaders(), |
| IsSupersetOf<PathAndHeaderMapMatchers>({ |
| Pair("/", Contains(Pair(kSecFetchStorageAccess, "none"))), |
| Pair(ServerRedirectPath(redirect_dest_url), |
| Not(Contains(Key(kSecFetchStorageAccess)))), |
| Pair("/empty.html", Not(Contains(Key(kSecFetchStorageAccess)))), |
| })); |
| } |
| |
| class StorageAccessHeadersWithThirdPartyCookiesBrowserTest |
| : public StorageAccessHeadersBrowserTest { |
| public: |
| std::vector<base::test::FeatureRef> GetDisabledFeatures() override { |
| std::vector<base::test::FeatureRef> features = |
| StorageAccessHeadersBrowserTest::GetDisabledFeatures(); |
| features.push_back(content_settings::features::kTrackingProtection3pcd); |
| return features; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessHeadersWithThirdPartyCookiesBrowserTest, |
| RetryHeader_NoopWhenCookiesAllowed) { |
| SetBlockThirdPartyCookies(false); |
| SetRetryAllowedOriginFromHost(kHostA); |
| |
| // Note: we do *not* pre-seed with a <A, B> permission grant. |
| |
| // Now attempt to use the `retry` header for a B subresource fetched by an A |
| // document, without invoking the Storage Access API. |
| NavigateToPage(kHostA, "/empty.html"); |
| EXPECT_THAT(ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, "cross-site=b.test"), |
| Pair(kSecFetchStorageAccess, "active")))); |
| EXPECT_EQ(retry_path_fetch_count_, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageAccessAPIAutograntsWithFedCMBrowserTest, |
| RetryHeader) { |
| SetBlockThirdPartyCookies(true); |
| SetRetryAllowedOriginFromHost(kHostA); |
| GrantFedCMPermission(); |
| |
| NavigateToPageWithPermissionsPolicyIframes({kHostA, kHostB}); |
| NavigateFrameTo(EchoCookiesURL(kHostB), browser(), /*iframe_id=*/"child-0"); |
| ASSERT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame( |
| GetFrame(), /*omit_user_gesture=*/true)); |
| |
| // Attempt to use the FedCM permission grant for a B subresource fetched by an |
| // A document, without invoking the Storage Access API. Since there was no |
| // `identity-credentials-get` permissions policy associated with this |
| // particular opt-in, the FedCM grant should not be usable via the header. |
| NavigateToPage(kHostA, "/empty.html"); |
| EXPECT_THAT( |
| ContentFromFetch(GetPrimaryMainFrame(), kHostB, kRetryPath), |
| HeadersAre(UnorderedElementsAre( |
| Pair(net::HttpRequestHeaders::kCookie, kHeaderNotProvidedSentinel), |
| Pair(kSecFetchStorageAccess, "none")))); |
| EXPECT_EQ(retry_path_fetch_count_, 1); |
| } |
| |
| class StorageAccessAPIWindowOpenTestBase |
| : public StorageAccessAPIBaseBrowserTest { |
| public: |
| void SetUpOnMainThread() override { |
| StorageAccessAPIBaseBrowserTest::SetUpOnMainThread(); |
| SetBlockThirdPartyCookies(!Are3PCEnabled()); |
| SetFirstPartyData(); |
| } |
| |
| void TearDownOnMainThread() override { |
| new_prompt_factory_.reset(); |
| StorageAccessAPIBaseBrowserTest::TearDownOnMainThread(); |
| } |
| |
| std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override { |
| std::vector<base::test::FeatureRefAndParams> enabled_features( |
| {{blink::features::kPartitionedPopins, {}}}); |
| if (AreTrackingProtectionsEnabled()) { |
| enabled_features.emplace_back( |
| content_settings::features::kTrackingProtection3pcd, |
| base::FieldTrialParams{}); |
| } |
| return enabled_features; |
| } |
| |
| std::vector<base::test::FeatureRef> GetDisabledFeatures() override { |
| std::vector<base::test::FeatureRef> disabled_features; |
| if (!AreTrackingProtectionsEnabled()) { |
| disabled_features.emplace_back( |
| content_settings::features::kTrackingProtection3pcd); |
| } |
| return disabled_features; |
| } |
| |
| protected: |
| virtual bool Are3PCEnabled() const = 0; |
| |
| virtual bool AreTrackingProtectionsEnabled() const = 0; |
| |
| virtual bool ArePermissionPromptsAccepted() const = 0; |
| |
| virtual std::string MainFrameHost() const = 0; |
| |
| bool Are3PCFullyEnabled() const { |
| return Are3PCEnabled() && !AreTrackingProtectionsEnabled(); |
| } |
| |
| void SetupPromptFactoryForNewWebContents( |
| content::WebContents* new_web_contents) { |
| new_prompt_factory_ = |
| std::make_unique<permissions::MockPermissionPromptFactory>( |
| permissions::PermissionRequestManager::FromWebContents( |
| new_web_contents)); |
| new_prompt_factory_->set_response_type( |
| ArePermissionPromptsAccepted() |
| ? permissions::PermissionRequestManager::ACCEPT_ALL |
| : permissions::PermissionRequestManager::DENY_ALL); |
| } |
| |
| void ExpectNoStorageAccessGrants() { |
| EXPECT_THAT( |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(kExpectedSettingDefault)); |
| } |
| |
| void ExpectOneStorageAccessGrantFor(std::string_view frame_host, |
| std::string_view embedder_host) { |
| std::vector<ContentSettingPatternSource> settings = |
| HostContentSettingsMapFactory::GetForProfile(browser()->profile()) |
| ->GetSettingsForOneType(ContentSettingsType::STORAGE_ACCESS); |
| for (ContentSettingPatternSource& setting : settings) { |
| // We aren't trying to verify metadata so it's easier to clear it. |
| setting.metadata = content_settings::RuleMetaData(); |
| } |
| EXPECT_THAT( |
| settings, |
| UnorderedElementsAre( |
| kExpectedSettingDefault, |
| ContentSettingPatternSource( |
| ContentSettingsPattern::FromURLToSchemefulSitePattern( |
| GetURL(frame_host)), |
| ContentSettingsPattern::FromURLToSchemefulSitePattern( |
| GetURL(embedder_host)), |
| content_settings::ContentSettingToValue( |
| ArePermissionPromptsAccepted() ? CONTENT_SETTING_ALLOW |
| : CONTENT_SETTING_BLOCK), |
| content_settings::ProviderType::kPrefProvider, |
| /*incognito=*/false))); |
| } |
| |
| content::EvalJsResult DoesFrameHaveLocalStorageForHost( |
| content::RenderFrameHost* frame, |
| std::string_view host) { |
| return content::EvalJs( |
| frame, content::JsReplace("!!window.localStorage.getItem($1)", host)); |
| } |
| |
| content::EvalJsResult DoesFrameAllowRequestStorageAccessForHost( |
| content::RenderFrameHost* frame, |
| std::string_view host) { |
| return content::EvalJs( |
| frame, |
| content::JsReplace("document.requestStorageAccess({" |
| " localStorage: true," |
| " cookies: true," |
| "}).then(" |
| " (handle) => !!handle &&" |
| " !!handle.localStorage.getItem($1)," |
| " () => false" |
| ")", |
| host)); |
| } |
| |
| private: |
| void SetFirstPartyData() { |
| NavigateToPage(kHostA, "/empty.html"); |
| EXPECT_TRUE(content::ExecJs( |
| GetPrimaryMainFrame(), |
| content::JsReplace("window.localStorage.setItem($1, $1);", kHostA))); |
| NavigateToPage(kHostB, "/empty.html"); |
| EXPECT_TRUE(content::ExecJs( |
| GetPrimaryMainFrame(), |
| content::JsReplace("window.localStorage.setItem($1, $1);", kHostB))); |
| NavigateToPage(kHostC, "/empty.html"); |
| EXPECT_TRUE(content::ExecJs( |
| GetPrimaryMainFrame(), |
| content::JsReplace("window.localStorage.setItem($1, $1);", kHostC))); |
| } |
| |
| std::unique_ptr<permissions::MockPermissionPromptFactory> new_prompt_factory_; |
| }; |
| |
| class StorageAccessAPIWindowOpenMainFrameTest |
| : public StorageAccessAPIWindowOpenTestBase, |
| public testing::WithParamInterface< |
| std::tuple<bool, bool, bool, std::string>> { |
| protected: |
| bool Are3PCEnabled() const override { return std::get<0>(GetParam()); } |
| |
| bool AreTrackingProtectionsEnabled() const override { |
| return std::get<1>(GetParam()); |
| } |
| |
| bool ArePermissionPromptsAccepted() const override { |
| return std::get<2>(GetParam()); |
| } |
| |
| std::string MainFrameHost() const override { return std::get<3>(GetParam()); } |
| |
| bool IsCrossOriginToOpenerFrame() const { return kHostA != MainFrameHost(); } |
| }; |
| |
| // Opens a popup and checks that the RSA call within the main frame fails. |
| IN_PROC_BROWSER_TEST_P(StorageAccessAPIWindowOpenMainFrameTest, |
| PopupRSAMainFrameTest) { |
| // Navigate to site a and open popup of site b. |
| NavigateToPage(kHostA, "/empty.html"); |
| content::WebContentsAddedObserver new_tab_observer; |
| content::TestNavigationObserver nav_observer(nullptr); |
| nav_observer.StartWatchingNewWebContents(); |
| EXPECT_TRUE(content::ExecJs( |
| browser()->tab_strip_model()->GetActiveWebContents(), |
| content::JsReplace("window.open($1, '_blank', 'popup')", |
| EchoCookiesURL(MainFrameHost()).spec()))); |
| content::WebContents* popup_web_contents = new_tab_observer.GetWebContents(); |
| nav_observer.Wait(); |
| SetupPromptFactoryForNewWebContents(popup_web_contents); |
| |
| // Expect first-party data for popup. |
| ASSERT_EQ(!IsCrossOriginToOpenerFrame(), |
| DoesFrameHaveLocalStorageForHost( |
| popup_web_contents->GetPrimaryMainFrame(), kHostA)); |
| ASSERT_EQ(IsCrossOriginToOpenerFrame(), |
| DoesFrameHaveLocalStorageForHost( |
| popup_web_contents->GetPrimaryMainFrame(), kHostB)); |
| EXPECT_EQ(ReadCookiesAndContent(popup_web_contents->GetPrimaryMainFrame(), |
| MainFrameHost()), |
| CookieBundleWithContent("cross-site=" + MainFrameHost())); |
| |
| // Expect no custom content settings related to storage access. |
| ExpectNoStorageAccessGrants(); |
| |
| // Expect no handle returned for popup. |
| EXPECT_EQ(false, |
| DoesFrameAllowRequestStorageAccessForHost( |
| popup_web_contents->GetPrimaryMainFrame(), MainFrameHost())); |
| |
| // Expect no custom content settings related to storage access. |
| ExpectNoStorageAccessGrants(); |
| } |
| |
| // Opens a popin and checks that the RSA call within the main frame can succeed. |
| IN_PROC_BROWSER_TEST_P(StorageAccessAPIWindowOpenMainFrameTest, |
| PopinRSAMainFrameTest) { |
| // Navigate to site a and open popin of site b. |
| NavigateToPage(kHostA, "/empty.html"); |
| content::WebContentsAddedObserver new_tab_observer; |
| content::TestNavigationObserver nav_observer(nullptr); |
| nav_observer.StartWatchingNewWebContents(); |
| EXPECT_TRUE(content::ExecJs( |
| browser()->tab_strip_model()->GetActiveWebContents(), |
| content::JsReplace( |
| "window.open($1 + '?allow_popins=true', '_blank', 'popin')", |
| GetURL(MainFrameHost(), "/empty.html").spec()))); |
| content::WebContents* popin_web_contents = new_tab_observer.GetWebContents(); |
| nav_observer.Wait(); |
| SetupPromptFactoryForNewWebContents(popin_web_contents); |
| |
| // Expect no first-party data for cross-origin popin. |
| ASSERT_EQ(!IsCrossOriginToOpenerFrame(), |
| DoesFrameHaveLocalStorageForHost( |
| popin_web_contents->GetPrimaryMainFrame(), kHostA)); |
| ASSERT_EQ(false, DoesFrameHaveLocalStorageForHost( |
| popin_web_contents->GetPrimaryMainFrame(), kHostB)); |
| |
| // Expect 3p cookies only if enabled. |
| const auto& cookie_before = |
| ReadCookies(popin_web_contents->GetPrimaryMainFrame(), MainFrameHost()); |
| if (!IsCrossOriginToOpenerFrame() || Are3PCFullyEnabled()) { |
| EXPECT_EQ(cookie_before, CookieBundle("cross-site=" + MainFrameHost())); |
| } else { |
| EXPECT_EQ(cookie_before, kNoCookies); |
| } |
| |
| // Expect no custom content settings related to storage access. |
| ExpectNoStorageAccessGrants(); |
| |
| // Expect handle returned for popin if the popin is same-origin, if it accepts |
| // all prompts, or if 3pc are enabled and tracking protection is disabled. |
| EXPECT_EQ(!IsCrossOriginToOpenerFrame() || ArePermissionPromptsAccepted() || |
| Are3PCFullyEnabled(), |
| DoesFrameAllowRequestStorageAccessForHost( |
| popin_web_contents->GetPrimaryMainFrame(), MainFrameHost())); |
| |
| // Expect 3p cookies added only if granted. |
| const auto& cookie_after = |
| ReadCookies(popin_web_contents->GetPrimaryMainFrame(), MainFrameHost()); |
| if (!IsCrossOriginToOpenerFrame() || ArePermissionPromptsAccepted() || |
| Are3PCFullyEnabled()) { |
| EXPECT_EQ(cookie_after, CookieBundle("cross-site=" + MainFrameHost())); |
| } else { |
| EXPECT_EQ(cookie_after, kNoCookies); |
| } |
| |
| // Expect custom content settings related to storage access if an explicit |
| // deny/allow occurred. |
| if (!IsCrossOriginToOpenerFrame() || Are3PCFullyEnabled()) { |
| ExpectNoStorageAccessGrants(); |
| } else { |
| ExpectOneStorageAccessGrantFor(kHostB, kHostA); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /*no prefix*/, |
| StorageAccessAPIWindowOpenMainFrameTest, |
| testing::Combine(/*3pc_enabled=*/testing::Bool(), |
| /*tracking_protections_enabled=*/testing::Bool(), |
| /*permission_prompts_accepted=*/testing::Bool(), |
| /*main_frame_host=*/testing::Values(kHostA, kHostB)), |
| [](const testing::TestParamInfo<std::tuple<bool, bool, bool, std::string>>& |
| info) { |
| return base::StringPrintf( |
| "%s_%s_%s_main_%c", |
| std::get<0>(info.param) ? "3PCEnabled" : "3PCDisabled", |
| std::get<1>(info.param) ? "TrackingProtectionsEnabled" |
| : "TrackingProtectionsDisabled", |
| std::get<2>(info.param) ? "PermissionPromptsAccepted" |
| : "PermissionPromptsDenied", |
| std::get<3>(info.param)[0]); |
| }); |
| |
| class StorageAccessAPIWindowOpenSubFrameTest |
| : public StorageAccessAPIWindowOpenTestBase, |
| public testing::WithParamInterface< |
| std::tuple<bool, bool, bool, std::string, std::string>> { |
| protected: |
| bool Are3PCEnabled() const override { return std::get<0>(GetParam()); } |
| |
| bool AreTrackingProtectionsEnabled() const override { |
| return std::get<1>(GetParam()); |
| } |
| |
| bool ArePermissionPromptsAccepted() const override { |
| return std::get<2>(GetParam()); |
| } |
| |
| std::string MainFrameHost() const override { return std::get<3>(GetParam()); } |
| |
| std::string SubFrameHost() const { return std::get<4>(GetParam()); } |
| |
| bool IsCrossOriginToMainFrame() const { |
| return MainFrameHost() != SubFrameHost(); |
| } |
| |
| bool IsCrossOriginToOpenerFrame() const { return kHostA != SubFrameHost(); } |
| |
| bool IsCrossOriginToOpenerOrMainFrame() const { |
| return IsCrossOriginToMainFrame() || IsCrossOriginToOpenerFrame(); |
| } |
| }; |
| |
| // Opens a popin and checks that the RSA call within the sub frame can succeed. |
| IN_PROC_BROWSER_TEST_P(StorageAccessAPIWindowOpenSubFrameTest, |
| PopinRSASubFrameTest) { |
| // Navigate to site a and open popin. |
| NavigateToPage(kHostA, "/empty.html"); |
| content::WebContentsAddedObserver new_tab_observer; |
| content::TestNavigationObserver main_nav_observer(nullptr); |
| main_nav_observer.StartWatchingNewWebContents(); |
| EXPECT_TRUE(content::ExecJs( |
| browser()->tab_strip_model()->GetActiveWebContents(), |
| content::JsReplace( |
| "window.open($1 + '?allow_popins=true', '_blank', 'popin')", |
| GetURL(MainFrameHost(), "/empty.html").spec()))); |
| content::WebContents* popin_web_contents = new_tab_observer.GetWebContents(); |
| main_nav_observer.Wait(); |
| SetupPromptFactoryForNewWebContents(popin_web_contents); |
| |
| // Open a subframe in the popin. |
| content::TestNavigationObserver sub_nav_observer(popin_web_contents, 1); |
| EXPECT_TRUE(content::ExecJs( |
| popin_web_contents, |
| content::JsReplace("const iframe = document.createElement('iframe');" |
| "iframe.src = $1;" |
| "document.body.appendChild(iframe);", |
| GetURL(SubFrameHost(), "/title1.html").spec()))); |
| sub_nav_observer.Wait(); |
| content::RenderFrameHost* popin_sub_frame_host = |
| content::FrameMatchingPredicate( |
| popin_web_contents->GetPrimaryPage(), |
| base::BindRepeating([](content::RenderFrameHost* rfh) { |
| return rfh->GetLastCommittedURL().path_piece() == "/title1.html"; |
| })); |
| |
| // Expect no first-party data for cross-origin popin subframes. |
| ASSERT_EQ(!IsCrossOriginToOpenerOrMainFrame(), |
| DoesFrameHaveLocalStorageForHost(popin_sub_frame_host, kHostA)); |
| ASSERT_EQ(false, |
| DoesFrameHaveLocalStorageForHost(popin_sub_frame_host, kHostB)); |
| ASSERT_EQ(false, |
| DoesFrameHaveLocalStorageForHost(popin_sub_frame_host, kHostC)); |
| |
| // Expect 3p cookies only if enabled. |
| const auto& cookie_before = ReadCookies(popin_sub_frame_host, SubFrameHost()); |
| if (!IsCrossOriginToOpenerOrMainFrame() || Are3PCFullyEnabled()) { |
| EXPECT_EQ(cookie_before, CookieBundle("cross-site=" + SubFrameHost())); |
| } else { |
| EXPECT_EQ(cookie_before, kNoCookies); |
| } |
| |
| // Expect no custom content settings related to storage access. |
| ExpectNoStorageAccessGrants(); |
| |
| // Expect handle returned for popin if subframe is same-origin, if it accepts |
| // all prompts, or if 3pc are enabled and tracking protection is disabled. |
| EXPECT_EQ(!IsCrossOriginToOpenerFrame() || ArePermissionPromptsAccepted() || |
| Are3PCFullyEnabled(), |
| DoesFrameAllowRequestStorageAccessForHost(popin_sub_frame_host, |
| SubFrameHost())); |
| |
| // Expect 3p cookies added only if granted. |
| const auto& cookie_after = ReadCookies(popin_sub_frame_host, SubFrameHost()); |
| if (!IsCrossOriginToOpenerFrame() || ArePermissionPromptsAccepted() || |
| Are3PCFullyEnabled()) { |
| EXPECT_EQ(cookie_after, CookieBundle("cross-site=" + SubFrameHost())); |
| } else { |
| EXPECT_EQ(cookie_after, kNoCookies); |
| } |
| |
| // Expect custom content settings related to storage access if an explicit |
| // deny/allow occurred. |
| if (!IsCrossOriginToOpenerFrame() || Are3PCFullyEnabled()) { |
| ExpectNoStorageAccessGrants(); |
| } else { |
| ExpectOneStorageAccessGrantFor(SubFrameHost(), kHostA); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /*no prefix*/, |
| StorageAccessAPIWindowOpenSubFrameTest, |
| testing::Combine( |
| /*3pc_enabled=*/testing::Bool(), |
| /*tracking_protections_enabled=*/testing::Bool(), |
| /*permission_prompts_accepted=*/testing::Bool(), |
| /*main_frame_host=*/testing::Values(kHostA, kHostB), |
| /*sub_frame_host=*/testing::Values(kHostA, kHostB, kHostC)), |
| [](const testing::TestParamInfo< |
| std::tuple<bool, bool, bool, std::string, std::string>>& info) { |
| return base::StringPrintf( |
| "%s_%s_%s_main_%c_sub_%c", |
| std::get<0>(info.param) ? "3PCEnabled" : "3PCDisabled", |
| std::get<1>(info.param) ? "TrackingProtectionsEnabled" |
| : "TrackingProtectionsDisabled", |
| std::get<2>(info.param) ? "PermissionPromptsAccepted" |
| : "PermissionPromptsDenied", |
| std::get<3>(info.param)[0], std::get<4>(info.param)[0]); |
| }); |
| |
| } // namespace |