| // 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 "chrome/browser/storage_access_api/storage_access_grant_permission_context.h" |
| |
| #include <memory> |
| |
| #include "base/barrier_callback.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "base/version.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/content_settings/page_specific_content_settings_delegate.h" |
| #include "chrome/browser/dips/dips_service.h" |
| #include "chrome/browser/dips/dips_utils.h" |
| #include "chrome/browser/first_party_sets/scoped_mock_first_party_sets_handler.h" |
| #include "chrome/browser/webid/federated_identity_permission_context.h" |
| #include "chrome/browser/webid/federated_identity_permission_context_factory.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.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_constraints.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/pref_names.h" |
| #include "components/permissions/constants.h" |
| #include "components/permissions/features.h" |
| #include "components/permissions/permission_request_id.h" |
| #include "components/permissions/permission_request_manager.h" |
| #include "components/permissions/permission_util.h" |
| #include "components/permissions/test/mock_permission_prompt_factory.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/first_party_sets/first_party_set_entry.h" |
| #include "net/first_party_sets/global_first_party_sets.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" |
| |
| namespace { |
| |
| using testing::_; |
| using testing::ElementsAre; |
| using testing::IsEmpty; |
| using testing::Pair; |
| using testing::UnorderedElementsAre; |
| using PermissionStatus = blink::mojom::PermissionStatus; |
| |
| constexpr char kGrantIsImplicitHistogram[] = |
| "API.StorageAccess.GrantIsImplicit"; |
| constexpr char kPromptResultHistogram[] = "Permissions.Action.StorageAccess"; |
| constexpr char kRequestOutcomeHistogram[] = "API.StorageAccess.RequestOutcome"; |
| |
| GURL GetTopLevelURL() { |
| return GURL("https://embedder.com"); |
| } |
| |
| GURL GetTopLevelURLSubdomain() { |
| return GURL("https://sub.embedder.com"); |
| } |
| |
| GURL GetDummyEmbeddingUrlWithSubdomain() { |
| return GURL("https://subdomain.example_embedder_1.com"); |
| } |
| |
| GURL GetRequesterURL() { |
| return GURL("https://requester.example.com"); |
| } |
| |
| net::SchemefulSite GetRequesterSite() { |
| return net::SchemefulSite(GetRequesterURL()); |
| } |
| |
| GURL GetRequesterURLSubdomain() { |
| return GURL("https://another-requester.example.com"); |
| } |
| |
| GURL GetDummyEmbeddingUrl(int dummy_id) { |
| return GURL(std::string(url::kHttpsScheme) + "://example_embedder_" + |
| base::NumberToString(dummy_id) + ".com"); |
| } |
| |
| } // namespace |
| |
| class StorageAccessGrantPermissionContextTest |
| : public ChromeRenderViewHostTestHarness { |
| public: |
| StorageAccessGrantPermissionContextTest() = default; |
| |
| void SetUp() override { |
| std::vector<base::test::FeatureRefAndParams> enabled; |
| std::vector<base::test::FeatureRef> disabled; |
| features_.InitWithFeaturesAndParameters(enabled, disabled); |
| ChromeRenderViewHostTestHarness::SetUp(); |
| |
| // Ensure we are navigated to some page so that the proper views get setup. |
| NavigateAndCommit(GetTopLevelURL()); |
| |
| // Create PermissionRequestManager. |
| permissions::PermissionRequestManager::CreateForWebContents(web_contents()); |
| |
| mock_permission_prompt_factory_ = |
| std::make_unique<permissions::MockPermissionPromptFactory>( |
| permissions::PermissionRequestManager::FromWebContents( |
| web_contents())); |
| |
| // Enable 3p cookie blocking. |
| profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>( |
| content_settings::CookieControlsMode::kBlockThirdParty)); |
| |
| content_settings::PageSpecificContentSettings::CreateForWebContents( |
| web_contents(), |
| std::make_unique<chrome::PageSpecificContentSettingsDelegate>( |
| web_contents())); |
| |
| DIPSService* dips_service = DIPSService::Get(browser_context()); |
| CHECK(dips_service); |
| base::test::TestFuture<void> future; |
| dips_service->storage() |
| ->AsyncCall(&DIPSStorage::RecordInteraction) |
| .WithArgs(GetRequesterURL(), base::Time::Now(), |
| DIPSCookieMode::kBlock3PC) |
| .Then(future.GetCallback()); |
| ASSERT_TRUE(future.Wait()); |
| permission_context_ = |
| std::make_unique<StorageAccessGrantPermissionContext>(profile()); |
| } |
| |
| void TearDown() override { |
| permission_context_.reset(); |
| mock_permission_prompt_factory_.reset(); |
| ChromeRenderViewHostTestHarness::TearDown(); |
| } |
| |
| std::unique_ptr<base::test::TestFuture<ContentSetting>> DecidePermission( |
| bool user_gesture) { |
| auto future = std::make_unique<base::test::TestFuture<ContentSetting>>(); |
| permission_context_->DecidePermissionForTesting( |
| permissions::PermissionRequestData(permission_context(), CreateFakeID(), |
| user_gesture, GetRequesterURL(), |
| GetTopLevelURL()), |
| future->GetCallback()); |
| return future; |
| } |
| |
| ContentSetting DecidePermissionSync(bool user_gesture) { |
| return DecidePermission(user_gesture)->Get(); |
| } |
| |
| ContentSetting RequestPermissionSync() { |
| base::test::TestFuture<ContentSetting> future; |
| permission_context()->RequestPermission( |
| permissions::PermissionRequestData(permission_context(), CreateFakeID(), |
| /*user_gesture=*/true, |
| GetRequesterURL()), |
| future.GetCallback()); |
| |
| return future.Get(); |
| } |
| |
| // Helper to ensure that a given content setting is consistently applied on a |
| // cross-site scope. |
| void CheckCrossSiteContentSettings(ContentSetting expected_setting) { |
| HostContentSettingsMap* settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| DCHECK(settings_map); |
| |
| auto setting = |
| settings_map->GetContentSetting(GetRequesterURL(), GetTopLevelURL(), |
| ContentSettingsType::STORAGE_ACCESS); |
| |
| EXPECT_EQ(setting, expected_setting); |
| |
| setting = settings_map->GetContentSetting( |
| GetRequesterURLSubdomain(), GetTopLevelURL(), |
| ContentSettingsType::STORAGE_ACCESS); |
| |
| EXPECT_EQ(setting, expected_setting); |
| |
| setting = settings_map->GetContentSetting( |
| GetRequesterURLSubdomain(), GetTopLevelURLSubdomain(), |
| ContentSettingsType::STORAGE_ACCESS); |
| |
| EXPECT_EQ(setting, expected_setting); |
| |
| setting = settings_map->GetContentSetting( |
| GetRequesterURL(), GetTopLevelURLSubdomain(), |
| ContentSettingsType::STORAGE_ACCESS); |
| |
| EXPECT_EQ(setting, expected_setting); |
| } |
| |
| permissions::PermissionRequestID CreateFakeID() { |
| return permissions::PermissionRequestID( |
| web_contents()->GetPrimaryMainFrame(), |
| request_id_generator_.GenerateNextId()); |
| } |
| |
| void WaitUntilPrompt() { |
| mock_permission_prompt_factory_->WaitForPermissionBubble(); |
| ASSERT_TRUE(request_manager()->IsRequestInProgress()); |
| } |
| |
| content_settings::PageSpecificContentSettings* |
| page_specific_content_settings() { |
| return content_settings::PageSpecificContentSettings::GetForFrame( |
| web_contents()->GetPrimaryMainFrame()); |
| } |
| |
| StorageAccessGrantPermissionContext* permission_context() { |
| return permission_context_.get(); |
| } |
| |
| permissions::PermissionRequestManager* request_manager() { |
| permissions::PermissionRequestManager* manager = |
| permissions::PermissionRequestManager::FromWebContents(web_contents()); |
| CHECK(manager); |
| return manager; |
| } |
| |
| permissions::MockPermissionPromptFactory& prompt_factory() { |
| return *mock_permission_prompt_factory_; |
| } |
| |
| base::HistogramTester& histogram_tester() { return histogram_tester_; } |
| |
| private: |
| base::test::ScopedFeatureList features_; |
| base::HistogramTester histogram_tester_; |
| std::unique_ptr<StorageAccessGrantPermissionContext> permission_context_; |
| std::unique_ptr<permissions::MockPermissionPromptFactory> |
| mock_permission_prompt_factory_; |
| permissions::PermissionRequestID::RequestLocalId::Generator |
| request_id_generator_; |
| first_party_sets::ScopedMockFirstPartySetsHandler first_party_sets_handler_; |
| }; |
| |
| TEST_F(StorageAccessGrantPermissionContextTest, InsecureOriginsDisallowed) { |
| GURL insecure_url = GURL("http://www.example.com"); |
| EXPECT_FALSE(permission_context()->IsPermissionAvailableToOrigins( |
| insecure_url, insecure_url)); |
| EXPECT_FALSE(permission_context()->IsPermissionAvailableToOrigins( |
| insecure_url, GetRequesterURL())); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| } |
| |
| // Test that after a successful explicit storage access grant, there's a content |
| // setting that applies on an (embedded site, top-level site) scope. |
| TEST_F(StorageAccessGrantPermissionContextTest, |
| ExplicitGrantAcceptCrossSiteContentSettings) { |
| // Assert that all content settings are in their initial state. |
| CheckCrossSiteContentSettings(ContentSetting::CONTENT_SETTING_ASK); |
| |
| auto future = DecidePermission(/*user_gesture=*/true); |
| WaitUntilPrompt(); |
| |
| // Accept the prompt and validate we get the expected setting back in our |
| // callback. |
| request_manager()->Accept(); |
| EXPECT_EQ(CONTENT_SETTING_ALLOW, future->Get()); |
| |
| histogram_tester().ExpectUniqueSample(kGrantIsImplicitHistogram, |
| /*sample=*/false, 1); |
| histogram_tester().ExpectUniqueSample( |
| kPromptResultHistogram, /*sample=*/permissions::PermissionAction::GRANTED, |
| 1); |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, /*sample=*/RequestOutcome::kGrantedByUser, 1); |
| |
| // Assert that the permission grant set a content setting that applies |
| // at the right scope. |
| CheckCrossSiteContentSettings(ContentSetting::CONTENT_SETTING_ALLOW); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(GetRequesterSite(), true))); |
| } |
| |
| // When the Storage Access API feature is enabled and we have a user gesture we |
| // should get a decision. |
| TEST_F(StorageAccessGrantPermissionContextTest, PermissionDecided) { |
| auto future = DecidePermission(/*user_gesture=*/true); |
| WaitUntilPrompt(); |
| |
| permissions::PermissionRequest* request = |
| request_manager()->Requests().front(); |
| ASSERT_TRUE(request); |
| ASSERT_EQ(1u, request_manager()->Requests().size()); |
| // Prompt should have both origins. |
| EXPECT_EQ(GetRequesterURL(), request_manager()->GetRequestingOrigin()); |
| EXPECT_EQ(GetTopLevelURL(), request_manager()->GetEmbeddingOrigin()); |
| |
| request_manager()->Dismiss(); |
| EXPECT_EQ(CONTENT_SETTING_ASK, future->Get()); |
| histogram_tester().ExpectUniqueSample(kRequestOutcomeHistogram, |
| RequestOutcome::kDismissedByUser, 1); |
| // Expect no pscs entry for dismissed permissions. |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| } |
| |
| // No user gesture should force a permission rejection. |
| TEST_F(StorageAccessGrantPermissionContextTest, |
| PermissionDeniedWithoutUserGesture) { |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, |
| DecidePermissionSync(/*user_gesture=*/false)); |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, RequestOutcome::kDeniedByPrerequisites, 1); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| } |
| |
| TEST_F(StorageAccessGrantPermissionContextTest, PermissionGrantReused) { |
| auto* map = HostContentSettingsMapFactory::GetForProfile(profile()); |
| map->SetContentSettingDefaultScope(GetRequesterURL(), GetTopLevelURL(), |
| ContentSettingsType::STORAGE_ACCESS, |
| CONTENT_SETTING_ALLOW); |
| RequestPermissionSync(); |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, RequestOutcome::kReusedPreviousDecision, 1); |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(GetRequesterSite(), true))); |
| } |
| |
| TEST_F(StorageAccessGrantPermissionContextTest, BlockReused) { |
| auto* map = HostContentSettingsMapFactory::GetForProfile(profile()); |
| map->SetContentSettingDefaultScope(GetRequesterURL(), GetTopLevelURL(), |
| ContentSettingsType::STORAGE_ACCESS, |
| CONTENT_SETTING_BLOCK); |
| RequestPermissionSync(); |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, RequestOutcome::kReusedPreviousDecision, 1); |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(GetRequesterSite(), true))); |
| } |
| |
| TEST_F(StorageAccessGrantPermissionContextTest, FpsGrantReused) { |
| auto* map = HostContentSettingsMapFactory::GetForProfile(profile()); |
| content_settings::ContentSettingConstraints constraint; |
| constraint.set_session_model( |
| content_settings::mojom::SessionModel::NON_RESTORABLE_USER_SESSION); |
| map->SetContentSettingDefaultScope(GetRequesterURL(), GetTopLevelURL(), |
| ContentSettingsType::STORAGE_ACCESS, |
| CONTENT_SETTING_ALLOW, constraint); |
| |
| RequestPermissionSync(); |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, RequestOutcome::kReusedImplicitGrant, 1); |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| } |
| |
| TEST_F(StorageAccessGrantPermissionContextTest, |
| PermissionStatusAsksWhenFeatureEnabled) { |
| EXPECT_EQ(PermissionStatus::ASK, |
| permission_context() |
| ->GetPermissionStatus(/*render_frame_host=*/nullptr, |
| GetRequesterURL(), GetTopLevelURL()) |
| .status); |
| } |
| |
| // When 3p cookie access is already allowed by user-agent-specific cookie |
| // settings, request should be allowed without granting an explicit storage |
| // access permission. |
| TEST_F(StorageAccessGrantPermissionContextTest, AllowedByCookieSettings) { |
| // Allow 3p cookies. |
| profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>(content_settings::CookieControlsMode::kOff)); |
| |
| // User gesture is not needed. |
| EXPECT_EQ(CONTENT_SETTING_ALLOW, |
| DecidePermissionSync(/*user_gesture=*/false)); |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, RequestOutcome::kAllowedByCookieSettings, 1); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| } |
| |
| // When 3p cookie access is blocked by user explicitly, request should be denied |
| // without prompting. |
| TEST_F(StorageAccessGrantPermissionContextTest, DeniedByCookieSettings) { |
| HostContentSettingsMap* settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| settings_map->SetContentSettingDefaultScope( |
| GetRequesterURL(), GetTopLevelURL(), ContentSettingsType::COOKIES, |
| CONTENT_SETTING_BLOCK); |
| |
| // User gesture is not needed. |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, |
| DecidePermissionSync(/*user_gesture=*/false)); |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, RequestOutcome::kDeniedByCookieSettings, 1); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| } |
| |
| class StorageAccessGrantPermissionContextAPIWithImplicitGrantsTest |
| : public StorageAccessGrantPermissionContextTest { |
| public: |
| StorageAccessGrantPermissionContextAPIWithImplicitGrantsTest() { |
| StorageAccessGrantPermissionContext::SetImplicitGrantLimitForTesting(5); |
| } |
| |
| // Helper to request storage access on enough unique embedding_origin GURLs |
| // from |requesting_origin| to ensure that all potential implicit grants will |
| // be granted. |
| void ExhaustImplicitGrants(const GURL& requesting_origin) { |
| permissions::PermissionRequestID fake_id = CreateFakeID(); |
| |
| const int implicit_grant_limit = |
| StorageAccessGrantPermissionContext::GetImplicitGrantLimitForTesting(); |
| base::test::TestFuture<const std::vector<ContentSetting>> future; |
| auto barrier = base::BarrierCallback<ContentSetting>(implicit_grant_limit, |
| future.GetCallback()); |
| for (int grant_id = 0; grant_id < implicit_grant_limit; grant_id++) { |
| permission_context()->DecidePermissionForTesting( |
| permissions::PermissionRequestData(permission_context(), fake_id, |
| /*user_gesture=*/true, |
| requesting_origin, |
| GetDummyEmbeddingUrl(grant_id)), |
| barrier); |
| } |
| ASSERT_TRUE(future.Wait()); |
| EXPECT_FALSE(request_manager()->IsRequestInProgress()); |
| } |
| |
| private: |
| }; |
| |
| // Validate that each requesting origin has its own implicit grant limit. If |
| // the limit for one origin is exhausted it should not affect another. |
| TEST_F(StorageAccessGrantPermissionContextAPIWithImplicitGrantsTest, |
| ImplicitGrantLimitPerRequestingOrigin) { |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 0); |
| |
| ExhaustImplicitGrants(GetRequesterURL()); |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 5); |
| histogram_tester().ExpectBucketCount(kGrantIsImplicitHistogram, |
| /*sample=*/true, 5); |
| EXPECT_EQ(histogram_tester().GetBucketCount( |
| kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance), |
| 5); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| { |
| auto future = DecidePermission(/*user_gesture=*/true); |
| WaitUntilPrompt(); |
| |
| // Close the prompt and validate we get the expected setting back in our |
| // callback. |
| request_manager()->Dismiss(); |
| EXPECT_EQ(CONTENT_SETTING_ASK, future->Get()); |
| } |
| EXPECT_EQ(histogram_tester().GetBucketCount(kRequestOutcomeHistogram, |
| RequestOutcome::kDismissedByUser), |
| 1); |
| |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 5); |
| histogram_tester().ExpectBucketCount(kGrantIsImplicitHistogram, |
| /*sample=*/true, 5); |
| histogram_tester().ExpectTotalCount(kPromptResultHistogram, 1); |
| histogram_tester().ExpectBucketCount( |
| kPromptResultHistogram, |
| /*sample=*/permissions::PermissionAction::DISMISSED, 1); |
| |
| GURL alternate_requester_url = GURL("https://requester2_example.com"); |
| |
| // However now if a different requesting origin makes a request we should see |
| // it gets auto-granted as the limit has not been reached for it yet. |
| base::test::TestFuture<ContentSetting> future; |
| permission_context()->DecidePermissionForTesting( |
| permissions::PermissionRequestData( |
| permission_context(), CreateFakeID(), /*user_gesture=*/true, |
| alternate_requester_url, GetTopLevelURL()), |
| future.GetCallback()); |
| |
| // We should have no prompts still and our latest result should be an allow. |
| EXPECT_EQ(CONTENT_SETTING_ALLOW, future.Get()); |
| EXPECT_FALSE(request_manager()->IsRequestInProgress()); |
| EXPECT_EQ(histogram_tester().GetBucketCount( |
| kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance), |
| 6); |
| |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 6); |
| histogram_tester().ExpectBucketCount(kGrantIsImplicitHistogram, |
| /*sample=*/true, 6); |
| histogram_tester().ExpectBucketCount( |
| kPromptResultHistogram, |
| /*sample=*/permissions::PermissionAction::DISMISSED, 1); |
| } |
| |
| // Validate that each the implicit grant limit is scoped by top-level site. |
| TEST_F(StorageAccessGrantPermissionContextAPIWithImplicitGrantsTest, |
| ImplicitGrantLimitSiteScoping) { |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 0); |
| |
| ExhaustImplicitGrants(GetRequesterURL()); |
| |
| content::WebContentsTester::For(web_contents()) |
| ->NavigateAndCommit(GetDummyEmbeddingUrlWithSubdomain()); |
| |
| int implicit_grant_limit = |
| StorageAccessGrantPermissionContext::GetImplicitGrantLimitForTesting(); |
| |
| // Although the grants are exhausted, another request from a top-level origin |
| // that is same site with an existing grant should still be auto-granted. The |
| // call is to `RequestPermission`, which checks for existing grants, while |
| // `DecidePermission` does not. |
| // We should have no prompts still and our latest result should be an allow. |
| EXPECT_EQ(CONTENT_SETTING_ALLOW, RequestPermissionSync()); |
| EXPECT_FALSE(request_manager()->IsRequestInProgress()); |
| EXPECT_EQ(histogram_tester().GetBucketCount( |
| kRequestOutcomeHistogram, RequestOutcome::kGrantedByAllowance), |
| implicit_grant_limit); |
| |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, |
| implicit_grant_limit); |
| histogram_tester().ExpectBucketCount(kGrantIsImplicitHistogram, |
| /*sample=*/true, implicit_grant_limit); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| } |
| |
| TEST_F(StorageAccessGrantPermissionContextTest, ExplicitGrantDenial) { |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 0); |
| histogram_tester().ExpectTotalCount(kPromptResultHistogram, 0); |
| |
| auto future = DecidePermission(/*user_gesture=*/true); |
| WaitUntilPrompt(); |
| |
| // Deny the prompt and validate we get the expected setting back in our |
| // callback. |
| request_manager()->Deny(); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, future->Get()); |
| |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 0); |
| histogram_tester().ExpectUniqueSample( |
| kPromptResultHistogram, /*sample=*/permissions::PermissionAction::DENIED, |
| 1); |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, /*sample=*/RequestOutcome::kDeniedByUser, 1); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(GetRequesterSite(), false))); |
| } |
| |
| TEST_F(StorageAccessGrantPermissionContextTest, |
| ExplicitGrantDenialNotExposedViaQuery) { |
| // Set the content setting to blocked, mimicking a prompt rejection by the |
| // user. |
| HostContentSettingsMap* settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| settings_map->SetContentSettingDefaultScope( |
| GetRequesterURL(), GetTopLevelURL(), ContentSettingsType::STORAGE_ACCESS, |
| CONTENT_SETTING_BLOCK); |
| |
| prompt_factory().set_response_type( |
| permissions::PermissionRequestManager::AutoResponseType::NONE); |
| |
| auto future = DecidePermission(/*user_gesture=*/true); |
| // Ensure the prompt is not shown. |
| ASSERT_FALSE(request_manager()->IsRequestInProgress()); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, future->Get()); |
| |
| // However, ensure that the user's denial is not exposed when querying the |
| // permission, per the spec. |
| EXPECT_EQ(PermissionStatus::ASK, |
| permission_context() |
| ->GetPermissionStatus(/*render_frame_host=*/nullptr, |
| GetRequesterURL(), GetTopLevelURL()) |
| .status); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(GetRequesterSite(), false))); |
| } |
| |
| TEST_F(StorageAccessGrantPermissionContextTest, ExplicitGrantAccept) { |
| histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 0); |
| histogram_tester().ExpectTotalCount(kPromptResultHistogram, 0); |
| |
| auto future = DecidePermission(/*user_gesture=*/true); |
| WaitUntilPrompt(); |
| |
| // Accept the prompt and validate we get the expected setting back in our |
| // callback. |
| request_manager()->Accept(); |
| EXPECT_EQ(CONTENT_SETTING_ALLOW, future->Get()); |
| |
| histogram_tester().ExpectUniqueSample(kGrantIsImplicitHistogram, |
| /*sample=*/false, 1); |
| histogram_tester().ExpectUniqueSample( |
| kPromptResultHistogram, permissions::PermissionAction::GRANTED, 1); |
| histogram_tester().ExpectUniqueSample(kRequestOutcomeHistogram, |
| RequestOutcome::kGrantedByUser, 1); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| UnorderedElementsAre(Pair(GetRequesterSite(), true))); |
| } |
| |
| class StorageAccessGrantPermissionContextAPIWithFirstPartySetsTest |
| : public StorageAccessGrantPermissionContextTest { |
| public: |
| StorageAccessGrantPermissionContextAPIWithFirstPartySetsTest() = default; |
| |
| void SetUp() override { |
| StorageAccessGrantPermissionContextTest::SetUp(); |
| |
| // Create a FPS with https://requester.example.com as the member and |
| // https://embedder.com as the primary. |
| first_party_sets_handler_.SetGlobalSets(net::GlobalFirstPartySets( |
| base::Version("1.2.3"), |
| /*entries=*/ |
| {{net::SchemefulSite(GetTopLevelURL()), |
| {net::FirstPartySetEntry(net::SchemefulSite(GetTopLevelURL()), |
| net::SiteType::kPrimary, std::nullopt)}}, |
| {net::SchemefulSite(GetRequesterURL()), |
| {net::FirstPartySetEntry(net::SchemefulSite(GetTopLevelURL()), |
| net::SiteType::kAssociated, 0)}}}, |
| /*aliases=*/{})); |
| } |
| |
| private: |
| first_party_sets::ScopedMockFirstPartySetsHandler first_party_sets_handler_; |
| }; |
| |
| TEST_F(StorageAccessGrantPermissionContextAPIWithFirstPartySetsTest, |
| ImplicitGrant_AutograntedWithinFPS) { |
| base::Time expiration_lower_bound_check = base::Time::Now(); |
| |
| HostContentSettingsMap* settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| DCHECK(settings_map); |
| |
| // Check no `SessionModel::NON_RESTORABLE_USER_SESSION` setting exists yet. |
| ContentSettingsForOneType non_restorable_grants = |
| settings_map->GetSettingsForOneType( |
| ContentSettingsType::STORAGE_ACCESS, |
| content_settings::mojom::SessionModel::NON_RESTORABLE_USER_SESSION); |
| EXPECT_EQ(0u, non_restorable_grants.size()); |
| |
| EXPECT_EQ(DecidePermissionSync(/*user_gesture=*/true), CONTENT_SETTING_ALLOW); |
| |
| histogram_tester().ExpectUniqueSample( |
| kRequestOutcomeHistogram, RequestOutcome::kGrantedByFirstPartySet, 1); |
| histogram_tester().ExpectUniqueSample(kGrantIsImplicitHistogram, |
| /*sample=*/true, 1); |
| |
| DCHECK(settings_map); |
| // Check the `SessionModel::NON_RESTORABLE_USER_SESSION` settings granted by |
| // FPS. |
| non_restorable_grants = settings_map->GetSettingsForOneType( |
| ContentSettingsType::STORAGE_ACCESS, |
| content_settings::mojom::SessionModel::NON_RESTORABLE_USER_SESSION); |
| EXPECT_EQ(1u, non_restorable_grants.size()); |
| |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| |
| auto setting = non_restorable_grants[0]; |
| |
| EXPECT_NE(true, setting.IsExpired()); |
| // Check to ensure the expiration time is in expected range. |
| EXPECT_GT(setting.metadata.expiration(), |
| permissions::kStorageAccessAPIRelatedWebsiteSetsLifetime + |
| expiration_lower_bound_check); |
| EXPECT_LT(setting.metadata.expiration(), |
| permissions::kStorageAccessAPIRelatedWebsiteSetsLifetime + |
| base::Time::Now()); |
| } |
| |
| class StorageAccessGrantPermissionContextAPIWithFedCMConnectionTest |
| : public StorageAccessGrantPermissionContextTest { |
| public: |
| StorageAccessGrantPermissionContextAPIWithFedCMConnectionTest() = default; |
| |
| void SetUp() override { |
| StorageAccessGrantPermissionContextTest::SetUp(); |
| |
| feature_list_.InitAndEnableFeature( |
| blink::features::kFedCmWithStorageAccessAPI); |
| |
| FederatedIdentityPermissionContextFactory::GetForProfile(profile()) |
| ->GrantSharingPermission( |
| /*relying_party_requester=*/url::Origin::Create( |
| GURL("https://unrelated-site.test")), |
| /*relying_party_embedder=*/ |
| url::Origin::Create(GetTopLevelURL()), |
| /*identity_provider=*/url::Origin::Create(GetRequesterURL()), |
| "my_account"); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_F(StorageAccessGrantPermissionContextAPIWithFedCMConnectionTest, |
| AutoResolveWithConnection) { |
| prompt_factory().set_response_type( |
| permissions::PermissionRequestManager::AutoResponseType::NONE); |
| |
| auto future = DecidePermission(/*user_gesture=*/false); |
| // Ensure no prompt is shown. |
| ASSERT_FALSE(request_manager()->IsRequestInProgress()); |
| EXPECT_EQ(CONTENT_SETTING_ALLOW, future->Get()); |
| |
| histogram_tester().ExpectUniqueSample(kRequestOutcomeHistogram, |
| RequestOutcome::kAllowedByFedCM, 1); |
| |
| EXPECT_THAT(HostContentSettingsMapFactory::GetForProfile(profile()) |
| ->GetSettingsForOneType( |
| ContentSettingsType::STORAGE_ACCESS, |
| content_settings::mojom::SessionModel::DURABLE), |
| ElementsAre(ContentSettingPatternSource( |
| ContentSettingsPattern::Wildcard(), |
| ContentSettingsPattern::Wildcard(), |
| content_settings::ContentSettingToValue( |
| ContentSetting::CONTENT_SETTING_ASK), |
| /*source=*/"default", /*incognito=*/false))); |
| EXPECT_THAT(page_specific_content_settings()->GetTwoSiteRequests( |
| ContentSettingsType::STORAGE_ACCESS), |
| IsEmpty()); |
| } |