| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/permissions/permission_controller_impl.h" |
| |
| #include <cstdlib> |
| #include <memory> |
| #include <optional> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/test/mock_callback.h" |
| #include "content/public/browser/permission_controller.h" |
| #include "content/public/browser/permission_controller_delegate.h" |
| #include "content/public/browser/permission_descriptor_util.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/mock_permission_manager.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "services/network/public/cpp/permissions_policy/origin_with_possible_wildcards.h" |
| #include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h" |
| #include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/permissions/permission_utils.h" |
| #include "url/origin.h" |
| |
| using blink::PermissionType; |
| |
| namespace content { |
| |
| namespace { |
| using ::testing::Unused; |
| using OverrideStatus = PermissionControllerImpl::OverrideStatus; |
| using RequestsCallback = |
| base::OnceCallback<void(const std::vector<PermissionStatus>&)>; |
| |
| constexpr char kTestUrl[] = "https://google.com"; |
| |
| class MockManagerWithRequests : public MockPermissionManager { |
| public: |
| MockManagerWithRequests() {} |
| |
| MockManagerWithRequests(const MockManagerWithRequests&) = delete; |
| MockManagerWithRequests& operator=(const MockManagerWithRequests&) = delete; |
| |
| ~MockManagerWithRequests() override {} |
| MOCK_METHOD( |
| void, |
| RequestPermissionsFromCurrentDocument, |
| (RenderFrameHost * render_frame_host, |
| const PermissionRequestDescription& request_description, |
| const base::OnceCallback<void(const std::vector<PermissionStatus>&)> |
| callback), |
| (override)); |
| MOCK_METHOD( |
| void, |
| RequestPermissions, |
| (RenderFrameHost * render_frame_host, |
| const PermissionRequestDescription& request_description, |
| const base::OnceCallback<void(const std::vector<PermissionStatus>&)> |
| callback), |
| (override)); |
| MOCK_METHOD(bool, |
| IsPermissionOverridable, |
| (PermissionType, |
| base::optional_ref<const url::Origin>, |
| base::optional_ref<const url::Origin>), |
| (override)); |
| }; |
| |
| class TestPermissionManager : public MockPermissionManager { |
| public: |
| TestPermissionManager() = default; |
| ~TestPermissionManager() override = default; |
| |
| PermissionStatus GetPermissionStatusForCurrentDocument( |
| const blink::mojom::PermissionDescriptorPtr& permission_descriptor, |
| RenderFrameHost* render_frame_host, |
| bool should_include_device_status) override { |
| RenderFrameHost* top_frame = render_frame_host->GetParentOrOuterDocument(); |
| GURL url; |
| |
| if (top_frame) { |
| url = top_frame->GetLastCommittedOrigin().GetURL(); |
| } else { |
| url = render_frame_host->GetLastCommittedOrigin().GetURL(); |
| } |
| |
| if (override_status_.contains(url)) { |
| return override_status_[url]; |
| } |
| |
| return PermissionStatus::ASK; |
| } |
| |
| void SetPermissionStatus(GURL url, PermissionStatus status) { |
| override_status_[url] = status; |
| } |
| |
| private: |
| std::map<GURL, PermissionStatus> override_status_; |
| }; |
| |
| // Results are defined based on assumption that same types are queried for |
| // each test case. |
| const struct { |
| std::map<PermissionType, PermissionStatus> overrides; |
| |
| std::vector<PermissionType> delegated_permissions; |
| std::vector<PermissionStatus> delegated_statuses; |
| |
| std::vector<PermissionStatus> expected_results; |
| bool expect_death; |
| } kTestPermissionRequestCases[] = { |
| // No overrides present - all delegated. |
| {{}, |
| {PermissionType::GEOLOCATION, PermissionType::BACKGROUND_SYNC, |
| PermissionType::MIDI_SYSEX}, |
| {PermissionStatus::DENIED, PermissionStatus::GRANTED, |
| PermissionStatus::GRANTED}, |
| {PermissionStatus::DENIED, PermissionStatus::GRANTED, |
| PermissionStatus::GRANTED}, |
| /*expect_death=*/false}, |
| |
| // No delegates needed - all overridden. |
| {{{PermissionType::GEOLOCATION, PermissionStatus::GRANTED}, |
| {PermissionType::BACKGROUND_SYNC, PermissionStatus::GRANTED}, |
| {PermissionType::MIDI_SYSEX, PermissionStatus::ASK}}, |
| {}, |
| {}, |
| {PermissionStatus::GRANTED, PermissionStatus::GRANTED, |
| PermissionStatus::ASK}, |
| /*expect_death=*/false}, |
| |
| // Some overridden, some delegated. |
| {{{PermissionType::BACKGROUND_SYNC, PermissionStatus::DENIED}}, |
| {PermissionType::GEOLOCATION, PermissionType::MIDI_SYSEX}, |
| {PermissionStatus::GRANTED, PermissionStatus::ASK}, |
| {PermissionStatus::GRANTED, PermissionStatus::DENIED, |
| PermissionStatus::ASK}, |
| /*expect_death=*/false}, |
| |
| // Some overridden, some delegated. |
| {{{PermissionType::GEOLOCATION, PermissionStatus::GRANTED}, |
| {PermissionType::BACKGROUND_SYNC, PermissionStatus::DENIED}}, |
| {PermissionType::MIDI_SYSEX}, |
| {PermissionStatus::ASK}, |
| {PermissionStatus::GRANTED, PermissionStatus::DENIED, |
| PermissionStatus::ASK}, |
| /*expect_death=*/false}, |
| |
| // Too many delegates (causes death). |
| {{{PermissionType::GEOLOCATION, PermissionStatus::GRANTED}, |
| {PermissionType::MIDI_SYSEX, PermissionStatus::ASK}}, |
| {PermissionType::BACKGROUND_SYNC}, |
| {PermissionStatus::DENIED, PermissionStatus::GRANTED}, |
| // Results don't matter because will die. |
| {}, |
| /*expect_death=*/true}, |
| |
| // Too few delegates (causes death). |
| {{}, |
| {PermissionType::GEOLOCATION, PermissionType::BACKGROUND_SYNC, |
| PermissionType::MIDI_SYSEX}, |
| {PermissionStatus::GRANTED, PermissionStatus::GRANTED}, |
| // Results don't matter because will die. |
| {}, |
| /*expect_death=*/true}}; |
| |
| } // namespace |
| |
| class PermissionControllerImplTest : public ::testing::Test { |
| public: |
| PermissionControllerImplTest() { |
| browser_context_.SetPermissionControllerDelegate( |
| std::make_unique<::testing::NiceMock<MockManagerWithRequests>>()); |
| permission_controller_ = |
| std::make_unique<PermissionControllerImpl>(&browser_context_); |
| } |
| |
| PermissionControllerImplTest(const PermissionControllerImplTest&) = delete; |
| PermissionControllerImplTest& operator=(const PermissionControllerImplTest&) = |
| delete; |
| |
| ~PermissionControllerImplTest() override { |
| browser_context_.SetPermissionControllerDelegate(nullptr); |
| } |
| |
| void SetUp() override { |
| ON_CALL(*mock_manager(), IsPermissionOverridable) |
| .WillByDefault(testing::Return(true)); |
| } |
| |
| PermissionControllerImpl* permission_controller() { |
| return permission_controller_.get(); |
| } |
| |
| void PermissionControllerRequestPermissionsFromCurrentDocument( |
| RenderFrameHost* render_frame_host, |
| PermissionRequestDescription request_description, |
| base::OnceCallback<void(const std::vector<PermissionStatus>&)> callback) { |
| permission_controller()->RequestPermissionsFromCurrentDocument( |
| render_frame_host, std::move(request_description), std::move(callback)); |
| } |
| |
| void PermissionControllerRequestPermissions( |
| RenderFrameHost* render_frame_host, |
| PermissionRequestDescription request_description, |
| base::OnceCallback<void(const std::vector<PermissionStatus>&)> callback) { |
| permission_controller()->RequestPermissions( |
| render_frame_host, std::move(request_description), std::move(callback)); |
| } |
| |
| PermissionStatus GetPermissionStatusForWorker( |
| PermissionType permission, |
| RenderProcessHost* render_process_host, |
| const url::Origin& worker_origin) { |
| return permission_controller()->GetPermissionStatusForWorker( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionType(permission), |
| render_process_host, worker_origin); |
| } |
| |
| PermissionResult GetPermissionResultForOriginWithoutContext( |
| const blink::mojom::PermissionDescriptorPtr& permission, |
| const url::Origin& requesting_origin, |
| const url::Origin& embedding_origin) { |
| return permission_controller()->GetPermissionResultForOriginWithoutContext( |
| permission, requesting_origin, embedding_origin); |
| } |
| |
| BrowserContext* browser_context() { return &browser_context_; } |
| |
| MockManagerWithRequests* mock_manager() { |
| return static_cast<MockManagerWithRequests*>( |
| browser_context_.GetPermissionControllerDelegate()); |
| } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| TestBrowserContext browser_context_; |
| std::unique_ptr<PermissionControllerImpl> permission_controller_; |
| }; |
| |
| TEST_F(PermissionControllerImplTest, |
| RequestPermissionsFromCurrentDocumentDelegatesIffMissingOverrides) { |
| url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl)); |
| RenderViewHostTestEnabler enabler; |
| |
| const std::vector<PermissionType> kTypesToQuery = { |
| PermissionType::GEOLOCATION, PermissionType::BACKGROUND_SYNC, |
| PermissionType::MIDI_SYSEX}; |
| |
| std::unique_ptr<WebContents> web_contents( |
| WebContentsTester::CreateTestWebContents( |
| WebContents::CreateParams(browser_context()))); |
| |
| WebContentsTester* web_contents_tester = |
| WebContentsTester::For(web_contents.get()); |
| web_contents_tester->NavigateAndCommit(GURL(kTestUrl)); |
| |
| RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame(); |
| for (const auto& test_case : kTestPermissionRequestCases) { |
| // Need to reset overrides for each case to ensure delegation is as |
| // expected. |
| permission_controller()->ResetOverridesForDevTools(); |
| for (const auto& permission_status_pair : test_case.overrides) { |
| permission_controller()->SetOverrideForDevTools( |
| kTestOrigin, kTestOrigin, permission_status_pair.first, |
| permission_status_pair.second); |
| } |
| |
| // Expect request permission from current document calls if override are |
| // missing. |
| if (!test_case.delegated_permissions.empty()) { |
| auto forward_callbacks = testing::WithArg<2>( |
| [&test_case]( |
| base::OnceCallback<void(const std::vector<PermissionStatus>&)> |
| callback) { |
| std::move(callback).Run(test_case.delegated_statuses); |
| return 0; |
| }); |
| // Regular tests can set expectations. |
| if (test_case.expect_death) { |
| // Death tests cannot track these expectations but arguments should be |
| // forwarded to ensure death occurs. |
| ON_CALL(*mock_manager(), |
| RequestPermissionsFromCurrentDocument( |
| rfh, |
| PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionTypes( |
| test_case.delegated_permissions), |
| /*user_gesture*/ true, GURL(kTestUrl)), |
| testing::_)) |
| .WillByDefault(testing::Invoke(forward_callbacks)); |
| } else { |
| EXPECT_CALL(*mock_manager(), |
| RequestPermissionsFromCurrentDocument( |
| rfh, |
| PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionTypes( |
| test_case.delegated_permissions), |
| /*user_gesture*/ true, GURL(kTestUrl)), |
| testing::_)) |
| .WillOnce(testing::Invoke(forward_callbacks)); |
| } |
| } else { |
| // There should be no call to delegate if all overrides are defined. |
| EXPECT_CALL(*mock_manager(), RequestPermissionsFromCurrentDocument) |
| .Times(0); |
| } |
| |
| if (test_case.expect_death) { |
| GTEST_FLAG_SET(death_test_style, "threadsafe"); |
| base::MockCallback<RequestsCallback> callback; |
| EXPECT_DEATH_IF_SUPPORTED( |
| PermissionControllerRequestPermissionsFromCurrentDocument( |
| rfh, |
| PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionTypes( |
| kTypesToQuery), |
| /*user_gesture*/ true), |
| callback.Get()), |
| ""); |
| } else { |
| base::MockCallback<RequestsCallback> callback; |
| EXPECT_CALL(callback, |
| Run(testing::ElementsAreArray(test_case.expected_results))); |
| PermissionControllerRequestPermissionsFromCurrentDocument( |
| rfh, |
| PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionTypes(kTypesToQuery), |
| /*user_gesture*/ true), |
| callback.Get()); |
| } |
| } |
| } |
| |
| TEST_F(PermissionControllerImplTest, |
| RequestPermissionsDelegatesIffMissingOverrides) { |
| url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl)); |
| RenderViewHostTestEnabler enabler; |
| |
| const std::vector<PermissionType> kTypesToQuery = { |
| PermissionType::GEOLOCATION, PermissionType::BACKGROUND_SYNC, |
| PermissionType::MIDI_SYSEX}; |
| |
| std::unique_ptr<WebContents> web_contents( |
| WebContentsTester::CreateTestWebContents( |
| WebContents::CreateParams(browser_context()))); |
| |
| WebContentsTester* web_contents_tester = |
| WebContentsTester::For(web_contents.get()); |
| url::Origin testing_origin = url::Origin::Create(GURL(kTestUrl)); |
| web_contents_tester->NavigateAndCommit(testing_origin.GetURL()); |
| |
| RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame(); |
| for (const auto& test_case : kTestPermissionRequestCases) { |
| // Need to reset overrides for each case to ensure delegation is as |
| // expected. |
| permission_controller()->ResetOverridesForDevTools(); |
| for (const auto& permission_status_pair : test_case.overrides) { |
| permission_controller()->SetOverrideForDevTools( |
| kTestOrigin, kTestOrigin, permission_status_pair.first, |
| permission_status_pair.second); |
| } |
| |
| // Expect request permission call if override are missing. |
| if (!test_case.delegated_permissions.empty()) { |
| auto forward_callbacks = testing::WithArg<2>( |
| [&test_case]( |
| base::OnceCallback<void(const std::vector<PermissionStatus>&)> |
| callback) { |
| std::move(callback).Run(test_case.delegated_statuses); |
| return 0; |
| }); |
| // Regular tests can set expectations. |
| if (test_case.expect_death) { |
| // Death tests cannot track these expectations but arguments should be |
| // forwarded to ensure death occurs. |
| ON_CALL(*mock_manager(), |
| RequestPermissions( |
| rfh, |
| PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionTypes( |
| test_case.delegated_permissions), |
| /*user_gesture*/ true, GURL(kTestUrl)), |
| testing::_)) |
| .WillByDefault(testing::Invoke(forward_callbacks)); |
| } else { |
| EXPECT_CALL(*mock_manager(), |
| RequestPermissions( |
| rfh, |
| PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionTypes( |
| test_case.delegated_permissions), |
| /*user_gesture*/ true, GURL(kTestUrl)), |
| testing::_)) |
| .WillOnce(testing::Invoke(forward_callbacks)); |
| } |
| } else { |
| // There should be no call to delegate if all overrides are defined. |
| EXPECT_CALL(*mock_manager(), RequestPermissionsFromCurrentDocument) |
| .Times(0); |
| } |
| |
| if (test_case.expect_death) { |
| GTEST_FLAG_SET(death_test_style, "threadsafe"); |
| base::MockCallback<RequestsCallback> callback; |
| EXPECT_DEATH_IF_SUPPORTED( |
| PermissionControllerRequestPermissions( |
| rfh, |
| PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionTypes( |
| kTypesToQuery), |
| /*user_gesture*/ true, GURL(kTestUrl)), |
| callback.Get()), |
| ""); |
| } else { |
| base::MockCallback<RequestsCallback> callback; |
| EXPECT_CALL(callback, |
| Run(testing::ElementsAreArray(test_case.expected_results))); |
| PermissionControllerRequestPermissions( |
| rfh, |
| PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionTypes(kTypesToQuery), |
| /*user_gesture*/ true, GURL(kTestUrl)), |
| callback.Get()); |
| } |
| } |
| } |
| |
| TEST_F(PermissionControllerImplTest, |
| NotifyChangedSubscriptionsCallsOnChangeOnly) { |
| using PermissionStatusCallback = |
| base::RepeatingCallback<void(PermissionStatus)>; |
| GURL kUrl = GURL(kTestUrl); |
| url::Origin kTestOrigin = url::Origin::Create(kUrl); |
| |
| // Setup. |
| PermissionStatus sync_status = GetPermissionStatusForWorker( |
| PermissionType::BACKGROUND_SYNC, |
| /*render_process_host=*/nullptr, kTestOrigin); |
| permission_controller()->SetOverrideForDevTools(kTestOrigin, kTestOrigin, |
| PermissionType::GEOLOCATION, |
| PermissionStatus::DENIED); |
| |
| base::MockCallback<PermissionStatusCallback> geo_callback; |
| permission_controller()->SubscribeToPermissionStatusChange( |
| PermissionType::GEOLOCATION, nullptr, nullptr, kUrl, |
| /*should_include_device_status=*/false, geo_callback.Get()); |
| |
| base::MockCallback<PermissionStatusCallback> sync_callback; |
| permission_controller()->SubscribeToPermissionStatusChange( |
| PermissionType::BACKGROUND_SYNC, nullptr, nullptr, kUrl, |
| /*should_include_device_status=*/false, sync_callback.Get()); |
| |
| // Geolocation should change status, so subscriber is updated. |
| EXPECT_CALL(geo_callback, Run(PermissionStatus::ASK)); |
| EXPECT_CALL(sync_callback, Run).Times(0); |
| permission_controller()->SetOverrideForDevTools(kTestOrigin, kTestOrigin, |
| PermissionType::GEOLOCATION, |
| PermissionStatus::ASK); |
| |
| // Callbacks should not be called again because permission status has not |
| // changed. |
| permission_controller()->SetOverrideForDevTools( |
| kTestOrigin, kTestOrigin, PermissionType::BACKGROUND_SYNC, sync_status); |
| permission_controller()->SetOverrideForDevTools(kTestOrigin, kTestOrigin, |
| PermissionType::GEOLOCATION, |
| PermissionStatus::ASK); |
| } |
| |
| TEST_F(PermissionControllerImplTest, |
| PermissionsCannotBeOverriddenIfNotOverridable) { |
| url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl)); |
| EXPECT_EQ(OverrideStatus::kOverrideSet, |
| permission_controller()->SetOverrideForDevTools( |
| kTestOrigin, kTestOrigin, PermissionType::GEOLOCATION, |
| PermissionStatus::DENIED)); |
| |
| // Delegate will be called, but prevents override from being set. |
| EXPECT_CALL(*mock_manager(), |
| IsPermissionOverridable(PermissionType::GEOLOCATION, testing::_, |
| testing::_)) |
| .WillOnce(testing::Return(false)); |
| EXPECT_EQ(OverrideStatus::kOverrideNotSet, |
| permission_controller()->SetOverrideForDevTools( |
| kTestOrigin, kTestOrigin, PermissionType::GEOLOCATION, |
| PermissionStatus::ASK)); |
| |
| PermissionStatus status = GetPermissionStatusForWorker( |
| PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, |
| kTestOrigin); |
| EXPECT_EQ(PermissionStatus::DENIED, status); |
| } |
| |
| TEST_F(PermissionControllerImplTest, |
| GrantPermissionsReturnsStatusesBeingSetIfOverridable) { |
| GURL kUrl(kTestUrl); |
| url::Origin kTestOrigin = url::Origin::Create(kUrl); |
| permission_controller()->SetOverrideForDevTools(kTestOrigin, kTestOrigin, |
| PermissionType::GEOLOCATION, |
| PermissionStatus::DENIED); |
| permission_controller()->SetOverrideForDevTools( |
| kTestOrigin, kTestOrigin, PermissionType::MIDI, PermissionStatus::ASK); |
| permission_controller()->SetOverrideForDevTools( |
| kTestOrigin, kTestOrigin, PermissionType::BACKGROUND_SYNC, |
| PermissionStatus::ASK); |
| // Delegate will be called, but prevents override from being set. |
| EXPECT_CALL(*mock_manager(), |
| IsPermissionOverridable(PermissionType::GEOLOCATION, testing::_, |
| testing::_)) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(*mock_manager(), IsPermissionOverridable(PermissionType::MIDI, |
| testing::_, testing::_)) |
| .WillOnce(testing::Return(true)); |
| |
| // Since one cannot be overridden, none are overridden. |
| auto result = permission_controller()->GrantOverridesForDevTools( |
| kTestOrigin, kTestOrigin, |
| {PermissionType::MIDI, PermissionType::GEOLOCATION, |
| PermissionType::BACKGROUND_SYNC}); |
| EXPECT_EQ(OverrideStatus::kOverrideNotSet, result); |
| |
| // Keep original settings as before. |
| EXPECT_EQ(PermissionStatus::DENIED, |
| GetPermissionStatusForWorker(PermissionType::GEOLOCATION, |
| /*render_process_host=*/nullptr, |
| kTestOrigin)); |
| EXPECT_EQ( |
| PermissionStatus::ASK, |
| GetPermissionStatusForWorker( |
| PermissionType::MIDI, /*render_process_host=*/nullptr, kTestOrigin)); |
| EXPECT_EQ(PermissionStatus::ASK, |
| GetPermissionStatusForWorker(PermissionType::BACKGROUND_SYNC, |
| /*render_process_host=*/nullptr, |
| kTestOrigin)); |
| |
| EXPECT_CALL(*mock_manager(), |
| IsPermissionOverridable(PermissionType::GEOLOCATION, testing::_, |
| testing::_)) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*mock_manager(), IsPermissionOverridable(PermissionType::MIDI, |
| testing::_, testing::_)) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*mock_manager(), |
| IsPermissionOverridable(PermissionType::BACKGROUND_SYNC, |
| testing::_, testing::_)) |
| .WillOnce(testing::Return(true)); |
| // If all can be set, overrides will be stored. |
| result = permission_controller()->GrantOverridesForDevTools( |
| kTestOrigin, kTestOrigin, |
| {PermissionType::MIDI, PermissionType::GEOLOCATION, |
| PermissionType::BACKGROUND_SYNC}); |
| EXPECT_EQ(OverrideStatus::kOverrideSet, result); |
| EXPECT_EQ(PermissionStatus::GRANTED, |
| GetPermissionStatusForWorker(PermissionType::GEOLOCATION, |
| /*render_process_host=*/nullptr, |
| kTestOrigin)); |
| EXPECT_EQ( |
| PermissionStatus::GRANTED, |
| GetPermissionStatusForWorker( |
| PermissionType::MIDI, /*render_process_host=*/nullptr, kTestOrigin)); |
| EXPECT_EQ(PermissionStatus::GRANTED, |
| GetPermissionStatusForWorker(PermissionType::BACKGROUND_SYNC, |
| /*render_process_host=*/nullptr, |
| kTestOrigin)); |
| } |
| |
| TEST_F(PermissionControllerImplTest, SetOverrideEmbeddingOriginMatters) { |
| url::Origin requesting_origin = |
| url::Origin::Create(GURL("https://requester.com/")); |
| url::Origin embedding_origin_1 = |
| url::Origin::Create(GURL("https://embedder1.com/")); |
| url::Origin embedding_origin_2 = |
| url::Origin::Create(GURL("https://embedder2.com/")); |
| |
| // Create distinct overrides because embedding origin matters for |
| // STORAGE_ACCESS_GRANT. |
| permission_controller()->SetOverrideForDevTools( |
| requesting_origin, embedding_origin_1, |
| PermissionType::STORAGE_ACCESS_GRANT, PermissionStatus::GRANTED); |
| permission_controller()->SetOverrideForDevTools( |
| requesting_origin, embedding_origin_2, |
| PermissionType::STORAGE_ACCESS_GRANT, PermissionStatus::DENIED); |
| |
| const blink::mojom::PermissionDescriptorPtr |
| storage_access_permission_descriptor = content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionType( |
| PermissionType::STORAGE_ACCESS_GRANT); |
| |
| EXPECT_EQ(GetPermissionResultForOriginWithoutContext( |
| storage_access_permission_descriptor, requesting_origin, |
| embedding_origin_1) |
| .status, |
| PermissionStatus::GRANTED); |
| EXPECT_EQ(GetPermissionResultForOriginWithoutContext( |
| storage_access_permission_descriptor, requesting_origin, |
| embedding_origin_2) |
| .status, |
| PermissionStatus::DENIED); |
| |
| // Pairs without overrides should return ASK. |
| url::Origin no_overrides_origin = |
| url::Origin::Create(GURL("https://example.com")); |
| EXPECT_EQ(GetPermissionResultForOriginWithoutContext( |
| storage_access_permission_descriptor, no_overrides_origin, |
| embedding_origin_1) |
| .status, |
| PermissionStatus::ASK); |
| EXPECT_EQ(GetPermissionResultForOriginWithoutContext( |
| storage_access_permission_descriptor, requesting_origin, |
| no_overrides_origin) |
| .status, |
| PermissionStatus::ASK); |
| } |
| |
| TEST_F(PermissionControllerImplTest, SetOverrideCrashesOnSingleOrigin) { |
| url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl)); |
| |
| // Setting overrides should crash if only one origin is provided. |
| EXPECT_DEATH_IF_SUPPORTED( |
| permission_controller()->SetOverrideForDevTools( |
| kTestOrigin, std::nullopt, PermissionType::GEOLOCATION, |
| PermissionStatus::GRANTED), |
| ""); |
| EXPECT_DEATH_IF_SUPPORTED( |
| permission_controller()->SetOverrideForDevTools( |
| std::nullopt, kTestOrigin, PermissionType::GEOLOCATION, |
| PermissionStatus::GRANTED), |
| ""); |
| } |
| |
| TEST_F(PermissionControllerImplTest, GrantOverridesCrashesOnSingleOrigin) { |
| url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl)); |
| |
| // Granting overrides should crash if only one origin is provided. |
| EXPECT_DEATH_IF_SUPPORTED( |
| permission_controller()->GrantOverridesForDevTools( |
| kTestOrigin, std::nullopt, {PermissionType::GEOLOCATION}), |
| ""); |
| EXPECT_DEATH_IF_SUPPORTED( |
| permission_controller()->GrantOverridesForDevTools( |
| std::nullopt, kTestOrigin, {PermissionType::GEOLOCATION}), |
| ""); |
| } |
| |
| class PermissionControllerImplWithDelegateTest |
| : public content::RenderViewHostTestHarness { |
| public: |
| std::unique_ptr<BrowserContext> CreateBrowserContext() override { |
| std::unique_ptr<TestBrowserContext> browser_context = |
| std::make_unique<TestBrowserContext>(); |
| |
| std::unique_ptr<TestPermissionManager> permission_manager = |
| std::make_unique<TestPermissionManager>(); |
| permission_manager_ = permission_manager.get(); |
| browser_context->SetPermissionControllerDelegate( |
| std::move(permission_manager)); |
| |
| return browser_context; |
| } |
| |
| void TearDown() override { |
| permission_manager_ = nullptr; |
| RenderViewHostTestHarness::TearDown(); |
| } |
| |
| content::RenderFrameHost* AddChildRFH( |
| content::RenderFrameHost* parent, |
| const GURL& origin, |
| network::mojom::PermissionsPolicyFeature feature = |
| network::mojom::PermissionsPolicyFeature::kNotFound) { |
| network::ParsedPermissionsPolicy frame_policy = {}; |
| if (feature != network::mojom::PermissionsPolicyFeature::kNotFound) { |
| frame_policy.emplace_back( |
| feature, |
| std::vector{*network::OriginWithPossibleWildcards::FromOrigin( |
| url::Origin::Create(origin))}, |
| /*self_if_matches=*/std::nullopt, |
| /*matches_all_origins=*/false, |
| /*matches_opaque_src=*/false); |
| } |
| content::RenderFrameHost* result = |
| content::RenderFrameHostTester::For(parent)->AppendChildWithPolicy( |
| "", frame_policy); |
| content::RenderFrameHostTester::For(result) |
| ->InitializeRenderFrameIfNeeded(); |
| SimulateNavigation(&result, origin); |
| return result; |
| } |
| |
| void SimulateNavigation(content::RenderFrameHost** rfh, const GURL& url) { |
| auto navigation_simulator = |
| content::NavigationSimulator::CreateRendererInitiated(url, *rfh); |
| navigation_simulator->Commit(); |
| *rfh = navigation_simulator->GetFinalRenderFrameHost(); |
| } |
| |
| TestPermissionManager* permission_manager() const { |
| return permission_manager_; |
| } |
| |
| private: |
| raw_ptr<TestPermissionManager> permission_manager_; |
| }; |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| TEST_F(PermissionControllerImplWithDelegateTest, PermissionPolicyTest) { |
| const char* kOrigin1 = "https://example.com"; |
| const char* kOrigin2 = "https://example-child.com"; |
| |
| NavigateAndCommit(GURL(kOrigin1)); |
| PermissionController* permission_controller = |
| GetBrowserContext()->GetPermissionController(); |
| RenderFrameHost* parent = main_rfh(); |
| |
| ASSERT_TRUE(parent); |
| |
| const auto geolocation_permission_descriptor = content:: |
| PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType( |
| PermissionType::GEOLOCATION); |
| |
| EXPECT_EQ(PermissionStatus::ASK, |
| permission_controller->GetPermissionStatusForCurrentDocument( |
| geolocation_permission_descriptor, parent)); |
| |
| content::RenderFrameHost* child_without_policy = |
| AddChildRFH(parent, GURL(kOrigin2)); |
| ASSERT_TRUE(child_without_policy); |
| |
| // A cross-origin iframe without a permission policy has no access to a |
| // permission-gated functionality. |
| EXPECT_EQ(PermissionStatus::DENIED, |
| permission_controller->GetPermissionStatusForCurrentDocument( |
| geolocation_permission_descriptor, child_without_policy)); |
| |
| content::RenderFrameHost* child_with_policy = |
| AddChildRFH(parent, GURL(kOrigin2), |
| network::mojom::PermissionsPolicyFeature::kGeolocation); |
| ASSERT_TRUE(child_with_policy); |
| |
| // The top-level frame has no permission, hence a cross-origin iframe has no |
| // permission as well. |
| EXPECT_EQ(PermissionStatus::DENIED, |
| permission_controller->GetPermissionStatusForCurrentDocument( |
| geolocation_permission_descriptor, child_without_policy)); |
| |
| permission_manager()->SetPermissionStatus(GURL(kOrigin1), |
| PermissionStatus::GRANTED); |
| |
| // The top-level frame has granted permission. |
| EXPECT_EQ(PermissionStatus::GRANTED, |
| permission_controller->GetPermissionStatusForCurrentDocument( |
| geolocation_permission_descriptor, parent)); |
| |
| // A cross-origin iframe with a permission policy has full access to a |
| // permission-gated functionality as long as the top-level frame has |
| // permission. |
| EXPECT_EQ(PermissionStatus::GRANTED, |
| permission_controller->GetPermissionStatusForCurrentDocument( |
| geolocation_permission_descriptor, child_with_policy)); |
| |
| // The frame without a permission policy still has no access. |
| EXPECT_EQ(PermissionStatus::DENIED, |
| permission_controller->GetPermissionStatusForCurrentDocument( |
| geolocation_permission_descriptor, child_without_policy)); |
| } |
| #endif |
| |
| } // namespace content |