| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/functional/bind.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/notifications/notification_permission_context.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/permissions/contexts/geolocation_permission_context.h" |
| #include "components/permissions/contexts/midi_permission_context.h" |
| #include "components/permissions/permission_decision.h" |
| #include "components/permissions/permission_manager.h" |
| #include "components/permissions/permission_request_id.h" |
| #include "components/permissions/permission_util.h" |
| #include "components/permissions/resolvers/content_setting_permission_resolver.h" |
| #include "content/public/browser/permission_descriptor_util.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_renderer_host.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.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/geolocation/geolocation_permission_context_delegate_android.h" |
| #else |
| #include "chrome/browser/geolocation/geolocation_permission_context_delegate.h" |
| #endif |
| |
| // Integration tests for querying permissions that have a permissions policy |
| // set. These tests are not meant to cover every edge case as the |
| // PermissionsPolicy class itself is tested thoroughly in |
| // permissions_policy_unittest.cc and in |
| // render_frame_host_permissions_policy_unittest.cc. Instead they are meant to |
| // ensure that integration with content::PermissionContextBase |
| // works correctly. |
| class PermissionContextBasePermissionsPolicyTest |
| : public ChromeRenderViewHostTestHarness { |
| public: |
| void EnableBlockMidiByDefault() { |
| feature_list_.InitAndEnableFeature(blink::features::kBlockMidiByDefault); |
| } |
| PermissionContextBasePermissionsPolicyTest() = default; |
| |
| void SetUp() override { ChromeRenderViewHostTestHarness::SetUp(); } |
| |
| protected: |
| static constexpr const char* kOrigin1 = "https://google.com"; |
| static constexpr const char* kOrigin2 = "https://maps.google.com"; |
| |
| content::RenderFrameHost* GetMainRFH(const char* origin) { |
| content::RenderFrameHost* result = web_contents()->GetPrimaryMainFrame(); |
| content::RenderFrameHostTester::For(result) |
| ->InitializeRenderFrameIfNeeded(); |
| SimulateNavigation(&result, GURL(origin)); |
| return result; |
| } |
| |
| content::RenderFrameHost* AddChildRFH( |
| content::RenderFrameHost* parent, |
| const char* 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(GURL(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, GURL(origin)); |
| return result; |
| } |
| |
| // The header policy should only be set once on page load, so we refresh the |
| // page to simulate that. |
| void RefreshPageAndSetHeaderPolicy( |
| content::RenderFrameHost** rfh, |
| network::mojom::PermissionsPolicyFeature feature, |
| const std::vector<std::string>& origins) { |
| content::RenderFrameHost* current = *rfh; |
| auto navigation = content::NavigationSimulator::CreateRendererInitiated( |
| current->GetLastCommittedURL(), current); |
| std::vector<network::OriginWithPossibleWildcards> parsed_origins; |
| for (const std::string& origin : origins) { |
| parsed_origins.emplace_back( |
| *network::OriginWithPossibleWildcards::FromOrigin( |
| url::Origin::Create(GURL(origin)))); |
| } |
| navigation->SetPermissionsPolicyHeader( |
| {{feature, parsed_origins, /*self_if_matches=*/std::nullopt, |
| /*matches_all_origins=*/false, |
| /*matches_opaque_src=*/false}}); |
| navigation->Commit(); |
| *rfh = navigation->GetFinalRenderFrameHost(); |
| } |
| |
| ContentSetting GetPermissionForFrame(permissions::PermissionContextBase* pcb, |
| content::RenderFrameHost* rfh) { |
| return permissions::PermissionUtil::PermissionStatusToContentSetting( |
| pcb->GetPermissionStatus( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionType( |
| permissions::PermissionUtil:: |
| ContentSettingsTypeToPermissionType( |
| pcb->content_settings_type())), |
| rfh, rfh->GetLastCommittedURL(), |
| web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL()) |
| .status); |
| } |
| |
| PermissionStatus RequestPermissionForFrame( |
| permissions::PermissionContextBase* pcb, |
| content::RenderFrameHost* rfh) { |
| permissions::PermissionRequestID id( |
| rfh, permission_request_id_generator_.GenerateNextId()); |
| pcb->RequestPermission( |
| std::make_unique<permissions::PermissionRequestData>( |
| std::make_unique<permissions::ContentSettingPermissionResolver>( |
| pcb->content_settings_type()), |
| id, /*user_gesture=*/true, rfh->GetLastCommittedURL()), |
| base::BindOnce(&PermissionContextBasePermissionsPolicyTest:: |
| RequestPermissionForFrameFinished, |
| base::Unretained(this))); |
| EXPECT_NE(PermissionStatus::ASK, last_request_result_); |
| PermissionStatus result = last_request_result_; |
| last_request_result_ = PermissionStatus::ASK; |
| return result; |
| } |
| |
| std::unique_ptr<permissions::GeolocationPermissionContext> |
| MakeGeolocationPermissionContext() { |
| return std::make_unique<permissions::GeolocationPermissionContext>( |
| profile(), |
| #if BUILDFLAG(IS_ANDROID) |
| std::make_unique<GeolocationPermissionContextDelegateAndroid>(profile()) |
| #else |
| std::make_unique<GeolocationPermissionContextDelegate>(profile()) |
| #endif |
| ); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| void RequestPermissionForFrameFinished(PermissionStatus status) { |
| last_request_result_ = status; |
| } |
| |
| void SimulateNavigation(content::RenderFrameHost** rfh, const GURL& url) { |
| auto navigation_simulator = |
| content::NavigationSimulator::CreateRendererInitiated(url, *rfh); |
| navigation_simulator->Commit(); |
| *rfh = navigation_simulator->GetFinalRenderFrameHost(); |
| } |
| |
| PermissionStatus last_request_result_ = PermissionStatus::ASK; |
| permissions::PermissionRequestID::RequestLocalId::Generator |
| permission_request_id_generator_; |
| }; |
| |
| TEST_F(PermissionContextBasePermissionsPolicyTest, DefaultPolicy) { |
| content::RenderFrameHost* parent = GetMainRFH(kOrigin1); |
| content::RenderFrameHost* child = AddChildRFH(parent, kOrigin2); |
| |
| // Midi is ask by default in the top level frame but not in subframes. |
| permissions::MidiPermissionContext midi(profile()); |
| EXPECT_EQ(CONTENT_SETTING_ASK, GetPermissionForFrame(&midi, parent)); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, GetPermissionForFrame(&midi, child)); |
| |
| // Geolocation is ask by default in top level frames but not in subframes. |
| auto geolocation = MakeGeolocationPermissionContext(); |
| EXPECT_EQ(CONTENT_SETTING_ASK, |
| GetPermissionForFrame(geolocation.get(), parent)); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, |
| GetPermissionForFrame(geolocation.get(), child)); |
| |
| // Notifications is ask by default in top level frames but not in subframes. |
| NotificationPermissionContext notifications(profile()); |
| EXPECT_EQ(CONTENT_SETTING_ASK, GetPermissionForFrame(¬ifications, parent)); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, |
| GetPermissionForFrame(¬ifications, child)); |
| } |
| |
| TEST_F(PermissionContextBasePermissionsPolicyTest, |
| DefaultPolicyBlockMidiByDefault) { |
| EnableBlockMidiByDefault(); |
| |
| content::RenderFrameHost* parent = GetMainRFH(kOrigin1); |
| content::RenderFrameHost* child = AddChildRFH(parent, kOrigin2); |
| |
| // Midi is disallowed by default in the top level frame and blocked in |
| // subframes. |
| permissions::MidiPermissionContext midi(profile()); |
| EXPECT_EQ(CONTENT_SETTING_ASK, GetPermissionForFrame(&midi, parent)); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, GetPermissionForFrame(&midi, child)); |
| } |
| |
| TEST_F(PermissionContextBasePermissionsPolicyTest, DisabledTopLevelFrame) { |
| content::RenderFrameHost* parent = GetMainRFH(kOrigin1); |
| |
| // Disable midi in the top level frame. |
| RefreshPageAndSetHeaderPolicy( |
| &parent, network::mojom::PermissionsPolicyFeature::kMidiFeature, |
| std::vector<std::string>()); |
| content::RenderFrameHost* child = AddChildRFH(parent, kOrigin2); |
| permissions::MidiPermissionContext midi(profile()); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, GetPermissionForFrame(&midi, parent)); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, GetPermissionForFrame(&midi, child)); |
| |
| // Disable geolocation in the top level frame. |
| RefreshPageAndSetHeaderPolicy( |
| &parent, network::mojom::PermissionsPolicyFeature::kGeolocation, |
| std::vector<std::string>()); |
| child = AddChildRFH(parent, kOrigin2); |
| auto geolocation = MakeGeolocationPermissionContext(); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, |
| GetPermissionForFrame(geolocation.get(), parent)); |
| EXPECT_EQ(CONTENT_SETTING_BLOCK, |
| GetPermissionForFrame(geolocation.get(), child)); |
| } |
| |
| TEST_F(PermissionContextBasePermissionsPolicyTest, EnabledForChildFrame) { |
| content::RenderFrameHost* parent = GetMainRFH(kOrigin1); |
| |
| // Enable midi for the child frame. |
| content::RenderFrameHost* child = AddChildRFH( |
| parent, kOrigin2, network::mojom::PermissionsPolicyFeature::kMidiFeature); |
| permissions::MidiPermissionContext midi(profile()); |
| EXPECT_EQ(CONTENT_SETTING_ASK, GetPermissionForFrame(&midi, parent)); |
| EXPECT_EQ(CONTENT_SETTING_ASK, GetPermissionForFrame(&midi, child)); |
| |
| // Enable geolocation for the child frame. |
| child = AddChildRFH(parent, kOrigin2, |
| network::mojom::PermissionsPolicyFeature::kGeolocation); |
| auto geolocation = MakeGeolocationPermissionContext(); |
| EXPECT_EQ(CONTENT_SETTING_ASK, |
| GetPermissionForFrame(geolocation.get(), parent)); |
| EXPECT_EQ(CONTENT_SETTING_ASK, |
| GetPermissionForFrame(geolocation.get(), child)); |
| } |
| |
| TEST_F(PermissionContextBasePermissionsPolicyTest, |
| EnabledForChildFrameBlockMidiByDefault) { |
| EnableBlockMidiByDefault(); |
| |
| content::RenderFrameHost* parent = GetMainRFH(kOrigin1); |
| |
| // Enable midi for the child frame. |
| content::RenderFrameHost* child = AddChildRFH( |
| parent, kOrigin2, network::mojom::PermissionsPolicyFeature::kMidiFeature); |
| permissions::MidiPermissionContext midi(profile()); |
| EXPECT_EQ(CONTENT_SETTING_ASK, GetPermissionForFrame(&midi, parent)); |
| EXPECT_EQ(CONTENT_SETTING_ASK, GetPermissionForFrame(&midi, child)); |
| } |
| |
| TEST_F(PermissionContextBasePermissionsPolicyTest, RequestPermission) { |
| content::RenderFrameHost* parent = GetMainRFH(kOrigin1); |
| |
| HostContentSettingsMapFactory::GetForProfile(profile()) |
| ->SetDefaultContentSetting(ContentSettingsType::GEOLOCATION, |
| CONTENT_SETTING_ALLOW); |
| |
| // Request geolocation in the top level frame, request should work. |
| auto geolocation = MakeGeolocationPermissionContext(); |
| EXPECT_EQ(PermissionStatus::GRANTED, |
| RequestPermissionForFrame(geolocation.get(), parent)); |
| |
| // Disable geolocation in the top level frame. |
| RefreshPageAndSetHeaderPolicy( |
| &parent, network::mojom::PermissionsPolicyFeature::kGeolocation, |
| std::vector<std::string>()); |
| |
| // Request should fail. |
| EXPECT_EQ(PermissionStatus::DENIED, |
| RequestPermissionForFrame(geolocation.get(), parent)); |
| } |