| // 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 "chrome/browser/hid/hid_chooser_context.h" |
| |
| #include <array> |
| #include <optional> |
| #include <string_view> |
| |
| #include "base/barrier_closure.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/scoped_observation.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "base/test/values_test_util.h" |
| #include "base/uuid.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/hid/hid_chooser_context_factory.h" |
| #include "chrome/browser/hid/mock_hid_device_observer.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/permissions/test/object_permission_context_base_mock_permission_observer.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "extensions/common/extension_features.h" |
| #include "google_apis/gaia/gaia_id.h" |
| #include "services/device/public/cpp/device_features.h" |
| #include "services/device/public/cpp/hid/hid_blocklist.h" |
| #include "services/device/public/cpp/test/fake_hid_manager.h" |
| #include "services/device/public/mojom/hid.mojom.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" |
| #include "chromeos/ash/components/browser_context_helper/browser_context_types.h" |
| #include "components/account_id/account_id.h" |
| #include "components/user_manager/scoped_user_manager.h" |
| #endif |
| |
| using ::base::test::ParseJson; |
| using ::base::test::RunClosure; |
| |
| namespace { |
| |
| // The device IDs used by the simulated HID device. |
| constexpr uint16_t kTestVendorId = 0x1234; |
| constexpr uint16_t kTestProductId = 0xabcd; |
| constexpr char kTestSerialNumber[] = "serial-number"; |
| constexpr char kTestProductName[] = "product-name"; |
| constexpr auto kTestPhysicalDeviceIds = std::to_array<const char*>( |
| {"physical-device-id-1", "physical-device-id-2"}); |
| constexpr char kTestUserEmail[] = "user@example.com"; |
| |
| // The HID usages assigned to the top-level collection of the simulated device. |
| constexpr uint16_t kTestUsagePage = device::mojom::kPageGenericDesktop; |
| constexpr uint16_t kTestUsage = device::mojom::kGenericDesktopGamePad; |
| |
| constexpr char kTestExtensionId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; |
| |
| // Main text fixture. |
| class HidChooserContextTestBase { |
| public: |
| HidChooserContextTestBase() { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kSecurityKeyHidInterfacesAreFido); |
| } |
| |
| HidChooserContextTestBase(const HidChooserContextTestBase&) = delete; |
| HidChooserContextTestBase& operator=(const HidChooserContextTestBase&) = |
| delete; |
| ~HidChooserContextTestBase() = default; |
| |
| void DoSetUp(bool is_affiliated, bool login_user) { |
| auto* profile_name = kTestUserEmail; |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (login_user) { |
| const GaiaId kTestUserGaiaId("1111111111"); |
| auto fake_user_manager = std::make_unique<ash::FakeChromeUserManager>(); |
| auto* fake_user_manager_ptr = fake_user_manager.get(); |
| scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>( |
| std::move(fake_user_manager)); |
| |
| auto account_id = |
| AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId); |
| fake_user_manager_ptr->AddUserWithAffiliation(account_id, is_affiliated); |
| fake_user_manager_ptr->LoginUser(account_id); |
| } else { |
| profile_name = ash::kSigninBrowserContextBaseName; |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| testing_profile_manager_ = std::make_unique<TestingProfileManager>( |
| TestingBrowserProcess::GetGlobal()); |
| ASSERT_TRUE(testing_profile_manager_->SetUp()); |
| profile_ = testing_profile_manager_->CreateTestingProfile(profile_name); |
| ASSERT_TRUE(profile_); |
| |
| mojo::PendingRemote<device::mojom::HidManager> hid_manager; |
| hid_manager_.Bind(hid_manager.InitWithNewPipeAndPassReceiver()); |
| context_ = HidChooserContextFactory::GetForProfile(profile_); |
| |
| // Connect the HidManager and ensure we've received the initial enumeration |
| // before continuing. |
| base::RunLoop run_loop; |
| context_->SetHidManagerForTesting( |
| std::move(hid_manager), |
| base::BindLambdaForTesting( |
| [&run_loop](std::vector<device::mojom::HidDeviceInfoPtr> devices) { |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| scoped_permission_observation_.Observe(context_.get()); |
| scoped_device_observation_.Observe(context_.get()); |
| } |
| |
| void DoTearDown() { |
| // Because HidBlocklist is a singleton it must be cleared after tests run to |
| // prevent leakage between tests. |
| feature_list_.Reset(); |
| device::HidBlocklist::Get().ResetToDefaultValuesForTest(); |
| } |
| |
| HidChooserContext* context() { return context_; } |
| permissions::MockPermissionObserver& permission_observer() { |
| return permission_observer_; |
| } |
| MockHidDeviceObserver& device_observer() { return device_observer_; } |
| |
| device::mojom::HidDeviceInfoPtr CreateDevice( |
| std::string_view serial_number, |
| const std::string& physical_device_id = kTestPhysicalDeviceIds[0]) { |
| auto collection = device::mojom::HidCollectionInfo::New(); |
| collection->usage = |
| device::mojom::HidUsageAndPage::New(kTestUsage, kTestUsagePage); |
| collection->collection_type = device::mojom::kHIDCollectionTypeApplication; |
| collection->input_reports.push_back( |
| device::mojom::HidReportDescription::New()); |
| |
| auto device = device::mojom::HidDeviceInfo::New(); |
| device->guid = base::Uuid::GenerateRandomV4().AsLowercaseString(); |
| device->physical_device_id = physical_device_id; |
| device->vendor_id = kTestVendorId; |
| device->product_id = kTestProductId; |
| device->product_name = kTestProductName; |
| device->serial_number = std::string{serial_number}; |
| device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB; |
| device->collections.push_back(std::move(collection)); |
| device->protected_input_report_ids = |
| device::HidBlocklist::Get().GetProtectedReportIds( |
| device::HidBlocklist::kReportTypeInput, kTestVendorId, |
| kTestProductId, device->collections); |
| device->protected_output_report_ids = |
| device::HidBlocklist::Get().GetProtectedReportIds( |
| device::HidBlocklist::kReportTypeOutput, kTestVendorId, |
| kTestProductId, device->collections); |
| device->protected_feature_report_ids = |
| device::HidBlocklist::Get().GetProtectedReportIds( |
| device::HidBlocklist::kReportTypeFeature, kTestVendorId, |
| kTestProductId, device->collections); |
| device->is_excluded_by_blocklist = |
| device::HidBlocklist::Get().IsVendorProductBlocked(kTestVendorId, |
| kTestProductId); |
| return device; |
| } |
| |
| device::mojom::HidDeviceInfoPtr ConnectEphemeralDeviceBlocking() { |
| return ConnectDeviceBlocking(CreateDevice(/*serial_number=*/"")); |
| } |
| |
| device::mojom::HidDeviceInfoPtr ConnectPersistentUsbDeviceBlocking() { |
| return ConnectDeviceBlocking(CreateDevice(kTestSerialNumber)); |
| } |
| |
| device::mojom::HidDeviceInfoPtr ConnectFidoDeviceBlocking() { |
| auto device = CreateDevice(/*serial_number=*/""); |
| device->collections[0]->usage->usage_page = device::mojom::kPageFido; |
| device->collections[0]->usage->usage = 1; |
| return ConnectDeviceBlocking(std::move(device)); |
| } |
| |
| // Security key devices with multiple USB HID interfaces may have some that |
| // are not FIDO. This method creates a HidDeviceInfoPtr representing one of |
| // the non-FIDO sibling interfaces of a known security key. |
| device::mojom::HidDeviceInfoPtr ConnectFidoSiblingDeviceBlocking() { |
| // Device identifiers for a known security key |
| static constexpr uint16_t kVendorGoogle = 0x18d1; |
| static constexpr uint16_t kProductTitan = 0x5026; |
| |
| auto device = CreateDevice(/*serial_number=*/""); |
| device->vendor_id = kVendorGoogle; |
| device->product_id = kProductTitan; |
| device->is_excluded_by_blocklist = |
| device::HidBlocklist::Get().IsVendorProductBlocked(kVendorGoogle, |
| kProductTitan); |
| device->collections[0]->usage->usage_page = device::mojom::kPageVendor; |
| device->collections[0]->usage->usage = 1; |
| return ConnectDeviceBlocking(std::move(device)); |
| } |
| |
| device::mojom::HidDeviceInfoPtr ConnectDeviceBlocking( |
| device::mojom::HidDeviceInfoPtr device) { |
| base::test::TestFuture<device::mojom::HidDeviceInfoPtr> future_device; |
| EXPECT_CALL(device_observer_, OnDeviceAdded).WillOnce([&](const auto& d) { |
| future_device.SetValue(d.Clone()); |
| }); |
| hid_manager_.AddDevice(std::move(device)); |
| return future_device.Take(); |
| } |
| |
| device::mojom::HidDeviceInfoPtr DisconnectDeviceBlocking( |
| const std::string& device_guid) { |
| base::test::TestFuture<device::mojom::HidDeviceInfoPtr> future_device; |
| EXPECT_CALL(device_observer_, OnDeviceRemoved).WillOnce([&](const auto& d) { |
| future_device.SetValue(d.Clone()); |
| }); |
| hid_manager_.RemoveDevice(device_guid); |
| return future_device.Take(); |
| } |
| |
| device::mojom::HidDeviceInfoPtr UpdateDeviceBlocking( |
| device::mojom::HidDeviceInfoPtr device) { |
| base::test::TestFuture<device::mojom::HidDeviceInfoPtr> future_device; |
| EXPECT_CALL(device_observer_, OnDeviceChanged).WillOnce([&](const auto& d) { |
| future_device.SetValue(d.Clone()); |
| }); |
| hid_manager_.ChangeDevice(std::move(device)); |
| return future_device.Take(); |
| } |
| |
| void SimulateHidManagerConnectionError() { |
| hid_manager_.SimulateConnectionError(); |
| } |
| |
| void ExpectObjectPermissionChanged() { |
| EXPECT_CALL(permission_observer_, |
| OnObjectPermissionChanged( |
| std::make_optional(ContentSettingsType::HID_GUARD), |
| ContentSettingsType::HID_CHOOSER_DATA)); |
| } |
| |
| void GrantDevicePermissionBlocking( |
| const url::Origin& origin, |
| const device::mojom::HidDeviceInfo& device, |
| const std::optional<url::Origin>& embedding_origin = std::nullopt) { |
| base::RunLoop loop; |
| EXPECT_CALL(permission_observer_, |
| OnObjectPermissionChanged( |
| std::make_optional(ContentSettingsType::HID_GUARD), |
| ContentSettingsType::HID_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| context()->GrantDevicePermission(origin, device, embedding_origin); |
| loop.Run(); |
| } |
| |
| void RevokeObjectPermissionBlocking(const url::Origin& origin, |
| const base::Value::Dict& object) { |
| base::RunLoop loop; |
| EXPECT_CALL(permission_observer_, |
| OnObjectPermissionChanged( |
| std::make_optional(ContentSettingsType::HID_GUARD), |
| ContentSettingsType::HID_CHOOSER_DATA)) |
| .Times(testing::AtLeast(1)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| context()->RevokeObjectPermission(origin, object); |
| loop.Run(); |
| } |
| |
| void SetDynamicBlocklist(std::string_view value) { |
| feature_list_.Reset(); |
| |
| std::map<std::string, std::string> parameters; |
| parameters[device::kWebHidBlocklistAdditions.name] = std::string{value}; |
| feature_list_.InitWithFeaturesAndParameters( |
| {{device::kWebHidBlocklist, parameters}}, {}); |
| |
| device::HidBlocklist::Get().ResetToDefaultValuesForTest(); |
| } |
| |
| void SetContentSettingDefaultForOrigin(const url::Origin& origin, |
| ContentSetting content_setting) { |
| HostContentSettingsMapFactory::GetForProfile(profile_) |
| ->SetContentSettingDefaultScope(origin.GetURL(), origin.GetURL(), |
| ContentSettingsType::HID_GUARD, |
| content_setting); |
| } |
| |
| void SetContentSettingDefaultPolicy(ContentSetting content_setting) { |
| profile_->GetTestingPrefService()->SetManagedPref( |
| prefs::kManagedDefaultWebHidGuardSetting, |
| std::make_unique<base::Value>(content_setting)); |
| } |
| |
| void SetAskForUrlsPolicy(std::string_view policy) { |
| profile_->GetTestingPrefService()->SetManagedPref( |
| prefs::kManagedWebHidAskForUrls, ParseJson(policy)); |
| } |
| |
| void SetBlockedForUrlsPolicy(std::string_view policy) { |
| profile_->GetTestingPrefService()->SetManagedPref( |
| prefs::kManagedWebHidBlockedForUrls, ParseJson(policy)); |
| } |
| |
| void SetAllowDevicesForUrlsPolicy(std::string_view policy) { |
| TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref( |
| prefs::kManagedWebHidAllowDevicesForUrls, ParseJson(policy)); |
| } |
| |
| void SetAllowDevicesForUrlsOnLoginScreenPolicy(std::string_view policy) { |
| TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref( |
| prefs::kManagedWebHidAllowDevicesForUrlsOnLoginScreen, |
| ParseJson(policy)); |
| } |
| |
| void SetAllowDevicesWithHidUsagesForUrlsPolicy(std::string_view policy) { |
| TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref( |
| prefs::kManagedWebHidAllowDevicesWithHidUsagesForUrls, |
| ParseJson(policy)); |
| } |
| |
| void SetAllowAllDevicesForUrlsPolicy(std::string_view policy) { |
| TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref( |
| prefs::kManagedWebHidAllowAllDevicesForUrls, ParseJson(policy)); |
| } |
| |
| private: |
| device::FakeHidManager hid_manager_; |
| content::BrowserTaskEnvironment task_environment_; |
| base::test::ScopedFeatureList feature_list_; |
| std::unique_ptr<TestingProfileManager> testing_profile_manager_; |
| raw_ptr<TestingProfile> profile_ = nullptr; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; |
| #endif |
| |
| raw_ptr<HidChooserContext> context_; |
| permissions::MockPermissionObserver permission_observer_; |
| base::ScopedObservation< |
| permissions::ObjectPermissionContextBase, |
| permissions::ObjectPermissionContextBase::PermissionObserver> |
| scoped_permission_observation_{&permission_observer_}; |
| MockHidDeviceObserver device_observer_; |
| base::ScopedObservation<HidChooserContext, HidChooserContext::DeviceObserver> |
| scoped_device_observation_{&device_observer_}; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| class HidChooserContextTest : public HidChooserContextTestBase, |
| public testing::Test { |
| public: |
| void SetUp() override { |
| DoSetUp(/*is_affiliated=*/true, /*login_user=*/true); |
| } |
| void TearDown() override { DoTearDown(); } |
| }; |
| |
| } // namespace |
| |
| TEST_F(HidChooserContextTest, GrantAndRevokeEphemeralDevice) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a device that is only eligible for ephemeral permissions. |
| auto device = ConnectEphemeralDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant an ephemeral permission. |
| GrantDevicePermissionBlocking(kOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| |
| std::vector<std::unique_ptr<HidChooserContext::Object>> origin_objects = |
| context()->GetGrantedObjects(kOrigin); |
| ASSERT_EQ(1u, origin_objects.size()); |
| |
| std::vector<std::unique_ptr<HidChooserContext::Object>> objects = |
| context()->GetAllGrantedObjects(); |
| ASSERT_EQ(1u, objects.size()); |
| EXPECT_EQ(kOrigin.GetURL(), objects[0]->origin); |
| EXPECT_EQ(origin_objects[0]->value, objects[0]->value); |
| EXPECT_EQ(content_settings::SettingSource::kUser, objects[0]->source); |
| EXPECT_FALSE(objects[0]->incognito); |
| |
| // Revoke the permission. |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)); |
| RevokeObjectPermissionBlocking(kOrigin, objects[0]->value); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, GrantAndForgetEphemeralDevice) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a device with multiple HID interfaces that is only eligible for |
| // ephemeral permissions. |
| auto device1 = ConnectEphemeralDeviceBlocking(); |
| auto device2 = ConnectEphemeralDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant ephemeral permissions. |
| GrantDevicePermissionBlocking(kOrigin, *device1); |
| GrantDevicePermissionBlocking(kOrigin, *device2); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device2)); |
| EXPECT_EQ(2u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(2u, context()->GetAllGrantedObjects().size()); |
| |
| // Forget the ephemeral device. |
| base::RunLoop permissions_revoked_loop; |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)) |
| .WillOnce(RunClosure(permissions_revoked_loop.QuitClosure())); |
| EXPECT_CALL(permission_observer(), |
| OnObjectPermissionChanged( |
| std::make_optional(ContentSettingsType::HID_GUARD), |
| ContentSettingsType::HID_CHOOSER_DATA)); |
| context()->RevokeDevicePermission(kOrigin, *device1); |
| permissions_revoked_loop.Run(); |
| |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, GrantTwoEphemeralDevicesForgetOne) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect two devices that are only eligible for ephemeral permissions. |
| auto device1 = ConnectDeviceBlocking(CreateDevice( |
| /*serial_number=*/"", /*physical_device_id=*/kTestPhysicalDeviceIds[0])); |
| auto device2 = ConnectDeviceBlocking(CreateDevice( |
| /*serial_number=*/"", /*physical_device_id=*/kTestPhysicalDeviceIds[1])); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant ephemeral permissions. |
| GrantDevicePermissionBlocking(kOrigin, *device1); |
| GrantDevicePermissionBlocking(kOrigin, *device2); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device2)); |
| EXPECT_EQ(2u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(2u, context()->GetAllGrantedObjects().size()); |
| |
| // Forget the first device. |
| base::RunLoop permissions_revoked_loop; |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)) |
| .WillOnce(RunClosure(permissions_revoked_loop.QuitClosure())); |
| EXPECT_CALL(permission_observer(), |
| OnObjectPermissionChanged( |
| std::make_optional(ContentSettingsType::HID_GUARD), |
| ContentSettingsType::HID_CHOOSER_DATA)); |
| context()->RevokeDevicePermission(kOrigin, *device1); |
| permissions_revoked_loop.Run(); |
| |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device2)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, GrantAndDisconnectEphemeralDevice) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a device that is only eligible for ephemeral permissions. |
| auto device = ConnectEphemeralDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant an ephemeral permission. |
| GrantDevicePermissionBlocking(kOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| |
| auto origin_objects = context()->GetGrantedObjects(kOrigin); |
| ASSERT_EQ(1u, origin_objects.size()); |
| |
| auto objects = context()->GetAllGrantedObjects(); |
| ASSERT_EQ(1u, objects.size()); |
| EXPECT_EQ(kOrigin.GetURL(), objects[0]->origin); |
| EXPECT_EQ(origin_objects[0]->value, objects[0]->value); |
| EXPECT_EQ(content_settings::SettingSource::kUser, objects[0]->source); |
| EXPECT_FALSE(objects[0]->incognito); |
| |
| // Disconnect the device. Because an ephemeral permission was granted, the |
| // permission should be revoked on disconnect. |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)); |
| EXPECT_CALL(permission_observer(), |
| OnObjectPermissionChanged( |
| std::make_optional(ContentSettingsType::HID_GUARD), |
| ContentSettingsType::HID_CHOOSER_DATA)); |
| DisconnectDeviceBlocking(device->guid); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, GrantDisconnectRevokeUsbPersistentDevice) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a USB device eligible for persistent permissions. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant a persistent permission. |
| GrantDevicePermissionBlocking(kOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| |
| auto origin_objects = context()->GetGrantedObjects(kOrigin); |
| ASSERT_EQ(1u, origin_objects.size()); |
| auto objects = context()->GetAllGrantedObjects(); |
| ASSERT_EQ(1u, objects.size()); |
| EXPECT_EQ(kOrigin.GetURL(), objects[0]->origin); |
| EXPECT_EQ(origin_objects[0]->value, objects[0]->value); |
| EXPECT_EQ(content_settings::SettingSource::kUser, objects[0]->source); |
| EXPECT_FALSE(objects[0]->incognito); |
| |
| // Disconnect the device. The permission should not be revoked. |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)).Times(0); |
| DisconnectDeviceBlocking(device->guid); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| objects = context()->GetAllGrantedObjects(); |
| EXPECT_EQ(1u, objects.size()); |
| |
| // Revoke the persistent permission. |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)); |
| RevokeObjectPermissionBlocking(kOrigin, objects[0]->value); |
| |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, GrantForgetUsbPersistentDevice) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a USB device with multiple HID interfaces eligible for |
| // persistent permissions. |
| auto device1 = ConnectPersistentUsbDeviceBlocking(); |
| auto device2 = ConnectPersistentUsbDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2)); |
| |
| // Grant persistent permissions. |
| GrantDevicePermissionBlocking(kOrigin, *device1); |
| GrantDevicePermissionBlocking(kOrigin, *device2); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device2)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| auto objects = context()->GetAllGrantedObjects(); |
| ASSERT_EQ(1u, objects.size()); |
| |
| // Forget the device by revoking the persistent permission. |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)); |
| RevokeObjectPermissionBlocking(kOrigin, objects[0]->value); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, GuardPermission) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a device that is only eligible for ephemeral permissions. |
| auto device = ConnectEphemeralDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant an ephemeral device permission. |
| GrantDevicePermissionBlocking(kOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| |
| // Set the guard permission to CONTENT_SETTING_BLOCK. |
| SetContentSettingDefaultForOrigin(kOrigin, CONTENT_SETTING_BLOCK); |
| |
| // Check that the device permission is no longer granted. |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, ConnectionErrorWithEphemeralPermission) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a device that is only eligible for ephemeral permissions. |
| auto device = ConnectEphemeralDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant an ephemeral device permission. |
| GrantDevicePermissionBlocking(kOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| |
| // Simulate a connection error. The ephemeral permission should be revoked. |
| base::RunLoop loop; |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| EXPECT_CALL(permission_observer(), |
| OnObjectPermissionChanged( |
| std::make_optional(ContentSettingsType::HID_GUARD), |
| ContentSettingsType::HID_CHOOSER_DATA)); |
| EXPECT_CALL(device_observer(), OnHidManagerConnectionError()); |
| SimulateHidManagerConnectionError(); |
| loop.Run(); |
| |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, ConnectionErrorWithPersistentPermission) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a USB device eligible for persistent permissions. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant a persistent device permission. |
| GrantDevicePermissionBlocking(kOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| |
| // Simulate a connection error. The persistent permission should not be |
| // affected. |
| base::RunLoop loop; |
| EXPECT_CALL(device_observer(), OnHidManagerConnectionError()) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| EXPECT_CALL(permission_observer(), |
| OnObjectPermissionChanged( |
| std::make_optional(ContentSettingsType::HID_GUARD), |
| ContentSettingsType::HID_CHOOSER_DATA)); |
| SimulateHidManagerConnectionError(); |
| loop.Run(); |
| |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| namespace { |
| |
| device::mojom::HidDeviceInfoPtr CreateDeviceWithOneCollection( |
| const std::string& guid) { |
| auto device_info = device::mojom::HidDeviceInfo::New(); |
| device_info->guid = guid; |
| auto collection = device::mojom::HidCollectionInfo::New(); |
| collection->usage = device::mojom::HidUsageAndPage::New(1, 1); |
| collection->input_reports.push_back( |
| device::mojom::HidReportDescription::New()); |
| device_info->collections.push_back(std::move(collection)); |
| return device_info; |
| } |
| |
| device::mojom::HidDeviceInfoPtr CreateDeviceWithTwoCollections( |
| const std::string& guid) { |
| auto device_info = CreateDeviceWithOneCollection(guid); |
| auto collection = device::mojom::HidCollectionInfo::New(); |
| collection->usage = device::mojom::HidUsageAndPage::New(2, 2); |
| collection->output_reports.push_back( |
| device::mojom::HidReportDescription::New()); |
| device_info->collections.push_back(std::move(collection)); |
| return device_info; |
| } |
| |
| } // namespace |
| |
| TEST_F(HidChooserContextTest, AddChangeRemoveDevice) { |
| const char kTestGuid[] = "guid"; |
| |
| EXPECT_FALSE(context()->GetDeviceInfo(kTestGuid)); |
| |
| // Connect a partially-initialized device. |
| auto partial_device = |
| ConnectDeviceBlocking(CreateDeviceWithOneCollection(kTestGuid)); |
| EXPECT_EQ(partial_device->guid, kTestGuid); |
| EXPECT_EQ(partial_device->collections.size(), 1u); |
| auto* device_info = context()->GetDeviceInfo(kTestGuid); |
| ASSERT_TRUE(device_info); |
| EXPECT_EQ(device_info->collections.size(), 1u); |
| |
| // Update the device to add another collection. |
| auto complete_device = |
| UpdateDeviceBlocking(CreateDeviceWithTwoCollections(kTestGuid)); |
| EXPECT_EQ(complete_device->guid, kTestGuid); |
| EXPECT_EQ(complete_device->collections.size(), 2u); |
| device_info = context()->GetDeviceInfo(kTestGuid); |
| ASSERT_TRUE(device_info); |
| EXPECT_EQ(device_info->collections.size(), 2u); |
| |
| // Disconnect the device. |
| auto removed_device = DisconnectDeviceBlocking(complete_device->guid); |
| EXPECT_EQ(removed_device->guid, kTestGuid); |
| EXPECT_EQ(removed_device->collections.size(), 2u); |
| ASSERT_FALSE(context()->GetDeviceInfo(kTestGuid)); |
| } |
| |
| namespace { |
| |
| struct BlocklistTestData { |
| const char* blocklist; |
| bool expect_device_permission; |
| } kBlocklistTestData[]{ |
| {nullptr, true}, {"", true}, |
| {"1234:abcd::::", false}, {"1234:0001::::", true}, |
| {"1234:::::", false}, {"2468:::::", true}, |
| {"::0001:0005::", true}, {"::0001:0006::", true}, |
| {"::0001:::", true}, {"::ff00:::", true}, |
| }; |
| |
| class HidChooserContextBlocklistTest |
| : public HidChooserContextTestBase, |
| public testing::TestWithParam<BlocklistTestData> { |
| public: |
| HidChooserContextBlocklistTest() = default; |
| |
| void SetUp() override { |
| DoSetUp(/*is_affiliated=*/true, /*login_user=*/true); |
| } |
| void TearDown() override { DoTearDown(); } |
| }; |
| |
| } // namespace |
| |
| TEST_P(HidChooserContextBlocklistTest, Blocklist) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| if (GetParam().blocklist) |
| SetDynamicBlocklist(GetParam().blocklist); |
| |
| // Connect a device. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Try to grant permission. |
| GrantDevicePermissionBlocking(kOrigin, *device); |
| EXPECT_EQ(GetParam().expect_device_permission, |
| context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(HidChooserContextBlocklistTestInstance, |
| HidChooserContextBlocklistTest, |
| ::testing::ValuesIn(kBlocklistTestData)); |
| |
| TEST_F(HidChooserContextTest, PolicyGuardPermission) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a device. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kOrigin)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant permission and check that the permission was granted. |
| GrantDevicePermissionBlocking(kOrigin, *device); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kOrigin)); |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| |
| // Set a policy to block access to HID devices. After setting the policy, no |
| // permissions should be granted and requesting permissions should be blocked. |
| SetContentSettingDefaultPolicy(CONTENT_SETTING_BLOCK); |
| EXPECT_FALSE(context()->CanRequestObjectPermission(kOrigin)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, PolicyAskForUrls) { |
| const auto kAskOrigin = url::Origin::Create(GURL("https://ask.origin")); |
| const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin")); |
| |
| // Connect a device. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kAskOrigin)); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant permission for kAskOrigin and kOtherOrigin to access |device|. |
| GrantDevicePermissionBlocking(kAskOrigin, *device); |
| GrantDevicePermissionBlocking(kOtherOrigin, *device); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kAskOrigin)); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin)); |
| EXPECT_TRUE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_TRUE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(2u, context()->GetAllGrantedObjects().size()); |
| |
| // Set the default guard policy to "block", overriding the granted |
| // permissions. |
| SetContentSettingDefaultPolicy(CONTENT_SETTING_BLOCK); |
| EXPECT_FALSE(context()->CanRequestObjectPermission(kAskOrigin)); |
| EXPECT_FALSE(context()->CanRequestObjectPermission(kOtherOrigin)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Set the AskForUrls policy to allow kAskOrigin to request permissions. |
| // This policy overrides the default guard policy. |
| SetAskForUrlsPolicy(R"( [ "https://ask.origin" ] )"); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kAskOrigin)); |
| EXPECT_FALSE(context()->CanRequestObjectPermission(kOtherOrigin)); |
| EXPECT_TRUE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, PolicyBlockedForUrls) { |
| const auto kBlockedOrigin = |
| url::Origin::Create(GURL("https://blocked.origin")); |
| const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin")); |
| |
| // Connect a device. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kBlockedOrigin)); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin)); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Grant permission for kBlockedOrigin and kOtherOrigin to access |device|. |
| GrantDevicePermissionBlocking(kBlockedOrigin, *device); |
| GrantDevicePermissionBlocking(kOtherOrigin, *device); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kBlockedOrigin)); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin)); |
| EXPECT_TRUE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_TRUE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(2u, context()->GetAllGrantedObjects().size()); |
| |
| // Set the BlockedForUrls policy to block kBlockedOrigin from accessing |
| // devices or requesting permissions. This policy overrides user-granted |
| // permissions and the default guard setting. |
| SetBlockedForUrlsPolicy(R"([ "https://blocked.origin" ])"); |
| EXPECT_FALSE(context()->CanRequestObjectPermission(kBlockedOrigin)); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin)); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_TRUE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| namespace { |
| |
| class HidChooserContextAffiliatedTest : public HidChooserContextTestBase, |
| public testing::TestWithParam<bool> { |
| public: |
| HidChooserContextAffiliatedTest() : is_affiliated_(GetParam()) {} |
| |
| void SetUp() override { DoSetUp(is_affiliated_, /*login_user=*/true); } |
| void TearDown() override { DoTearDown(); } |
| |
| bool is_affiliated() const { return is_affiliated_; } |
| |
| private: |
| bool is_affiliated_; |
| }; |
| |
| } // namespace |
| |
| TEST_P(HidChooserContextAffiliatedTest, PolicyAllowForUrls) { |
| const auto kBlockedOrigin = |
| url::Origin::Create(GURL("https://blocked.origin")); |
| const auto kAskOrigin = url::Origin::Create(GURL("https://ask.origin")); |
| const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin")); |
| |
| // Connect a device. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| |
| // Set the default content settings to "block" by policy, and set the |
| // AskForUrls and BlockedForUrls policies to override the default for |
| // kAskOrigin and kBlockedOrigin. |
| SetContentSettingDefaultPolicy(CONTENT_SETTING_BLOCK); |
| SetAskForUrlsPolicy(R"([ "https://ask.origin" ])"); |
| SetBlockedForUrlsPolicy(R"([ "https://blocked.origin" ])"); |
| EXPECT_FALSE(context()->CanRequestObjectPermission(kBlockedOrigin)); |
| EXPECT_TRUE(context()->CanRequestObjectPermission(kAskOrigin)); |
| EXPECT_FALSE(context()->CanRequestObjectPermission(kOtherOrigin)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Set the AllowAllDevicesForUrls policy to grant all three origins permission |
| // to access any device. |
| SetAllowAllDevicesForUrlsPolicy(R"( |
| [ |
| "https://blocked.origin", |
| "https://ask.origin", |
| "https://other.origin" |
| ])"); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kOtherOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(3u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| // Set the AllowAllDevicesForUrls policy back to the default value. |
| SetAllowAllDevicesForUrlsPolicy("[]"); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Set the AllowDevicesForUrls policy to grant permissions to kAskOrigin and |
| // kOtherOrigin by matching kTestVendorId and kTestProductId. |
| SetAllowDevicesForUrlsPolicy(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 4660, "product_id": 43981 }], |
| "urls": [ "https://ask.origin", "https://other.origin" ] |
| } |
| ])"); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kOtherOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(2u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| // Set the AllowDevicesForUrls policy to give permissions to kBlockedOrigin. |
| // This policy overrides the BlockedForUrls policy. |
| SetAllowDevicesForUrlsPolicy(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 4660, "product_id": 43981 }], |
| "urls": [ "https://blocked.origin" ] |
| } |
| ])"); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| // Set the AllowDevicesForUrls policy to grant permission to kOtherOrigin to |
| // access devices with kTestVendorId and any product ID. A second rule grants |
| // permission for kAskOrigin to access a different device with the same vendor |
| // ID and a third rule grants permission for kBlockedOrigin to access a |
| // different vendor ID. Only the first rule matches the device. |
| SetAllowDevicesForUrlsPolicy(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 4660 }], |
| "urls": [ "https://other.origin" ] |
| }, |
| { |
| "devices": [{ "vendor_id": 4660, "product_id": 1 }], |
| "urls": [ "https://ask.origin" ] |
| }, |
| { |
| "devices": [{ "vendor_id": 123 }], |
| "urls": [ "https://blocked.origin" ] |
| } |
| ])"); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kOtherOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(3u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| // Set the AllowDevicesForUrls policy back to the default value. |
| SetAllowDevicesForUrlsPolicy("[]"); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // Set the AllowDevicesWithHidUsagesForUrls policy to grant permissions to |
| // kAskOrigin and kOtherOrigin by matching kTestUsagePage and kTestUsage. |
| SetAllowDevicesWithHidUsagesForUrlsPolicy(R"( |
| [ |
| { |
| "usages": [{ "usage_page": 1, "usage": 5 }], |
| "urls": [ "https://ask.origin", "https://other.origin" ] |
| } |
| ])"); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kOtherOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(2u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| // Set the AllowDevicesWithHidUsagesForUrls policy to give permissions to |
| // kBlockedOrigin. This policy overrides the BlockedForUrls policy. |
| SetAllowDevicesWithHidUsagesForUrlsPolicy(R"( |
| [ |
| { |
| "usages": [{ "usage_page": 1, "usage": 5 }], |
| "urls": [ "https://blocked.origin" ] |
| } |
| ])"); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| // Set the AllowDevicesWithHidUsagesForUrls policy to grant permission to |
| // kOtherOrigin to access devices with any usage from kTestUsagePage. A |
| // second rule grants permission for kAskOrigin to access devices with a |
| // different usage from kTestUsagePage and a third rule grants permission for |
| // kBlockedOrigin to access a different usage page. Only the first rule |
| // matches the device. |
| SetAllowDevicesWithHidUsagesForUrlsPolicy(R"( |
| [ |
| { |
| "usages": [{ "usage_page": 1 }], |
| "urls": [ "https://other.origin" ] |
| }, |
| { |
| "usages": [{ "usage_page": 1, "usage": 1 }], |
| "urls": [ "https://ask.origin" ] |
| }, |
| { |
| "usages": [{ "usage_page": 123 }], |
| "urls": [ "https://blocked.origin" ] |
| } |
| ])"); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_EQ(is_affiliated(), |
| context()->HasDevicePermission(kOtherOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(3u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| // Set the AllowDevicesWithHidUsagesForUrls policy back to the default value. |
| SetAllowDevicesWithHidUsagesForUrlsPolicy("[]"); |
| EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size()); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_P(HidChooserContextAffiliatedTest, BlocklistOverridesPolicy) { |
| const auto kOrigin = url::Origin::Create(GURL("https://test.origin")); |
| |
| // Set the blocklist to deny access to devices with kTestVendorId and |
| // kTestProductId. |
| SetDynamicBlocklist("1234:abcd::::"); |
| |
| // Connect a device. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // The AllowAllDevicesForUrls policy cannot override the blocklist. |
| SetAllowAllDevicesForUrlsPolicy(R"([ "https://test.origin" ])"); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| SetAllowAllDevicesForUrlsPolicy("[]"); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // The AllowDevicesForUrls policy cannot override the blocklist. |
| SetAllowDevicesForUrlsPolicy(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 4660, "product_id": 43981 }], |
| "urls": [ "https://test.origin" ] |
| } |
| ])"); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| SetAllowDevicesForUrlsPolicy("[]"); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| |
| // The AllowDevicesWithHidUsagesForUrls policy cannot override the blocklist. |
| SetAllowDevicesWithHidUsagesForUrlsPolicy(R"( |
| [ |
| { |
| "usages": [{ "usage_page": 1, "usage": 5 }], |
| "urls": [ "https://test.origin" ] |
| } |
| ])"); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| if (is_affiliated()) { |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| } else { |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| SetAllowDevicesWithHidUsagesForUrlsPolicy("[]"); |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| } |
| |
| TEST_F(HidChooserContextTest, FidoAllowlistOverridesBlocklistDeviceIdRule) { |
| const auto kFidoAllowedOrigin = url::Origin::Create( |
| GURL("chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok")); |
| const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin")); |
| |
| // Configure the blocklist to deny access to devices with kTestVendorId and |
| // kTestProductId. |
| SetDynamicBlocklist("1234:abcd::::"); |
| |
| // Connect a FIDO device. |
| auto device = ConnectFidoDeviceBlocking(); |
| |
| // Check that the FIDO device is still blocked. Now it is blocked both for |
| // being FIDO and also for matching the device ID rule. |
| EXPECT_FALSE(context()->HasDevicePermission(kFidoAllowedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| |
| // Granting permission to the privileged origin succeeds. |
| GrantDevicePermissionBlocking(kFidoAllowedOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kFidoAllowedOrigin, *device)); |
| |
| // Granting permission to the non-privileged origin fails. |
| GrantDevicePermissionBlocking(kOtherOrigin, *device); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| } |
| |
| TEST_P(HidChooserContextAffiliatedTest, FidoAllowlistAndPolicy) { |
| const auto kFidoAndPolicyAllowedOrigin = url::Origin::Create( |
| GURL("chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok")); |
| const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin")); |
| |
| // Configure the blocklist to deny access to devices with kTestVendorId and |
| // kTestProductId. |
| SetDynamicBlocklist("1234:abcd::::"); |
| |
| SetAllowDevicesWithHidUsagesForUrlsPolicy(R"( |
| [ |
| { |
| "usages": [{ "usage_page": 61904 }], |
| "urls": [ "chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok" ] |
| } |
| ])"); |
| |
| // Connect a device matching the first policy rule. If the policy could be set |
| // then the policy-granted origin should already have permission. |
| auto device = ConnectFidoDeviceBlocking(); |
| EXPECT_EQ(is_affiliated(), context()->HasDevicePermission( |
| kFidoAndPolicyAllowedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| } |
| |
| TEST_P(HidChooserContextAffiliatedTest, |
| FidoAllowlistAndPolicyAppliesToSiblingInterface) { |
| const auto kFidoAndPolicyAllowedOrigin = url::Origin::Create( |
| GURL("chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok")); |
| const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin")); |
| |
| // Configure a policy to grant permission for a privileged origin to access a |
| // blocklisted device. |
| SetAllowDevicesForUrlsPolicy(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 6353, "product_id": 20518 }], |
| "urls": [ "chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok" ] |
| } |
| ])"); |
| |
| // Connect a device matching the policy rule. If the policy could be set then |
| // the policy-granted origin should already have permission. |
| auto device = ConnectFidoSiblingDeviceBlocking(); |
| EXPECT_EQ(is_affiliated(), context()->HasDevicePermission( |
| kFidoAndPolicyAllowedOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device)); |
| } |
| |
| // Boolean parameter means if user is affiliated on the device. Affiliated |
| // users belong to the domain that owns the device and is only meaningful |
| // on ChromeOS. |
| // |
| // The WebHidAllowDevicesForUrls, WebHidAllowDevicesWithHidUsagesForUrls, and |
| // WebHidAllowAllDevicesForUrls policies only take effect for affiliated users. |
| INSTANTIATE_TEST_SUITE_P( |
| HidChooserContextAffiliatedTestInstance, |
| HidChooserContextAffiliatedTest, |
| #if BUILDFLAG(IS_CHROMEOS) |
| testing::Values(true, false), |
| #else |
| testing::Values(true), |
| #endif |
| [](const testing::TestParamInfo<HidChooserContextAffiliatedTest::ParamType>& |
| info) { return info.param ? "affiliated" : "unaffiliated"; }); |
| |
| namespace { |
| |
| class HidChooserContextLoginScreenTest : public HidChooserContextTestBase, |
| public testing::Test { |
| public: |
| HidChooserContextLoginScreenTest() = default; |
| |
| void SetUp() override { |
| DoSetUp(/*is_affiliated=*/false, /*login_user=*/false); |
| } |
| void TearDown() override { DoTearDown(); } |
| }; |
| |
| } // namespace |
| |
| TEST_F(HidChooserContextLoginScreenTest, ApplyPolicyOnLoginScreen) { |
| const auto kOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect a device. |
| auto device = ConnectPersistentUsbDeviceBlocking(); |
| |
| // Set the DeviceLoginScreenWebHidAllowDevicesForUrls policy |
| SetAllowDevicesForUrlsOnLoginScreenPolicy(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 4660, "product_id": 43981 }], |
| "urls": [ "https://google.com" ] |
| } |
| ])"); |
| |
| // The policy has an effect only for IS_CHROMEOS build, otherwise it is |
| // ignored. |
| #if BUILDFLAG(IS_CHROMEOS) |
| EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(1u, context()->GetAllGrantedObjects().size()); |
| #else |
| EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device)); |
| EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size()); |
| EXPECT_EQ(0u, context()->GetAllGrantedObjects().size()); |
| #endif |
| } |
| |
| class HidChooserContextWebViewTest : public HidChooserContextTest { |
| public: |
| HidChooserContextWebViewTest() { |
| scoped_feature_list_.InitAndEnableFeature( |
| extensions_features::kEnableWebHidInWebView); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Verifies that a permission for WebView can be granted successfully. |
| // Checks that this permission is *not* accessible outside the embedding app. |
| TEST_F(HidChooserContextWebViewTest, GrantDevicePermissionToWebView) { |
| const auto kEmbeddingOrigin = url::Origin::Create( |
| GURL("chrome-extension://" + std::string(kTestExtensionId))); |
| const auto kWebViewOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect an ephemeral device and grant permission to the extension. |
| auto device = ConnectEphemeralDeviceBlocking(); |
| GrantDevicePermissionBlocking(kEmbeddingOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kEmbeddingOrigin, *device)); |
| EXPECT_EQ(1u, context()->GetGrantedObjects(kEmbeddingOrigin).size()); |
| |
| // Grant permission to the embedded WebView. |
| GrantDevicePermissionBlocking(kWebViewOrigin, *device, kEmbeddingOrigin); |
| EXPECT_TRUE(context()->HasDevicePermission(kEmbeddingOrigin, *device)); |
| EXPECT_TRUE(context()->HasDevicePermission(kWebViewOrigin, *device, |
| kEmbeddingOrigin)); |
| // WebView permission should not leak outside the embedding app context. |
| EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device)); |
| const auto kAnotherAppOrigin = url::Origin::Create( |
| GURL("chrome-extension://abababababababababababababababababababab")); |
| EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device, |
| kAnotherAppOrigin)); |
| } |
| |
| // Tests that revoking HID device permission from an embedding application |
| // simultaneously revokes permissions for any associated WebViews that were |
| // granted access through the embedder. |
| TEST_F(HidChooserContextWebViewTest, RevokeDevicePermissionFromEmbedder) { |
| const auto kEmbeddingOrigin = url::Origin::Create( |
| GURL("chrome-extension://" + std::string(kTestExtensionId))); |
| const auto kWebViewOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect an ephemeral device and grant permission to the embedder and |
| // WebView. |
| auto device = ConnectEphemeralDeviceBlocking(); |
| GrantDevicePermissionBlocking(kEmbeddingOrigin, *device); |
| GrantDevicePermissionBlocking(kWebViewOrigin, *device, kEmbeddingOrigin); |
| EXPECT_TRUE(context()->HasDevicePermission(kWebViewOrigin, *device, |
| kEmbeddingOrigin)); |
| |
| std::vector<std::unique_ptr<HidChooserContext::Object>> origin_objects = |
| context()->GetGrantedObjects(kEmbeddingOrigin); |
| ASSERT_EQ(1u, origin_objects.size()); |
| |
| // Revoke permission from the embedder. WebView's permission should also be |
| // revoked. |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kEmbeddingOrigin)); |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kWebViewOrigin)); |
| RevokeObjectPermissionBlocking(kEmbeddingOrigin, origin_objects[0]->value); |
| EXPECT_FALSE(context()->HasDevicePermission(kEmbeddingOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device, |
| kEmbeddingOrigin)); |
| } |
| |
| // Ensures that the revocation of HID device permission from a WebView does not |
| // interfere with the permissions held by the embedding application. |
| TEST_F(HidChooserContextWebViewTest, RevokeDevicePermissionFromWebView) { |
| const auto kEmbeddingOrigin = url::Origin::Create( |
| GURL("chrome-extension://" + std::string(kTestExtensionId))); |
| const auto kWebViewOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect an ephemeral device and grant permission to the embedder and |
| // WebView. |
| auto device = ConnectEphemeralDeviceBlocking(); |
| GrantDevicePermissionBlocking(kEmbeddingOrigin, *device); |
| GrantDevicePermissionBlocking(kWebViewOrigin, *device, kEmbeddingOrigin); |
| EXPECT_TRUE(context()->HasDevicePermission(kWebViewOrigin, *device, |
| kEmbeddingOrigin)); |
| |
| // Revoke permission from the WebView. Embedder's permission should not be |
| // affected. |
| EXPECT_CALL(permission_observer(), OnPermissionRevoked(kWebViewOrigin)); |
| ExpectObjectPermissionChanged(); |
| context()->RevokeDevicePermission(kWebViewOrigin, *device, kEmbeddingOrigin); |
| EXPECT_TRUE(context()->HasDevicePermission(kEmbeddingOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device, |
| kEmbeddingOrigin)); |
| } |
| |
| // Confirms the strict isolation of HID device permissions granted directly to a |
| // website's origin. Verifies that these permissions are not accessible within |
| // an embedded WebView, even if belonging to the same website. |
| TEST_F(HidChooserContextWebViewTest, WebsitePermissionDoesNotLeakToWebView) { |
| const auto kEmbeddingOrigin = url::Origin::Create( |
| GURL("chrome-extension://" + std::string(kTestExtensionId))); |
| const auto kWebViewOrigin = url::Origin::Create(GURL("https://google.com")); |
| |
| // Connect an ephemeral device and grant permission to the WebView's origin. |
| auto device = ConnectEphemeralDeviceBlocking(); |
| GrantDevicePermissionBlocking(kWebViewOrigin, *device); |
| EXPECT_TRUE(context()->HasDevicePermission(kWebViewOrigin, *device)); |
| EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device, |
| kEmbeddingOrigin)); |
| } |