blob: 163535a89915e3e42a4996eeadcfb20ea165973b [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/device_permissions_prompt.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/message_formatter.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/device_service.h"
#include "extensions/browser/api/device_permissions_manager.h"
#include "extensions/browser/api/usb/usb_device_manager.h"
#include "extensions/common/extension.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/cpp/hid/hid_device_filter.h"
#include "services/device/public/cpp/hid/hid_report_utils.h"
#include "services/device/public/cpp/usb/usb_utils.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/dbus/permission_broker/permission_broker_client.h" // nogncheck
#endif // BUILDFLAG(IS_CHROMEOS)
using device::HidDeviceFilter;
using device::mojom::UsbDeviceFilterPtr;
namespace extensions {
namespace {
class UsbDeviceInfo : public DevicePermissionsPrompt::Prompt::DeviceInfo {
public:
explicit UsbDeviceInfo(device::mojom::UsbDeviceInfoPtr device)
: device_(std::move(device)) {
name_ = DevicePermissionsManager::GetPermissionMessage(
device_->vendor_id, device_->product_id,
device_->manufacturer_name.value_or(std::u16string()),
device_->product_name.value_or(std::u16string()),
std::u16string(), // Serial number is displayed separately.
true);
serial_number_ =
device_->serial_number ? *(device_->serial_number) : std::u16string();
}
~UsbDeviceInfo() override {}
device::mojom::UsbDeviceInfoPtr& device() { return device_; }
private:
device::mojom::UsbDeviceInfoPtr device_;
};
class UsbDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt,
public UsbDeviceManager::Observer {
public:
UsbDevicePermissionsPrompt(
const Extension* extension,
content::BrowserContext* context,
bool multiple,
std::vector<UsbDeviceFilterPtr> filters,
DevicePermissionsPrompt::UsbDevicesCallback callback)
: Prompt(extension, context, multiple),
filters_(std::move(filters)),
callback_(std::move(callback)) {}
private:
~UsbDevicePermissionsPrompt() override { manager_observation_.Reset(); }
// DevicePermissionsPrompt::Prompt implementation:
void SetObserver(
DevicePermissionsPrompt::Prompt::Observer* observer) override {
DevicePermissionsPrompt::Prompt::SetObserver(observer);
if (observer) {
auto* device_manager = UsbDeviceManager::Get(browser_context());
if (device_manager &&
!manager_observation_.IsObservingSource(device_manager)) {
device_manager->GetDevices(base::BindOnce(
&UsbDevicePermissionsPrompt::OnDevicesEnumerated, this));
manager_observation_.Observe(device_manager);
}
}
}
void Dismissed() override {
DevicePermissionsManager* permissions_manager =
DevicePermissionsManager::Get(browser_context());
std::vector<device::mojom::UsbDeviceInfoPtr> devices;
for (const auto& device : devices_) {
if (device->granted()) {
UsbDeviceInfo* usb_device = static_cast<UsbDeviceInfo*>(device.get());
if (permissions_manager) {
DCHECK(usb_device->device());
permissions_manager->AllowUsbDevice(extension()->id(),
*usb_device->device());
}
devices.push_back(std::move(usb_device->device()));
}
}
DCHECK(multiple() || devices.size() <= 1);
std::move(callback_).Run(std::move(devices));
}
// extensions::UsbDeviceManager::Observer implementation
void OnDeviceAdded(const device::mojom::UsbDeviceInfo& device) override {
MaybeAddDevice(device, /*initial_enumeration=*/false);
}
// extensions::UsbDeviceManager::Observer implementation
void OnDeviceRemoved(const device::mojom::UsbDeviceInfo& device) override {
for (auto it = devices_.begin(); it != devices_.end(); ++it) {
UsbDeviceInfo* entry = static_cast<UsbDeviceInfo*>((*it).get());
if (entry->device()->guid == device.guid) {
size_t index = it - devices_.begin();
std::u16string device_name = (*it)->name();
devices_.erase(it);
if (observer())
observer()->OnDeviceRemoved(index, device_name);
return;
}
}
}
void OnDevicesEnumerated(
std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
for (const auto& device : devices) {
MaybeAddDevice(*device, /*initial_enumeration=*/true);
}
}
void MaybeAddDevice(const device::mojom::UsbDeviceInfo& device,
bool initial_enumeration) {
if (!device::UsbDeviceFilterMatchesAny(filters_, device))
return;
if (initial_enumeration)
remaining_initial_devices_++;
auto device_info = std::make_unique<UsbDeviceInfo>(device.Clone());
#if BUILDFLAG(IS_CHROMEOS)
auto* device_manager = UsbDeviceManager::Get(browser_context());
DCHECK(device_manager);
device_manager->CheckAccess(
device.guid,
base::BindOnce(&UsbDevicePermissionsPrompt::AddCheckedDevice, this,
std::move(device_info), initial_enumeration));
#else
AddCheckedDevice(std::move(device_info), initial_enumeration,
/*allowed=*/true);
#endif // BUILDFLAG(IS_CHROMEOS)
}
void AddCheckedDevice(std::unique_ptr<UsbDeviceInfo> device_info,
bool initial_enumeration,
bool allowed) {
if (allowed)
AddDevice(std::move(device_info));
if (initial_enumeration && --remaining_initial_devices_ == 0 &&
observer()) {
observer()->OnDevicesInitialized();
}
}
std::vector<UsbDeviceFilterPtr> filters_;
size_t remaining_initial_devices_ = 0;
DevicePermissionsPrompt::UsbDevicesCallback callback_;
base::ScopedObservation<UsbDeviceManager, UsbDeviceManager::Observer>
manager_observation_{this};
};
class HidDeviceInfo : public DevicePermissionsPrompt::Prompt::DeviceInfo {
public:
explicit HidDeviceInfo(device::mojom::HidDeviceInfoPtr device)
: device_(std::move(device)) {
name_ = DevicePermissionsManager::GetPermissionMessage(
device_->vendor_id, device_->product_id,
std::u16string(), // HID devices include manufacturer in product name.
base::UTF8ToUTF16(device_->product_name),
std::u16string(), // Serial number is displayed separately.
false);
serial_number_ = base::UTF8ToUTF16(device_->serial_number);
}
~HidDeviceInfo() override {}
device::mojom::HidDeviceInfoPtr& device() { return device_; }
private:
device::mojom::HidDeviceInfoPtr device_;
};
DevicePermissionsPrompt::HidManagerBinder& GetHidManagerBinderOverride() {
static base::NoDestructor<DevicePermissionsPrompt::HidManagerBinder> binder;
return *binder;
}
class HidDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt,
public device::mojom::HidManagerClient {
public:
HidDevicePermissionsPrompt(
const Extension* extension,
content::BrowserContext* context,
bool multiple,
const std::vector<HidDeviceFilter>& filters,
DevicePermissionsPrompt::HidDevicesCallback callback)
: Prompt(extension, context, multiple),
initialized_(false),
filters_(filters),
callback_(std::move(callback)) {}
private:
~HidDevicePermissionsPrompt() override {}
// DevicePermissionsPrompt::Prompt implementation:
void SetObserver(
DevicePermissionsPrompt::Prompt::Observer* observer) override {
DevicePermissionsPrompt::Prompt::SetObserver(observer);
if (observer)
LazyInitialize();
}
void LazyInitialize() {
if (initialized_) {
return;
}
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto receiver = hid_manager_.BindNewPipeAndPassReceiver();
const auto& binder = GetHidManagerBinderOverride();
if (binder)
binder.Run(std::move(receiver));
else
content::GetDeviceService().BindHidManager(std::move(receiver));
hid_manager_->GetDevicesAndSetClient(
receiver_.BindNewEndpointAndPassRemote(),
base::BindOnce(&HidDevicePermissionsPrompt::OnDevicesEnumerated, this));
initialized_ = true;
}
void Dismissed() override {
DevicePermissionsManager* permissions_manager =
DevicePermissionsManager::Get(browser_context());
std::vector<device::mojom::HidDeviceInfoPtr> devices;
for (const auto& device : devices_) {
if (device->granted()) {
HidDeviceInfo* hid_device = static_cast<HidDeviceInfo*>(device.get());
if (permissions_manager) {
DCHECK(hid_device->device());
permissions_manager->AllowHidDevice(extension()->id(),
*(hid_device->device()));
}
devices.push_back(std::move(hid_device->device()));
}
}
DCHECK(multiple() || devices.size() <= 1);
std::move(callback_).Run(std::move(devices));
}
// device::mojom::HidManagerClient implementation:
void DeviceAdded(device::mojom::HidDeviceInfoPtr device) override {
MaybeAddDevice(std::move(device), /*initial_enumeration=*/false);
}
void DeviceRemoved(device::mojom::HidDeviceInfoPtr device) override {
for (auto it = devices_.begin(); it != devices_.end(); ++it) {
HidDeviceInfo* entry = static_cast<HidDeviceInfo*>((*it).get());
if (entry->device()->guid == device->guid) {
size_t index = it - devices_.begin();
std::u16string device_name = (*it)->name();
devices_.erase(it);
if (observer())
observer()->OnDeviceRemoved(index, device_name);
return;
}
}
}
void DeviceChanged(device::mojom::HidDeviceInfoPtr device) override {
for (const auto& device_info : devices_) {
auto* hid_device_info =
reinterpret_cast<HidDeviceInfo*>(device_info.get());
if (hid_device_info->device()->guid == device->guid) {
// The device is already present in |devices_|. Update its device
// information.
hid_device_info->device() = std::move(device);
return;
}
}
// The device was not previously added to |devices_|, possibly due to
// filters or protected collections. Try adding it again.
MaybeAddDevice(std::move(device), /*initial_enumeration=*/false);
}
void OnDevicesEnumerated(
std::vector<device::mojom::HidDeviceInfoPtr> devices) {
for (auto& device : devices)
MaybeAddDevice(std::move(device), /*initial_enumeration=*/true);
}
// Returns true if `device` contains at least one unprotected report in any
// collection.
bool HasUnprotectedReports(const device::mojom::HidDeviceInfo& device) {
return base::ranges::any_of(device.collections, [](const auto& collection) {
return device::CollectionHasUnprotectedReports(*collection);
});
}
void MaybeAddDevice(device::mojom::HidDeviceInfoPtr device,
bool initial_enumeration) {
if (!HasUnprotectedReports(*device) ||
(!filters_.empty() &&
!HidDeviceFilter::MatchesAny(*device, filters_))) {
return;
}
if (initial_enumeration)
remaining_initial_devices_++;
auto device_info = std::make_unique<HidDeviceInfo>(std::move(device));
// TODO(huangs): Enable this for Lacros (crbug.com/1217124).
#if BUILDFLAG(IS_CHROMEOS_ASH)
chromeos::PermissionBrokerClient::Get()->CheckPathAccess(
device_info.get()->device()->device_node,
base::BindOnce(&HidDevicePermissionsPrompt::AddCheckedDevice, this,
std::move(device_info), initial_enumeration));
#else
AddCheckedDevice(std::move(device_info), initial_enumeration,
/*allowed=*/true);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
void AddCheckedDevice(std::unique_ptr<HidDeviceInfo> device_info,
bool initial_enumeration,
bool allowed) {
if (allowed)
AddDevice(std::move(device_info));
if (initial_enumeration && --remaining_initial_devices_ == 0 &&
observer()) {
observer()->OnDevicesInitialized();
}
}
bool initialized_;
std::vector<HidDeviceFilter> filters_;
size_t remaining_initial_devices_ = 0;
mojo::Remote<device::mojom::HidManager> hid_manager_;
DevicePermissionsPrompt::HidDevicesCallback callback_;
mojo::AssociatedReceiver<device::mojom::HidManagerClient> receiver_{this};
};
} // namespace
DevicePermissionsPrompt::Prompt::DeviceInfo::DeviceInfo() {
}
DevicePermissionsPrompt::Prompt::DeviceInfo::~DeviceInfo() {
}
DevicePermissionsPrompt::Prompt::Observer::~Observer() {
}
DevicePermissionsPrompt::Prompt::Prompt(const Extension* extension,
content::BrowserContext* context,
bool multiple)
: extension_(extension), browser_context_(context), multiple_(multiple) {
}
void DevicePermissionsPrompt::Prompt::SetObserver(Observer* observer) {
observer_ = observer;
}
std::u16string DevicePermissionsPrompt::Prompt::GetDeviceName(
size_t index) const {
DCHECK_LT(index, devices_.size());
return devices_[index]->name();
}
std::u16string DevicePermissionsPrompt::Prompt::GetDeviceSerialNumber(
size_t index) const {
DCHECK_LT(index, devices_.size());
return devices_[index]->serial_number();
}
void DevicePermissionsPrompt::Prompt::GrantDevicePermission(size_t index) {
DCHECK_LT(index, devices_.size());
devices_[index]->set_granted();
}
DevicePermissionsPrompt::Prompt::~Prompt() {
}
void DevicePermissionsPrompt::Prompt::AddDevice(
std::unique_ptr<DeviceInfo> device) {
std::u16string device_name = device->name();
devices_.push_back(std::move(device));
if (observer_)
observer_->OnDeviceAdded(devices_.size() - 1, device_name);
}
DevicePermissionsPrompt::DevicePermissionsPrompt(
content::WebContents* web_contents)
: web_contents_(web_contents) {
}
DevicePermissionsPrompt::~DevicePermissionsPrompt() {
}
void DevicePermissionsPrompt::AskForUsbDevices(
const Extension* extension,
content::BrowserContext* context,
bool multiple,
std::vector<UsbDeviceFilterPtr> filters,
UsbDevicesCallback callback) {
prompt_ = base::MakeRefCounted<UsbDevicePermissionsPrompt>(
extension, context, multiple, std::move(filters), std::move(callback));
ShowDialog();
}
void DevicePermissionsPrompt::AskForHidDevices(
const Extension* extension,
content::BrowserContext* context,
bool multiple,
const std::vector<HidDeviceFilter>& filters,
HidDevicesCallback callback) {
prompt_ = base::MakeRefCounted<HidDevicePermissionsPrompt>(
extension, context, multiple, filters, std::move(callback));
ShowDialog();
}
// static
scoped_refptr<DevicePermissionsPrompt::Prompt>
DevicePermissionsPrompt::CreateHidPromptForTest(const Extension* extension,
bool multiple) {
return base::MakeRefCounted<HidDevicePermissionsPrompt>(
extension, nullptr, multiple, std::vector<HidDeviceFilter>(),
base::DoNothing());
}
// static
scoped_refptr<DevicePermissionsPrompt::Prompt>
DevicePermissionsPrompt::CreateUsbPromptForTest(const Extension* extension,
bool multiple) {
return base::MakeRefCounted<UsbDevicePermissionsPrompt>(
extension, nullptr, multiple, std::vector<UsbDeviceFilterPtr>(),
base::DoNothing());
}
// static
void DevicePermissionsPrompt::OverrideHidManagerBinderForTesting(
HidManagerBinder binder) {
GetHidManagerBinderOverride() = std::move(binder);
}
} // namespace extensions