blob: d8be480e12f91fcd1be9c634e454671686912af5 [file] [log] [blame] [edit]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <optional>
#include <vector>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/content_setting_permission_context_base.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_actions_history.h"
#include "components/permissions/permissions_client.h"
#include "components/permissions/test/mock_permission_prompt_factory.h"
#include "components/permissions/test/mock_permission_request.h"
#include "components/permissions/test/permission_test_util.h"
#include "components/permissions/test/test_permissions_client.h"
#include "content/public/browser/permission_controller_delegate.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/render_frame_host_test_support.h"
#include "content/public/test/test_browser_context.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-shared.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/permissions/permission.mojom.h"
#include "url/gurl.h"
namespace permissions {
namespace {
using blink::mojom::EmbeddedPermissionRequestDescriptor;
using blink::mojom::EmbeddedPermissionRequestDescriptorPtr;
using blink::mojom::GeolocationEmbeddedPermissionRequestDescriptor;
using blink::mojom::PermissionDescriptor;
using blink::mojom::PermissionDescriptorPtr;
using blink::mojom::PermissionName;
} // namespace
class PEPCInitiatedPermissionRequestTest
: public content::RenderViewHostTestHarness {
public:
PEPCInitiatedPermissionRequestTest()
: scoped_feature_list_(blink::features::kPermissionElement) {}
PEPCInitiatedPermissionRequestTest(
const PEPCInitiatedPermissionRequestTest&) = delete;
PEPCInitiatedPermissionRequestTest& operator=(
const PEPCInitiatedPermissionRequestTest&) = delete;
~PEPCInitiatedPermissionRequestTest() override = default;
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
SetContents(CreateTestWebContents());
origin_ = GURL(permissions::MockPermissionRequest::kDefaultOrigin);
NavigateAndCommit(GURL(origin_));
PermissionRequestManager::CreateForWebContents(web_contents());
prompt_factory_ =
std::make_unique<permissions::MockPermissionPromptFactory>(
PermissionRequestManager::FromWebContents(web_contents()));
permission_request_callback_loop_ = std::make_unique<base::RunLoop>();
RebindPermissionService();
content::TestBrowserContext::FromBrowserContext(browser_context())
->SetPermissionControllerDelegate(
GetPermissionControllerDelegate(browser_context()));
}
void TearDown() override {
prompt_factory_.reset();
content::RenderViewHostTestHarness::TearDown();
}
void SetContentSetting(ContentSettingsType type, ContentSetting value) {
PermissionsClient::Get()
->GetSettingsMap(browser_context())
->SetContentSettingDefaultScope(origin_, origin_, type, value);
}
ContentSetting GetContentSetting(ContentSettingsType type) {
return PermissionsClient::Get()
->GetSettingsMap(browser_context())
->GetContentSetting(origin_, origin_, type);
}
blink::mojom::PermissionService* permission_service() {
return service_remote_.get();
}
MockPermissionPromptFactory* prompt_factory() {
return prompt_factory_.get();
}
std::vector<PermissionDescriptorPtr> CreatePermissionDescriptorPtrs(
ContentSettingsType type) {
std::vector<PermissionDescriptorPtr> descriptors;
PermissionDescriptorPtr permission_descriptor = PermissionDescriptor::New();
switch (type) {
case ContentSettingsType::MEDIASTREAM_CAMERA:
permission_descriptor->name = PermissionName::VIDEO_CAPTURE;
break;
case ContentSettingsType::MEDIASTREAM_MIC:
permission_descriptor->name = PermissionName::AUDIO_CAPTURE;
break;
case ContentSettingsType::GEOLOCATION:
permission_descriptor->name = PermissionName::GEOLOCATION;
break;
default:
NOTREACHED() << "Unsupported permission type in this test fixture";
}
descriptors.push_back(std::move(permission_descriptor));
return descriptors;
}
EmbeddedPermissionRequestDescriptorPtr
CreateEmbeddedPermissionRequestDescriptorPtr() {
EmbeddedPermissionRequestDescriptorPtr permission_descriptor =
EmbeddedPermissionRequestDescriptor::New();
return permission_descriptor;
}
EmbeddedPermissionRequestDescriptorPtr
CreateGeolocationEmbeddedPermissionRequestDescriptorPtr(
bool autolocate = false) {
EmbeddedPermissionRequestDescriptorPtr permission_descriptor =
EmbeddedPermissionRequestDescriptor::New();
permission_descriptor->geolocation =
GeolocationEmbeddedPermissionRequestDescriptor::New();
permission_descriptor->geolocation->autolocate = autolocate;
return permission_descriptor;
}
void WaitForPermissionServiceCallback() {
permission_request_callback_loop_->Run();
permission_request_callback_loop_ = std::make_unique<base::RunLoop>();
}
void PermissionServiceCallbackPEPC(
blink::mojom::EmbeddedPermissionControlResult result) {
permission_request_callback_loop_->Quit();
}
void PermissionServiceCallback(blink::mojom::PermissionStatus result) {
permission_request_callback_loop_->Quit();
}
void RebindPermissionService(content::RenderFrameHost* rfh = nullptr) {
if (!rfh) {
rfh = main_rfh();
}
service_remote_.reset();
content::CreatePermissionService(
rfh, service_remote_.BindNewPipeAndPassReceiver());
}
const GURL& origin() { return origin_; }
private:
GURL origin_;
mojo::Remote<blink::mojom::PermissionService> service_remote_;
std::unique_ptr<MockPermissionPromptFactory> prompt_factory_;
std::unique_ptr<base::RunLoop> permission_request_callback_loop_;
TestPermissionsClient client_;
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(PEPCInitiatedPermissionRequestTest, PEPCRequestWhenSettingAllowed) {
// The current setting is allowed, all new prompts will be denied.
SetContentSetting(ContentSettingsType::MEDIASTREAM_CAMERA,
CONTENT_SETTING_ALLOW);
prompt_factory()->set_response_type(
PermissionRequestManager::AutoResponseType::DENY_ALL);
// A regular request will not reach the permission request manager, since the
// permission is already granted.
permission_service()->RequestPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_CAMERA)
.front()
.Clone(),
/* user_gesture= */ true,
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallback,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// No new prompts and no change in setting.
EXPECT_EQ(0, prompt_factory()->request_count());
EXPECT_EQ(CONTENT_SETTING_ALLOW,
GetContentSetting(ContentSettingsType::MEDIASTREAM_CAMERA));
// A PEPC request is allowed through regardless of the state of the content
// setting.
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_CAMERA),
CreateEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// A prompt has been shown and since the user denied, it has resulted in a
// change to the content setting.
EXPECT_EQ(prompt_factory()->request_count(), 1);
EXPECT_EQ(CONTENT_SETTING_BLOCK,
GetContentSetting(ContentSettingsType::MEDIASTREAM_CAMERA));
}
TEST_F(PEPCInitiatedPermissionRequestTest, PEPCRequestWhenSettingBlocked) {
// The current setting is blocked, all new prompts will be allowed.
SetContentSetting(ContentSettingsType::MEDIASTREAM_MIC,
CONTENT_SETTING_BLOCK);
prompt_factory()->set_response_type(
PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
// A regular request will not reach the permission request manager, since the
// permission is blocked.
permission_service()->RequestPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_MIC)
.front()
.Clone(),
/* user_gesture= */ true,
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallback,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// No new prompts and no change in setting.
EXPECT_EQ(0, prompt_factory()->request_count());
EXPECT_EQ(CONTENT_SETTING_BLOCK,
GetContentSetting(ContentSettingsType::MEDIASTREAM_MIC));
// A PEPC request is allowed through regardless of the state of the content
// setting.
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_MIC),
CreateEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// A prompt has been shown and since the user granted, it has resulted in a
// change to the content setting.
EXPECT_EQ(prompt_factory()->request_count(), 1);
EXPECT_EQ(CONTENT_SETTING_ALLOW,
GetContentSetting(ContentSettingsType::MEDIASTREAM_MIC));
}
TEST_F(PEPCInitiatedPermissionRequestTest, PEPCRequestBlockedInFencedFrame) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
blink::features::kFencedFrames, {{"implementation_type", "mparch"}});
content::RenderFrameHost* fenced_frame =
content::RenderFrameHostTester::For(main_rfh())->AppendFencedFrame();
RebindPermissionService(fenced_frame);
// A PEPC request is not allowed in a fenced frame.
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_MIC),
CreateEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// No prompts have been created.
EXPECT_EQ(prompt_factory()->request_count(), 0);
}
TEST_F(PEPCInitiatedPermissionRequestTest,
PEPCRequestAllowedWithFeaturePolicy) {
// The current setting is blocked, all new prompts will be allowed.
SetContentSetting(ContentSettingsType::MEDIASTREAM_MIC,
CONTENT_SETTING_BLOCK);
prompt_factory()->set_response_type(
PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
network::ParsedPermissionsPolicy frame_policy;
frame_policy.emplace_back(
network::mojom::PermissionsPolicyFeature::kMicrophone,
/*allowed_origins=*/
std::vector{*network::OriginWithPossibleWildcards::FromOrigin(
url::Origin::Create(origin()))},
/*self_if_matches=*/std::nullopt, /*matches_all_origins=*/false,
/*matches_opaque_src=*/false);
content::RenderFrameHost* valid_child =
content::RenderFrameHostTester::For(main_rfh())
->AppendChildWithPolicy("child", frame_policy);
RebindPermissionService(valid_child);
// A PEPC request is allowed through from a frame with a valid policy.
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_MIC),
CreateEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// A prompt has been shown because the frame's policy allows requests.
EXPECT_EQ(prompt_factory()->request_count(), 1);
EXPECT_EQ(CONTENT_SETTING_ALLOW,
GetContentSetting(ContentSettingsType::MEDIASTREAM_MIC));
}
TEST_F(PEPCInitiatedPermissionRequestTest,
PEPCRequestBlockedWithoutFeaturePolicy) {
network::ParsedPermissionsPolicy frame_policy;
frame_policy.push_back({network::mojom::PermissionsPolicyFeature::kMicrophone,
/*allowed_origins=*/
{*network::OriginWithPossibleWildcards::FromOrigin(
url::Origin::Create(GURL("http://fakeurl.com")))},
/*self_if_matches=*/std::nullopt,
/*matches_all_origins=*/false,
/*matches_opaque_src=*/false});
content::RenderFrameHost* invalid_child =
content::RenderFrameHostTester::For(main_rfh())
->AppendChildWithPolicy("child", frame_policy);
RebindPermissionService(invalid_child);
// A PEPC request is not allowed through from a frame without a valid policy.
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_MIC),
CreateEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// No prompt has been shown because the request was not allowed.
EXPECT_EQ(prompt_factory()->request_count(), 0);
}
TEST_F(PEPCInitiatedPermissionRequestTest, PEPCRequestBlockedByKillSwitch) {
// Setup the kill switch.
std::map<std::string, std::string> params;
params[permissions::PermissionUtil::GetPermissionString(
ContentSettingsType::MEDIASTREAM_CAMERA)] =
ContentSettingPermissionContextBase::kPermissionsKillSwitchBlockedValue;
base::AssociateFieldTrialParams(
permissions::ContentSettingPermissionContextBase::
kPermissionsKillSwitchFieldStudy,
"TestGroup", params);
base::FieldTrialList::CreateFieldTrial(
permissions::ContentSettingPermissionContextBase::
kPermissionsKillSwitchFieldStudy,
"TestGroup");
// Attempt to make a PEPC request.
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_CAMERA),
CreateEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// PEPC requests are not allowed when the kill switch is on.
EXPECT_EQ(prompt_factory()->request_count(), 0);
}
TEST_F(PEPCInitiatedPermissionRequestTest, PEPCRequestBlockedOnInsecureOrigin) {
GURL insecure_origin("http://google.com");
NavigateAndCommit(insecure_origin);
RebindPermissionService();
// Attempt to make a PEPC request.
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::MEDIASTREAM_CAMERA),
CreateEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// PEPC requests are not allowed from insecure origins.
EXPECT_EQ(prompt_factory()->request_count(), 0);
}
class PEPCInitiatedPermissionRequestTestWithAutolocate
: public PEPCInitiatedPermissionRequestTest,
public ::testing::WithParamInterface<bool> {};
TEST_P(PEPCInitiatedPermissionRequestTestWithAutolocate,
PEPCRequestHeuristicallyGrantedGeolocation) {
bool autolocate = GetParam();
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(blink::features::kGeolocationElement);
auto* history =
PermissionsClient::Get()->GetPermissionActionsHistory(browser_context());
const int kHeuristicGrantThreshold = 3;
// Grant just below the threshold.
for (int i = 0; i < kHeuristicGrantThreshold; ++i) {
EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(
origin(), ContentSettingsType::GEOLOCATION));
}
// The next grant will trigger the auto-grant.
EXPECT_TRUE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(
origin(), ContentSettingsType::GEOLOCATION));
EXPECT_TRUE(history->CheckHeuristicallyAutoGranted(
origin(), ContentSettingsType::GEOLOCATION, /*needs_update*/ false));
if (!autolocate) {
// A PEPC request should be auto-granted without a prompt because Autolocate
// is on.
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::GEOLOCATION),
CreateGeolocationEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// TODO(crbug.com/448327619): verify content setting before and after.
EXPECT_EQ(prompt_factory()->request_count(), 0);
// The heuristic grant should not be reset because the user was not
// prompted.
EXPECT_TRUE(history->CheckHeuristicallyAutoGranted(
origin(), ContentSettingsType::GEOLOCATION, /*needs_update*/ false));
// Let's test dismissing the prompt resets the heuristic.
// Now, reset the heuristic grant and increase the count by 2.
history->ResetHeuristicData(origin(), ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(
origin(), ContentSettingsType::GEOLOCATION));
EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(
origin(), ContentSettingsType::GEOLOCATION));
prompt_factory()->set_response_type(
PermissionRequestManager::AutoResponseType::DISMISS);
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::GEOLOCATION),
CreateGeolocationEmbeddedPermissionRequestDescriptorPtr(),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// A second prompt was shown.
EXPECT_EQ(prompt_factory()->request_count(), 1);
EXPECT_EQ(history->GetTemporaryGrantCountForTesting(
origin(), ContentSettingsType::GEOLOCATION),
0);
} else {
// A geolocation PEPC request should still show a prompt, even with a
// heuristic grant, if autolocate is true.
prompt_factory()->set_response_type(
PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
permission_service()->RequestPageEmbeddedPermission(
CreatePermissionDescriptorPtrs(ContentSettingsType::GEOLOCATION),
CreateGeolocationEmbeddedPermissionRequestDescriptorPtr(autolocate),
base::BindOnce(
&PEPCInitiatedPermissionRequestTest::PermissionServiceCallbackPEPC,
base::Unretained(this)));
WaitForPermissionServiceCallback();
// A prompt should have been shown, and the setting updated to an ALLOW.
EXPECT_EQ(prompt_factory()->request_count(), 1);
EXPECT_EQ(CONTENT_SETTING_ALLOW,
GetContentSetting(ContentSettingsType::GEOLOCATION));
}
}
INSTANTIATE_TEST_SUITE_P(All,
PEPCInitiatedPermissionRequestTestWithAutolocate,
::testing::Bool());
} // namespace permissions