blob: 70f7b32b5c23b0574b63e686c36646467543a866 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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 "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.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/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/permissions/test/object_permission_context_base_mock_permission_observer.h"
#include "content/public/test/browser_task_environment.h"
#include "services/device/public/cpp/hid/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"
using ::base::test::RunClosure;
using ::testing::_;
namespace {
// Main text fixture.
class HidChooserContextTest : public testing::Test {
public:
HidChooserContextTest()
: origin_(url::Origin::Create(GURL("https://google.com"))) {}
HidChooserContextTest(const HidChooserContextTest&) = delete;
HidChooserContextTest& operator=(const HidChooserContextTest&) = delete;
~HidChooserContextTest() override = default;
const url::Origin& origin() { return origin_; }
Profile* profile() { return &profile_; }
permissions::MockPermissionObserver& permission_observer() {
return mock_permission_observer_;
}
MockHidDeviceObserver& device_observer() { return mock_device_observer_; }
HidChooserContext* GetContext() {
auto* context = HidChooserContextFactory::GetForProfile(&profile_);
if (!observers_added_) {
context->AddObserver(&mock_permission_observer_);
context->AddDeviceObserver(&mock_device_observer_);
observers_added_ = true;
}
return context;
}
void SetUp() override {
mojo::PendingRemote<device::mojom::HidManager> hid_manager;
hid_manager_.Bind(hid_manager.InitWithNewPipeAndPassReceiver());
auto* chooser_context = HidChooserContextFactory::GetForProfile(&profile_);
// Connect the HidManager and ensure we've received the initial enumeration
// before continuing.
base::RunLoop run_loop;
chooser_context->SetHidManagerForTesting(
std::move(hid_manager),
base::BindLambdaForTesting(
[&run_loop](std::vector<device::mojom::HidDeviceInfoPtr> devices) {
run_loop.Quit();
}));
run_loop.Run();
}
void TearDown() override {
if (observers_added_) {
auto* context = HidChooserContextFactory::GetForProfile(&profile_);
context->RemoveObserver(&mock_permission_observer_);
context->RemoveDeviceObserver(&mock_device_observer_);
}
}
device::mojom::HidDeviceInfoPtr ConnectEphemeralDevice() {
return hid_manager_.CreateAndAddDevice(
"physical-device-id", 0x1234, 0xabcd, "product-name",
/*serial_number=*/"", device::mojom::HidBusType::kHIDBusTypeUSB);
}
device::mojom::HidDeviceInfoPtr ConnectPersistentUsbDevice() {
return hid_manager_.CreateAndAddDevice(
"physical-device-id", 0x1234, 0xabcd, "product-name", "serial-number",
device::mojom::HidBusType::kHIDBusTypeUSB);
}
void ConnectDevice(const device::mojom::HidDeviceInfo& device) {
hid_manager_.AddDevice(device.Clone());
}
void DisconnectDevice(const device::mojom::HidDeviceInfo& device) {
hid_manager_.RemoveDevice(device.guid);
}
void UpdateDevice(const device::mojom::HidDeviceInfo& device) {
hid_manager_.ChangeDevice(device.Clone());
}
void SimulateHidManagerConnectionError() {
hid_manager_.SimulateConnectionError();
}
private:
url::Origin origin_;
device::FakeHidManager hid_manager_;
content::BrowserTaskEnvironment task_environment_;
TestingProfile profile_;
permissions::MockPermissionObserver mock_permission_observer_;
MockHidDeviceObserver mock_device_observer_;
bool observers_added_ = false;
};
} // namespace
TEST_F(HidChooserContextTest, GrantAndRevokeEphemeralDevice) {
base::RunLoop device_added_loop;
EXPECT_CALL(device_observer(), OnDeviceAdded(_))
.WillOnce(RunClosure(device_added_loop.QuitClosure()));
base::RunLoop permission_granted_loop;
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
base::make_optional(ContentSettingsType::HID_GUARD),
ContentSettingsType::HID_CHOOSER_DATA))
.WillOnce(RunClosure(permission_granted_loop.QuitClosure()))
.WillOnce([]() {
// Expect a 2nd permission change event when the permission is revoked.
});
base::RunLoop permission_revoked_loop;
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin()))
.WillOnce(RunClosure(permission_revoked_loop.QuitClosure()));
HidChooserContext* context = GetContext();
// 1. Connect a device that is only eligible for ephemeral permissions.
auto device = ConnectEphemeralDevice();
device_added_loop.Run();
EXPECT_FALSE(context->HasDevicePermission(origin(), *device));
// 2. Grant an ephemeral permission.
context->GrantDevicePermission(origin(), *device);
permission_granted_loop.Run();
EXPECT_TRUE(context->HasDevicePermission(origin(), *device));
std::vector<std::unique_ptr<HidChooserContext::Object>> origin_objects =
context->GetGrantedObjects(origin());
ASSERT_EQ(1u, origin_objects.size());
std::vector<std::unique_ptr<HidChooserContext::Object>> objects =
context->GetAllGrantedObjects();
ASSERT_EQ(1u, objects.size());
EXPECT_EQ(origin().GetURL(), objects[0]->origin);
EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
EXPECT_EQ(content_settings::SettingSource::SETTING_SOURCE_USER,
objects[0]->source);
EXPECT_FALSE(objects[0]->incognito);
// 3. Revoke the permission.
context->RevokeObjectPermission(origin(), objects[0]->value);
permission_revoked_loop.Run();
EXPECT_FALSE(context->HasDevicePermission(origin(), *device));
origin_objects = context->GetGrantedObjects(origin());
EXPECT_EQ(0u, origin_objects.size());
objects = context->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
}
TEST_F(HidChooserContextTest, GrantAndDisconnectEphemeralDevice) {
base::RunLoop device_added_loop;
EXPECT_CALL(device_observer(), OnDeviceAdded(_))
.WillOnce(RunClosure(device_added_loop.QuitClosure()));
EXPECT_CALL(device_observer(), OnDeviceRemoved(_));
base::RunLoop permission_granted_loop;
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
base::make_optional(ContentSettingsType::HID_GUARD),
ContentSettingsType::HID_CHOOSER_DATA))
.WillOnce(RunClosure(permission_granted_loop.QuitClosure()))
.WillOnce([]() {
// Expect a 2nd permission change event when the permission is revoked.
});
base::RunLoop permission_revoked_loop;
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin()))
.WillOnce(RunClosure(permission_revoked_loop.QuitClosure()));
HidChooserContext* context = GetContext();
// 1. Connect a device that is only eligible for ephemeral permissions.
auto device = ConnectEphemeralDevice();
device_added_loop.Run();
EXPECT_FALSE(context->HasDevicePermission(origin(), *device));
// 2. Grant an ephemeral permission.
context->GrantDevicePermission(origin(), *device);
permission_granted_loop.Run();
EXPECT_TRUE(context->HasDevicePermission(origin(), *device));
std::vector<std::unique_ptr<HidChooserContext::Object>> origin_objects =
context->GetGrantedObjects(origin());
ASSERT_EQ(1u, origin_objects.size());
std::vector<std::unique_ptr<HidChooserContext::Object>> objects =
context->GetAllGrantedObjects();
ASSERT_EQ(1u, objects.size());
EXPECT_EQ(origin().GetURL(), objects[0]->origin);
EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
EXPECT_EQ(content_settings::SettingSource::SETTING_SOURCE_USER,
objects[0]->source);
EXPECT_FALSE(objects[0]->incognito);
// 3. Disconnect the device. Because an ephemeral permission was granted, the
// permission should be revoked on disconnect.
DisconnectDevice(*device);
permission_revoked_loop.Run();
EXPECT_FALSE(context->HasDevicePermission(origin(), *device));
origin_objects = context->GetGrantedObjects(origin());
EXPECT_EQ(0u, origin_objects.size());
objects = context->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
}
TEST_F(HidChooserContextTest, GrantDisconnectRevokeUsbPersistentDevice) {
base::RunLoop device_added_loop;
EXPECT_CALL(device_observer(), OnDeviceAdded(_))
.WillOnce(RunClosure(device_added_loop.QuitClosure()));
base::RunLoop device_removed_loop;
EXPECT_CALL(device_observer(), OnDeviceRemoved(_))
.WillOnce(RunClosure(device_removed_loop.QuitClosure()));
base::RunLoop permission_granted_loop;
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
base::make_optional(ContentSettingsType::HID_GUARD),
ContentSettingsType::HID_CHOOSER_DATA))
.WillOnce(RunClosure(permission_granted_loop.QuitClosure()))
.WillOnce([]() {
// Expect a 2nd permission change event when the permission is revoked.
});
base::RunLoop permission_revoked_loop;
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin()))
.WillOnce(RunClosure(permission_revoked_loop.QuitClosure()));
HidChooserContext* context = GetContext();
// 1. Connect a USB device eligible for persistent permissions.
auto device = ConnectPersistentUsbDevice();
device_added_loop.Run();
EXPECT_FALSE(context->HasDevicePermission(origin(), *device));
// 2. Grant a persistent permission.
context->GrantDevicePermission(origin(), *device);
permission_granted_loop.Run();
EXPECT_TRUE(context->HasDevicePermission(origin(), *device));
std::vector<std::unique_ptr<HidChooserContext::Object>> origin_objects =
context->GetGrantedObjects(origin());
ASSERT_EQ(1u, origin_objects.size());
std::vector<std::unique_ptr<HidChooserContext::Object>> objects =
context->GetAllGrantedObjects();
ASSERT_EQ(1u, objects.size());
EXPECT_EQ(origin().GetURL(), objects[0]->origin);
EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
EXPECT_EQ(content_settings::SettingSource::SETTING_SOURCE_USER,
objects[0]->source);
EXPECT_FALSE(objects[0]->incognito);
// 3. Disconnect the device. The permission should not be revoked.
DisconnectDevice(*device);
device_removed_loop.Run();
EXPECT_TRUE(context->HasDevicePermission(origin(), *device));
// 4. Revoke the persistent permission.
context->RevokeObjectPermission(origin(), objects[0]->value);
permission_revoked_loop.Run();
EXPECT_FALSE(context->HasDevicePermission(origin(), *device));
origin_objects = context->GetGrantedObjects(origin());
EXPECT_EQ(0u, origin_objects.size());
objects = context->GetAllGrantedObjects();
EXPECT_EQ(0u, objects.size());
}
TEST_F(HidChooserContextTest, GuardPermission) {
base::RunLoop device_added_loop;
EXPECT_CALL(device_observer(), OnDeviceAdded(_))
.WillOnce(RunClosure(device_added_loop.QuitClosure()));
base::RunLoop permission_granted_loop;
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
base::make_optional(ContentSettingsType::HID_GUARD),
ContentSettingsType::HID_CHOOSER_DATA))
.WillOnce(RunClosure(permission_granted_loop.QuitClosure()));
HidChooserContext* context = GetContext();
// 1. Connect a device that is only eligible for ephemeral permissions.
auto device = ConnectEphemeralDevice();
device_added_loop.Run();
// 2. Grant an ephemeral device permission.
context->GrantDevicePermission(origin(), *device);
permission_granted_loop.Run();
EXPECT_TRUE(context->HasDevicePermission(origin(), *device));
// 3. Set the guard permission to CONTENT_SETTING_BLOCK.
auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
map->SetContentSettingDefaultScope(origin().GetURL(), origin().GetURL(),
ContentSettingsType::HID_GUARD,
CONTENT_SETTING_BLOCK);
// 4. Check that the device permission is no longer granted.
EXPECT_FALSE(context->HasDevicePermission(origin(), *device));
auto objects = context->GetGrantedObjects(origin());
EXPECT_EQ(0u, objects.size());
auto all_origin_objects = context->GetAllGrantedObjects();
EXPECT_EQ(0u, all_origin_objects.size());
}
TEST_F(HidChooserContextTest, ConnectionErrorWithEphemeralPermission) {
base::RunLoop device_added_loop;
EXPECT_CALL(device_observer(), OnDeviceAdded(_))
.WillOnce(RunClosure(device_added_loop.QuitClosure()));
EXPECT_CALL(device_observer(), OnHidManagerConnectionError());
base::RunLoop permission_granted_loop;
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
base::make_optional(ContentSettingsType::HID_GUARD),
ContentSettingsType::HID_CHOOSER_DATA))
.WillOnce(RunClosure(permission_granted_loop.QuitClosure()))
.WillOnce([]() {
// Expect a 2nd permission change event when the permission is revoked.
});
base::RunLoop permission_revoked_loop;
EXPECT_CALL(permission_observer(), OnPermissionRevoked(origin()))
.WillOnce(RunClosure(permission_revoked_loop.QuitClosure()));
HidChooserContext* context = GetContext();
// 1. Connect a device that is only eligible for persistent permissions.
auto device = ConnectEphemeralDevice();
device_added_loop.Run();
// 2. Grant an ephemeral device permission.
context->GrantDevicePermission(origin(), *device);
permission_granted_loop.Run();
// 3. Simulate a connection error. The ephemeral permission should be revoked.
SimulateHidManagerConnectionError();
permission_revoked_loop.Run();
EXPECT_FALSE(context->HasDevicePermission(origin(), *device));
}
TEST_F(HidChooserContextTest, ConnectionErrorWithPersistentPermission) {
base::RunLoop device_added_loop;
EXPECT_CALL(device_observer(), OnDeviceAdded(_))
.WillOnce(RunClosure(device_added_loop.QuitClosure()));
base::RunLoop connection_error_loop;
EXPECT_CALL(device_observer(), OnHidManagerConnectionError())
.WillOnce(RunClosure(connection_error_loop.QuitClosure()));
base::RunLoop permission_granted_loop;
EXPECT_CALL(permission_observer(),
OnObjectPermissionChanged(
base::make_optional(ContentSettingsType::HID_GUARD),
ContentSettingsType::HID_CHOOSER_DATA))
.WillOnce(RunClosure(permission_granted_loop.QuitClosure()))
.WillOnce([]() {
// Expect a 2nd permission change event when the permission is revoked.
});
HidChooserContext* context = GetContext();
// 1. Connect a device that is only eligible for persistent permissions.
auto device = ConnectPersistentUsbDevice();
device_added_loop.Run();
// 2. Grant a persistent device permission.
context->GrantDevicePermission(origin(), *device);
permission_granted_loop.Run();
// 3. Simulate a connection error. The persistent permission should not be
// affected.
SimulateHidManagerConnectionError();
connection_error_loop.Run();
EXPECT_TRUE(context->HasDevicePermission(origin(), *device));
}
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";
HidChooserContext* context = GetContext();
EXPECT_FALSE(context->GetDeviceInfo(kTestGuid));
// Connect a partially-initialized device.
base::RunLoop device_added_loop;
EXPECT_CALL(device_observer(), OnDeviceAdded).WillOnce([&](const auto& d) {
EXPECT_EQ(d.guid, kTestGuid);
EXPECT_EQ(d.collections.size(), 1u);
device_added_loop.Quit();
});
auto partial_device = CreateDeviceWithOneCollection(kTestGuid);
ConnectDevice(*partial_device);
device_added_loop.Run();
auto* device_info = context->GetDeviceInfo(kTestGuid);
ASSERT_TRUE(device_info);
EXPECT_EQ(device_info->collections.size(), 1u);
// Update the device to add another collection.
base::RunLoop device_changed_loop;
EXPECT_CALL(device_observer(), OnDeviceChanged).WillOnce([&](const auto& d) {
EXPECT_EQ(d.guid, kTestGuid);
EXPECT_EQ(d.collections.size(), 2u);
device_changed_loop.Quit();
});
auto complete_device = CreateDeviceWithTwoCollections(kTestGuid);
UpdateDevice(*complete_device);
device_changed_loop.Run();
device_info = context->GetDeviceInfo(kTestGuid);
ASSERT_TRUE(device_info);
EXPECT_EQ(device_info->collections.size(), 2u);
// Disconnect the device.
base::RunLoop device_removed_loop;
EXPECT_CALL(device_observer(), OnDeviceRemoved).WillOnce([&](const auto& d) {
EXPECT_EQ(d.guid, kTestGuid);
EXPECT_EQ(d.collections.size(), 2u);
device_removed_loop.Quit();
});
DisconnectDevice(*complete_device);
device_removed_loop.Run();
ASSERT_FALSE(context->GetDeviceInfo(kTestGuid));
}