blob: 9706c066c5ec92a1c7aacb29fa691924b99cb228 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include "extensions/buildflags/buildflags.h"
#include "base/barrier_closure.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/test_future.h"
#include "chrome/browser/usb/usb_chooser_context.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_renderer_host.h"
#include "services/device/public/cpp/test/fake_usb_device_info.h"
#include "services/device/public/cpp/test/fake_usb_device_manager.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/usb/web_usb_service.mojom.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "base/command_line.h"
#include "base/values.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
namespace {
using ::base::test::RunOnceCallback;
using ::base::test::RunOnceClosure;
using ::base::test::TestFuture;
using ::testing::_;
using ::testing::NiceMock;
constexpr base::StringPiece kDefaultTestUrl{"https://www.google.com/"};
constexpr base::StringPiece kCrossOriginTestUrl{"https://www.chromium.org"};
#if BUILDFLAG(ENABLE_EXTENSIONS)
constexpr char kAllowlistedImprivataExtensionId[] =
"dhodapiemamlmhlhblgcibabhdkohlen";
constexpr char kAllowlistedSmartCardExtensionId[] =
"khpfeaanjngmcnplbdlpegiifgpfgdco";
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
ACTION_P2(ExpectGuidAndThen, expected_guid, callback) {
ASSERT_TRUE(arg0);
EXPECT_EQ(expected_guid, arg0->guid);
if (!callback.is_null())
callback.Run();
}
// Calls GetDevices on `service` and blocks until the GetDevicesCallback is
// invoked. Fails the test if the guids of received UsbDeviceInfos do not
// exactly match `expected_guids`.
void GetDevicesBlocking(blink::mojom::WebUsbService* service,
const std::set<std::string>& expected_guids) {
TestFuture<std::vector<device::mojom::UsbDeviceInfoPtr>> get_devices_future;
service->GetDevices(get_devices_future.GetCallback());
EXPECT_EQ(expected_guids.size(), get_devices_future.Get().size());
std::set<std::string> actual_guids;
for (const auto& device : get_devices_future.Get())
actual_guids.insert(device->guid);
EXPECT_EQ(expected_guids, actual_guids);
}
device::mojom::UsbOpenDeviceResultPtr NewUsbOpenDeviceSuccess() {
return device::mojom::UsbOpenDeviceResult::NewSuccess(
device::mojom::UsbOpenDeviceSuccess::OK);
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Creates a FakeUsbDeviceInfo with HID class code.
scoped_refptr<device::FakeUsbDeviceInfo> CreateFakeHidDeviceInfo() {
auto alternate_setting = device::mojom::UsbAlternateInterfaceInfo::New();
alternate_setting->alternate_setting = 0;
alternate_setting->class_code = device::mojom::kUsbHidClass;
auto interface = device::mojom::UsbInterfaceInfo::New();
interface->interface_number = 0;
interface->alternates.push_back(std::move(alternate_setting));
auto config = device::mojom::UsbConfigurationInfo::New();
config->configuration_value = 1;
config->interfaces.push_back(std::move(interface));
std::vector<device::mojom::UsbConfigurationInfoPtr> configs;
configs.push_back(std::move(config));
return base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF", std::move(configs));
}
// Creates a FakeUsbDeviceInfo with Smart Card class code.
scoped_refptr<device::FakeUsbDeviceInfo> CreateFakeSmartCardDeviceInfo() {
auto alternate_setting = device::mojom::UsbAlternateInterfaceInfo::New();
alternate_setting->alternate_setting = 0;
alternate_setting->class_code = device::mojom::kUsbSmartCardClass;
auto interface = device::mojom::UsbInterfaceInfo::New();
interface->interface_number = 0;
interface->alternates.push_back(std::move(alternate_setting));
auto config = device::mojom::UsbConfigurationInfo::New();
config->configuration_value = 1;
config->interfaces.push_back(std::move(interface));
std::vector<device::mojom::UsbConfigurationInfoPtr> configs;
configs.push_back(std::move(config));
return base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x4321, 0x8765, "ACME", "Frobinator", "ABCDEF", std::move(configs));
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
// A mock UsbDeviceManagerClient implementation that can be used to listen for
// USB device connection events.
class MockDeviceManagerClient
: public UsbChooserContext::UsbDeviceManagerClient {
public:
MockDeviceManagerClient() = default;
~MockDeviceManagerClient() override = default;
mojo::PendingAssociatedRemote<UsbDeviceManagerClient>
CreateInterfacePtrAndBind() {
auto client = receiver_.BindNewEndpointAndPassRemote();
receiver_.set_disconnect_handler(base::BindOnce(
&MockDeviceManagerClient::OnConnectionError, base::Unretained(this)));
return client;
}
MOCK_METHOD1(OnDeviceAdded, void(device::mojom::UsbDeviceInfoPtr));
MOCK_METHOD1(OnDeviceRemoved, void(device::mojom::UsbDeviceInfoPtr));
MOCK_METHOD0(ConnectionError, void());
void OnConnectionError() {
receiver_.reset();
ConnectionError();
}
private:
mojo::AssociatedReceiver<UsbDeviceManagerClient> receiver_{this};
};
// Tests for embedder-specific behaviors of Chrome's blink::mojom::WebUsbService
// implementation.
class ChromeUsbDelegateTest : public ChromeRenderViewHostTestHarness {
public:
ChromeUsbDelegateTest() = default;
ChromeUsbDelegateTest(const ChromeUsbDelegateTest&) = delete;
ChromeUsbDelegateTest& operator=(const ChromeUsbDelegateTest&) = delete;
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
NavigateAndCommit(GURL(kDefaultTestUrl));
}
protected:
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Creates a fake extension with the specified `extension_id` so that it can
// exercise behaviors that are only enabled for privileged extensions.
absl::optional<GURL> CreateExtensionWithId(base::StringPiece extension_id) {
auto manifest = base::Value::Dict()
.Set("name", "Fake extension")
.Set("description", "For testing.")
.Set("version", "0.1")
.Set("manifest_version", 2)
.Set("web_accessible_resources",
base::Value::List().Append("index.html"));
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder()
.SetManifest(std::move(manifest))
.SetID(std::string(extension_id))
.Build();
if (!extension) {
return absl::nullopt;
}
extensions::TestExtensionSystem* extension_system =
static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile()));
extensions::ExtensionService* extension_service =
extension_system->CreateExtensionService(
base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
extension_service->AddExtension(extension.get());
return extension->GetResourceURL("index.html");
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
void SimulateDeviceServiceCrash() { device_manager()->CloseAllBindings(); }
void ConnectToService(
mojo::PendingReceiver<blink::mojom::WebUsbService> receiver) {
// Set fake device manager for UsbChooserContext.
if (!device_manager()->IsBound()) {
mojo::PendingRemote<device::mojom::UsbDeviceManager>
pending_device_manager;
device_manager()->AddReceiver(
pending_device_manager.InitWithNewPipeAndPassReceiver());
GetChooserContext()->SetDeviceManagerForTesting(
std::move(pending_device_manager));
}
content::RenderFrameHostTester::For(main_rfh())
->CreateWebUsbServiceForTesting(std::move(receiver));
}
UsbChooserContext* GetChooserContext() {
return UsbChooserContextFactory::GetForProfile(profile());
}
device::FakeUsbDeviceManager* device_manager() {
if (!device_manager_) {
device_manager_ = std::make_unique<device::FakeUsbDeviceManager>();
}
return device_manager_.get();
}
private:
std::unique_ptr<device::FakeUsbDeviceManager> device_manager_;
};
} // namespace
TEST_F(ChromeUsbDelegateTest, NoPermissionDevice) {
const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));
auto device1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
auto device2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x1234, 0x5679, "ACME", "Frobinator+", "GHIJKL");
auto no_permission_device1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0xffff, 0x567b, "ACME", "Frobinator II", "MNOPQR");
auto no_permission_device2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0xffff, 0x567c, "ACME", "Frobinator Xtreme", "STUVWX");
// Connect two devices and grant permission for one.
auto device_info_1 = device_manager()->AddDevice(device1);
GetChooserContext()->GrantDevicePermission(origin, *device_info_1);
device_manager()->AddDevice(no_permission_device1);
// Create the WebUsbService and register a `mock_client` to receive
// notifications on device connections and disconnections. GetDevices is
// called to ensure the service is started and the client is set.
mojo::Remote<blink::mojom::WebUsbService> web_usb_service;
ConnectToService(web_usb_service.BindNewPipeAndPassReceiver());
NiceMock<MockDeviceManagerClient> mock_client;
web_usb_service->SetClient(mock_client.CreateInterfacePtrAndBind());
GetDevicesBlocking(web_usb_service.get(), {device1->guid()});
// Connect two more devices and grant permission for one.
auto device_info_2 = device_manager()->AddDevice(device2);
GetChooserContext()->GrantDevicePermission(origin, *device_info_2);
device_manager()->AddDevice(no_permission_device2);
// Disconnect all four devices. The `mock_client` should be notified only for
// the devices it has permission to access.
device_manager()->RemoveDevice(device1);
device_manager()->RemoveDevice(device2);
device_manager()->RemoveDevice(no_permission_device1);
device_manager()->RemoveDevice(no_permission_device2);
{
base::RunLoop loop;
base::RepeatingClosure barrier =
base::BarrierClosure(2, loop.QuitClosure());
testing::InSequence s;
EXPECT_CALL(mock_client, OnDeviceRemoved)
.WillOnce(ExpectGuidAndThen(device1->guid(), barrier))
.WillOnce(ExpectGuidAndThen(device2->guid(), barrier));
loop.Run();
}
// Reconnect all four devices. The `mock_client` should be notified only for
// the devices it has permission to access.
device_manager()->AddDevice(device1);
device_manager()->AddDevice(device2);
device_manager()->AddDevice(no_permission_device1);
device_manager()->AddDevice(no_permission_device2);
{
base::RunLoop loop;
base::RepeatingClosure barrier =
base::BarrierClosure(2, loop.QuitClosure());
testing::InSequence s;
EXPECT_CALL(mock_client, OnDeviceAdded)
.WillOnce(ExpectGuidAndThen(device1->guid(), barrier))
.WillOnce(ExpectGuidAndThen(device2->guid(), barrier));
loop.Run();
}
}
TEST_F(ChromeUsbDelegateTest, ReconnectDeviceManager) {
const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));
auto* context = GetChooserContext();
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
auto ephemeral_device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 0, "ACME", "Frobinator II", "");
// Connect two devices and grant permission for both. The first device is
// eligible for persistent permissions and the second device is only eligible
// for ephemeral permissions.
auto device_info = device_manager()->AddDevice(device);
context->GrantDevicePermission(origin, *device_info);
auto ephemeral_device_info = device_manager()->AddDevice(ephemeral_device);
context->GrantDevicePermission(origin, *ephemeral_device_info);
// Create the WebUsbService and register a `mock_client` to receive
// notifications on device connections and disconnections. GetDevices is
// called to ensure the service is started and the client is set.
mojo::Remote<blink::mojom::WebUsbService> web_usb_service;
ConnectToService(web_usb_service.BindNewPipeAndPassReceiver());
MockDeviceManagerClient mock_client;
web_usb_service->SetClient(mock_client.CreateInterfacePtrAndBind());
GetDevicesBlocking(web_usb_service.get(),
{device->guid(), ephemeral_device->guid()});
EXPECT_TRUE(context->HasDevicePermission(origin, *device_info));
EXPECT_TRUE(context->HasDevicePermission(origin, *ephemeral_device_info));
// Simulate a device service crash. The ephemeral permission should be
// revoked.
SimulateDeviceServiceCrash();
base::RunLoop loop;
EXPECT_CALL(mock_client, ConnectionError()).WillOnce([&]() { loop.Quit(); });
loop.Run();
EXPECT_TRUE(context->HasDevicePermission(origin, *device_info));
EXPECT_FALSE(context->HasDevicePermission(origin, *ephemeral_device_info));
// Although a new device added, as the Device manager has been destroyed, no
// event will be triggered.
auto another_device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x1234, 0x5679, "ACME", "Frobinator+", "GHIJKL");
auto another_device_info = device_manager()->AddDevice(another_device);
EXPECT_CALL(mock_client, OnDeviceAdded).Times(0);
base::RunLoop().RunUntilIdle();
// Grant permission to the new device when service is off.
context->GrantDevicePermission(origin, *another_device_info);
device_manager()->RemoveDevice(device);
EXPECT_CALL(mock_client, OnDeviceRemoved).Times(0);
base::RunLoop().RunUntilIdle();
// Reconnect the service.
web_usb_service.reset();
base::RunLoop().RunUntilIdle();
ConnectToService(web_usb_service.BindNewPipeAndPassReceiver());
web_usb_service->SetClient(mock_client.CreateInterfacePtrAndBind());
GetDevicesBlocking(web_usb_service.get(), {another_device->guid()});
EXPECT_TRUE(context->HasDevicePermission(origin, *device_info));
EXPECT_TRUE(context->HasDevicePermission(origin, *another_device_info));
EXPECT_FALSE(context->HasDevicePermission(origin, *ephemeral_device_info));
}
TEST_F(ChromeUsbDelegateTest, RevokeDevicePermission) {
const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));
auto* context = GetChooserContext();
// Connect a fake device.
auto device_info = device_manager()->CreateAndAddDevice(
0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
// Create the WebUsbService and call GetDevices to ensure it is started.
mojo::Remote<blink::mojom::WebUsbService> web_usb_service;
ConnectToService(web_usb_service.BindNewPipeAndPassReceiver());
GetDevicesBlocking(web_usb_service.get(), {});
// Grant permission to access the connected device.
context->GrantDevicePermission(origin, *device_info);
auto objects = context->GetGrantedObjects(origin);
ASSERT_EQ(1u, objects.size());
// Connect the UsbDevice.
mojo::Remote<device::mojom::UsbDevice> device;
web_usb_service->GetDevice(device_info->guid,
device.BindNewPipeAndPassReceiver());
ASSERT_TRUE(device);
// Revoke the permission. The UsbDevice should be disconnected.
base::RunLoop disconnect_loop;
device.set_disconnect_handler(disconnect_loop.QuitClosure());
context->RevokeObjectPermission(origin, objects[0]->value);
disconnect_loop.Run();
}
TEST_F(ChromeUsbDelegateTest, OpenAndCloseDevice) {
const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));
mojo::Remote<blink::mojom::WebUsbService> service;
ConnectToService(service.BindNewPipeAndPassReceiver());
// Connect a device and grant permission to access it.
auto device_info = device_manager()->CreateAndAddDevice(
0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
GetChooserContext()->GrantDevicePermission(origin, *device_info);
device::MockUsbMojoDevice mock_device;
device_manager()->SetMockForDevice(device_info->guid, &mock_device);
// Call GetDevices and expect the device to be returned.
GetDevicesBlocking(service.get(), {device_info->guid});
// Call GetDevice to get the device. The WebContents should not indicate we
// are connected to a device since the device is not opened.
mojo::Remote<device::mojom::UsbDevice> device;
service->GetDevice(device_info->guid, device.BindNewPipeAndPassReceiver());
EXPECT_FALSE(web_contents()->IsConnectedToUsbDevice());
// Open the device. Now the WebContents should indicate we are connected to a
// USB device.
EXPECT_CALL(mock_device, Open)
.WillOnce(RunOnceCallback<0>(NewUsbOpenDeviceSuccess()));
TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
device->Open(open_future.GetCallback());
EXPECT_TRUE(open_future.Get()->is_success());
EXPECT_TRUE(web_contents()->IsConnectedToUsbDevice());
// Close the device and check that the WebContents no longer indicates we are
// connected.
EXPECT_CALL(mock_device, Close).WillOnce(RunOnceClosure<0>());
base::RunLoop loop;
device->Close(loop.QuitClosure());
loop.Run();
EXPECT_FALSE(web_contents()->IsConnectedToUsbDevice());
}
TEST_F(ChromeUsbDelegateTest, OpenAndDisconnectDevice) {
const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));
mojo::Remote<blink::mojom::WebUsbService> service;
ConnectToService(service.BindNewPipeAndPassReceiver());
// Connect a device and grant permission to access it.
auto fake_device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
auto device_info = device_manager()->AddDevice(fake_device);
GetChooserContext()->GrantDevicePermission(origin, *device_info);
device::MockUsbMojoDevice mock_device;
device_manager()->SetMockForDevice(device_info->guid, &mock_device);
// Call GetDevices and expect the device to be returned.
GetDevicesBlocking(service.get(), {device_info->guid});
// Call GetDevice to get the device. The WebContents should not indicate we
// are connected to a device since the device is not opened.
mojo::Remote<device::mojom::UsbDevice> device;
service->GetDevice(device_info->guid, device.BindNewPipeAndPassReceiver());
EXPECT_FALSE(web_contents()->IsConnectedToUsbDevice());
// Open the device. Now the WebContents should indicate we are connected to a
// USB device.
EXPECT_CALL(mock_device, Open)
.WillOnce(RunOnceCallback<0>(NewUsbOpenDeviceSuccess()));
TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
device->Open(open_future.GetCallback());
EXPECT_TRUE(open_future.Get()->is_success());
EXPECT_TRUE(web_contents()->IsConnectedToUsbDevice());
// Remove the device and check that the WebContents no longer indicates we are
// connected.
EXPECT_CALL(mock_device, Close).WillOnce(RunOnceClosure<0>());
device_manager()->RemoveDevice(fake_device);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(web_contents()->IsConnectedToUsbDevice());
}
TEST_F(ChromeUsbDelegateTest, OpenAndNavigateCrossOrigin) {
const auto origin = url::Origin::Create(GURL(kDefaultTestUrl));
mojo::Remote<blink::mojom::WebUsbService> service;
ConnectToService(service.BindNewPipeAndPassReceiver());
// Connect a device and grant permission to access it.
auto fake_device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF");
auto device_info = device_manager()->AddDevice(fake_device);
GetChooserContext()->GrantDevicePermission(origin, *device_info);
device::MockUsbMojoDevice mock_device;
device_manager()->SetMockForDevice(device_info->guid, &mock_device);
// Call GetDevices and expect the device info to be returned.
GetDevicesBlocking(service.get(), {device_info->guid});
// Call GetDevice to get the device. The WebContents should not indicate we
// are connected to a device since the device is not opened.
mojo::Remote<device::mojom::UsbDevice> device;
service->GetDevice(device_info->guid, device.BindNewPipeAndPassReceiver());
EXPECT_FALSE(web_contents()->IsConnectedToUsbDevice());
// Open the device. Now the WebContents should indicate we are connected to a
// USB device.
EXPECT_CALL(mock_device, Open)
.WillOnce(RunOnceCallback<0>(NewUsbOpenDeviceSuccess()));
TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
device->Open(open_future.GetCallback());
EXPECT_TRUE(open_future.Get()->is_success());
EXPECT_TRUE(web_contents()->IsConnectedToUsbDevice());
// Perform a cross-origin navigation. The WebContents should indicate we are
// no longer connected.
EXPECT_CALL(mock_device, Close).WillOnce(RunOnceClosure<0>());
NavigateAndCommit(GURL(kCrossOriginTestUrl));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(web_contents()->IsConnectedToUsbDevice());
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(ChromeUsbDelegateTest, AllowlistedImprivataExtension) {
absl::optional<GURL> imprivata_url =
CreateExtensionWithId(kAllowlistedImprivataExtensionId);
ASSERT_TRUE(imprivata_url);
const auto imprivata_origin = url::Origin::Create(*imprivata_url);
auto* context = GetChooserContext();
auto device_info = device_manager()->AddDevice(CreateFakeHidDeviceInfo());
context->GrantDevicePermission(imprivata_origin, *device_info);
NavigateAndCommit(*imprivata_url);
mojo::Remote<blink::mojom::WebUsbService> service;
ConnectToService(service.BindNewPipeAndPassReceiver());
GetDevicesBlocking(service.get(), {device_info->guid});
mojo::Remote<device::mojom::UsbDevice> device;
service->GetDevice(device_info->guid, device.BindNewPipeAndPassReceiver());
EXPECT_FALSE(web_contents()->IsConnectedToUsbDevice());
TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
device->Open(open_future.GetCallback());
EXPECT_TRUE(open_future.Get()->is_success());
EXPECT_TRUE(web_contents()->IsConnectedToUsbDevice());
TestFuture<bool> set_configuration_future;
device->SetConfiguration(1, set_configuration_future.GetCallback());
EXPECT_TRUE(set_configuration_future.Get());
TestFuture<device::mojom::UsbClaimInterfaceResult> claim_interface_future;
device->ClaimInterface(0, claim_interface_future.GetCallback());
#if BUILDFLAG(IS_CHROMEOS)
// The allowlist only allows the interface to be claimed on Chrome OS.
EXPECT_EQ(claim_interface_future.Get(),
device::mojom::UsbClaimInterfaceResult::kSuccess);
#else
EXPECT_EQ(claim_interface_future.Get(),
device::mojom::UsbClaimInterfaceResult::kProtectedClass);
#endif // BUILDFLAG(IS_CHROMEOS)
}
TEST_F(ChromeUsbDelegateTest, AllowlistedSmartCardConnectorExtension) {
absl::optional<GURL> page_url =
CreateExtensionWithId(kAllowlistedSmartCardExtensionId);
ASSERT_TRUE(page_url);
const auto extension_origin = url::Origin::Create(*page_url);
// Add a smart card device. Also add an unrelated device, in order to test
// that access is not automatically granted to it.
auto ccid_device_info =
device_manager()->AddDevice(CreateFakeSmartCardDeviceInfo());
auto hid_device_info = device_manager()->AddDevice(CreateFakeHidDeviceInfo());
// No need to grant permission. It is granted automatically for smart
// card device.
NavigateAndCommit(*page_url);
mojo::Remote<blink::mojom::WebUsbService> service;
ConnectToService(service.BindNewPipeAndPassReceiver());
// Check that the extensions is automatically granted access to the CCID
// device and can claim its interfaces.
{
GetDevicesBlocking(service.get(), {ccid_device_info->guid});
mojo::Remote<device::mojom::UsbDevice> device;
service->GetDevice(ccid_device_info->guid,
device.BindNewPipeAndPassReceiver());
EXPECT_FALSE(web_contents()->IsConnectedToUsbDevice());
TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
device->Open(open_future.GetCallback());
EXPECT_TRUE(open_future.Get()->is_success());
EXPECT_TRUE(web_contents()->IsConnectedToUsbDevice());
TestFuture<bool> set_configuration_future;
device->SetConfiguration(1, set_configuration_future.GetCallback());
EXPECT_TRUE(set_configuration_future.Get());
TestFuture<device::mojom::UsbClaimInterfaceResult> claim_interface_future;
device->ClaimInterface(0, claim_interface_future.GetCallback());
EXPECT_EQ(claim_interface_future.Get(),
device::mojom::UsbClaimInterfaceResult::kSuccess);
}
// Check that the extension, if granted permission to a HID device can't claim
// its interfaces.
{
GetChooserContext()->GrantDevicePermission(extension_origin,
*hid_device_info);
GetDevicesBlocking(service.get(),
{ccid_device_info->guid, hid_device_info->guid});
mojo::Remote<device::mojom::UsbDevice> device;
service->GetDevice(hid_device_info->guid,
device.BindNewPipeAndPassReceiver());
TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
device->Open(open_future.GetCallback());
EXPECT_TRUE(open_future.Get()->is_success());
TestFuture<bool> set_configuration_future;
device->SetConfiguration(1, set_configuration_future.GetCallback());
EXPECT_TRUE(set_configuration_future.Get());
TestFuture<device::mojom::UsbClaimInterfaceResult> claim_interface_future;
device->ClaimInterface(0, claim_interface_future.GetCallback());
EXPECT_EQ(claim_interface_future.Get(),
device::mojom::UsbClaimInterfaceResult::kProtectedClass);
}
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)