[go: nahoru, domu]

blob: 5751ff73b11b1e41011bfa2eb4d285dc604842ef [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 "extensions/browser/api/usb/usb_device_manager.h"
#include <functional>
#include <memory>
#include <utility>
#include "base/lazy_instance.h"
#include "base/strings/utf_string_conversions.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/extensions_api_client.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/common/api/usb.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/usb_device_permission.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
namespace usb = extensions::api::usb;
using content::BrowserThread;
namespace extensions {
namespace {
constexpr int kUsbClassMassStorage = 0x08;
bool IsMassStorageInterface(const device::mojom::UsbInterfaceInfo& interface) {
for (auto& alternate : interface.alternates) {
if (alternate->class_code == kUsbClassMassStorage)
return true;
}
return false;
}
bool ShouldExposeDevice(const device::mojom::UsbDeviceInfo& device_info) {
// ChromeOS always allows mass storage devices to be detached, but chrome.usb
// only gets access when the specific vid/pid is listed in device policy.
// This means that reloading policy can change the result of this function.
for (auto& configuration : device_info.configurations) {
for (auto& interface : configuration->interfaces) {
if (!IsMassStorageInterface(*interface))
return true;
}
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (ExtensionsAPIClient::Get()->ShouldAllowDetachingUsb(
device_info.vendor_id, device_info.product_id)) {
return true;
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
return false;
}
// Returns true if the given extension has permission to receive events
// regarding this device.
bool WillDispatchDeviceEvent(const device::mojom::UsbDeviceInfo& device_info,
content::BrowserContext* browser_context,
Feature::Context target_context,
const Extension* extension,
Event* event,
const base::DictionaryValue* listener_filter) {
// Check install-time and optional permissions.
std::unique_ptr<UsbDevicePermission::CheckParam> param =
UsbDevicePermission::CheckParam::ForUsbDevice(extension, device_info);
if (extension->permissions_data()->CheckAPIPermissionWithParam(
APIPermission::kUsbDevice, param.get())) {
return true;
}
// Check permissions granted through chrome.usb.getUserSelectedDevices.
DevicePermissions* device_permissions =
DevicePermissionsManager::Get(browser_context)
->GetForExtension(extension->id());
if (device_permissions->FindUsbDeviceEntry(device_info).get()) {
return true;
}
return false;
}
base::LazyInstance<BrowserContextKeyedAPIFactory<UsbDeviceManager>>::Leaky
g_event_router_factory = LAZY_INSTANCE_INITIALIZER;
} // namespace
// static
UsbDeviceManager* UsbDeviceManager::Get(
content::BrowserContext* browser_context) {
return BrowserContextKeyedAPIFactory<UsbDeviceManager>::Get(browser_context);
}
// static
BrowserContextKeyedAPIFactory<UsbDeviceManager>*
UsbDeviceManager::GetFactoryInstance() {
return g_event_router_factory.Pointer();
}
void UsbDeviceManager::Observer::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {}
void UsbDeviceManager::Observer::OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) {}
void UsbDeviceManager::Observer::OnDeviceManagerConnectionError() {}
UsbDeviceManager::UsbDeviceManager(content::BrowserContext* browser_context)
: browser_context_(browser_context) {
EventRouter* event_router = EventRouter::Get(browser_context_);
if (event_router) {
event_router->RegisterObserver(this, usb::OnDeviceAdded::kEventName);
event_router->RegisterObserver(this, usb::OnDeviceRemoved::kEventName);
}
}
UsbDeviceManager::~UsbDeviceManager() {}
void UsbDeviceManager::AddObserver(Observer* observer) {
EnsureConnectionWithDeviceManager();
observer_list_.AddObserver(observer);
}
void UsbDeviceManager::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
int UsbDeviceManager::GetIdFromGuid(const std::string& guid) {
auto iter = guid_to_id_map_.find(guid);
if (iter == guid_to_id_map_.end()) {
auto result = guid_to_id_map_.insert(std::make_pair(guid, next_id_++));
DCHECK(result.second);
iter = result.first;
id_to_guid_map_.insert(std::make_pair(iter->second, guid));
}
return iter->second;
}
bool UsbDeviceManager::GetGuidFromId(int id, std::string* guid) {
auto iter = id_to_guid_map_.find(id);
if (iter == id_to_guid_map_.end())
return false;
*guid = iter->second;
return true;
}
void UsbDeviceManager::GetApiDevice(
const device::mojom::UsbDeviceInfo& device_in,
api::usb::Device* device_out) {
device_out->device = GetIdFromGuid(device_in.guid);
device_out->vendor_id = device_in.vendor_id;
device_out->product_id = device_in.product_id;
device_out->version = device_in.device_version_major << 8 |
device_in.device_version_minor << 4 |
device_in.device_version_subminor;
device_out->product_name =
base::UTF16ToUTF8(device_in.product_name.value_or(std::u16string()));
device_out->manufacturer_name =
base::UTF16ToUTF8(device_in.manufacturer_name.value_or(std::u16string()));
device_out->serial_number =
base::UTF16ToUTF8(device_in.serial_number.value_or(std::u16string()));
}
void UsbDeviceManager::GetDevices(
device::mojom::UsbDeviceManager::GetDevicesCallback callback) {
if (!is_initialized_) {
pending_get_devices_requests_.push(std::move(callback));
EnsureConnectionWithDeviceManager();
return;
}
std::vector<device::mojom::UsbDeviceInfoPtr> device_list;
for (const auto& pair : devices_)
device_list.push_back(pair.second->Clone());
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(device_list)));
}
void UsbDeviceManager::GetDevice(
const std::string& guid,
mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver) {
EnsureConnectionWithDeviceManager();
device_manager_->GetDevice(guid, /*blocked_interface_classes=*/{},
std::move(device_receiver),
/*device_client=*/mojo::NullRemote());
}
const device::mojom::UsbDeviceInfo* UsbDeviceManager::GetDeviceInfo(
const std::string& guid) {
DCHECK(is_initialized_);
auto it = devices_.find(guid);
return it == devices_.end() ? nullptr : it->second.get();
}
bool UsbDeviceManager::UpdateActiveConfig(const std::string& guid,
uint8_t config_value) {
DCHECK(is_initialized_);
auto it = devices_.find(guid);
if (it == devices_.end()) {
return false;
}
it->second->active_configuration = config_value;
return true;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
void UsbDeviceManager::CheckAccess(
const std::string& guid,
device::mojom::UsbDeviceManager::CheckAccessCallback callback) {
EnsureConnectionWithDeviceManager();
device_manager_->CheckAccess(guid, std::move(callback));
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
void UsbDeviceManager::EnsureConnectionWithDeviceManager() {
if (device_manager_)
return;
// Receive mojo::Remote<UsbDeviceManager> from DeviceService.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::GetDeviceService().BindUsbDeviceManager(
device_manager_.BindNewPipeAndPassReceiver());
SetUpDeviceManagerConnection();
}
void UsbDeviceManager::SetDeviceManagerForTesting(
mojo::PendingRemote<device::mojom::UsbDeviceManager> fake_device_manager) {
DCHECK(!device_manager_);
DCHECK(fake_device_manager);
device_manager_.Bind(std::move(fake_device_manager));
SetUpDeviceManagerConnection();
}
void UsbDeviceManager::Shutdown() {
EventRouter* event_router = EventRouter::Get(browser_context_);
if (event_router) {
event_router->UnregisterObserver(this);
}
}
void UsbDeviceManager::OnListenerAdded(const EventListenerInfo& details) {
EnsureConnectionWithDeviceManager();
}
void UsbDeviceManager::OnDeviceAdded(
device::mojom::UsbDeviceInfoPtr device_info) {
DCHECK(device_info);
// Update the device list.
DCHECK(!base::Contains(devices_, device_info->guid));
if (!ShouldExposeDevice(*device_info))
return;
std::string guid = device_info->guid;
auto result =
devices_.insert(std::make_pair(std::move(guid), std::move(device_info)));
const device::mojom::UsbDeviceInfo& stored_info = *result.first->second;
DispatchEvent(usb::OnDeviceAdded::kEventName, stored_info);
// Notify all observers.
for (auto& observer : observer_list_)
observer.OnDeviceAdded(stored_info);
}
void UsbDeviceManager::OnDeviceRemoved(
device::mojom::UsbDeviceInfoPtr device_info) {
DCHECK(device_info);
// Handle if ShouldExposeDevice() returned false when the device was added.
if (!base::Contains(devices_, device_info->guid))
return;
// Update the device list.
devices_.erase(device_info->guid);
DispatchEvent(usb::OnDeviceRemoved::kEventName, *device_info);
// Notify all observers for OnDeviceRemoved event.
for (auto& observer : observer_list_)
observer.OnDeviceRemoved(*device_info);
auto iter = guid_to_id_map_.find(device_info->guid);
if (iter != guid_to_id_map_.end()) {
int id = iter->second;
guid_to_id_map_.erase(iter);
id_to_guid_map_.erase(id);
}
// Remove permission entry for ephemeral USB device.
DevicePermissionsManager* permissions_manager =
DevicePermissionsManager::Get(browser_context_);
DCHECK(permissions_manager);
permissions_manager->RemoveEntryByDeviceGUID(DevicePermissionEntry::Type::USB,
device_info->guid);
}
void UsbDeviceManager::SetUpDeviceManagerConnection() {
DCHECK(device_manager_);
device_manager_.set_disconnect_handler(
base::BindOnce(&UsbDeviceManager::OnDeviceManagerConnectionError,
base::Unretained(this)));
// Listen for added/removed device events.
DCHECK(!client_receiver_.is_bound());
device_manager_->EnumerateDevicesAndSetClient(
client_receiver_.BindNewEndpointAndPassRemote(),
base::BindOnce(&UsbDeviceManager::InitDeviceList,
weak_factory_.GetWeakPtr()));
}
void UsbDeviceManager::InitDeviceList(
std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
for (auto& device_info : devices) {
DCHECK(device_info);
if (!ShouldExposeDevice(*device_info))
continue;
std::string guid = device_info->guid;
devices_.insert(std::make_pair(guid, std::move(device_info)));
}
is_initialized_ = true;
while (!pending_get_devices_requests_.empty()) {
std::vector<device::mojom::UsbDeviceInfoPtr> device_list;
for (const auto& entry : devices_) {
device_list.push_back(entry.second->Clone());
}
std::move(pending_get_devices_requests_.front())
.Run(std::move(device_list));
pending_get_devices_requests_.pop();
}
}
void UsbDeviceManager::OnDeviceManagerConnectionError() {
device_manager_.reset();
client_receiver_.reset();
devices_.clear();
is_initialized_ = false;
guid_to_id_map_.clear();
id_to_guid_map_.clear();
// Notify all observers.
for (auto& observer : observer_list_)
observer.OnDeviceManagerConnectionError();
}
void UsbDeviceManager::DispatchEvent(
const std::string& event_name,
const device::mojom::UsbDeviceInfo& device_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
EventRouter* event_router = EventRouter::Get(browser_context_);
if (event_router) {
usb::Device device_obj;
GetApiDevice(device_info, &device_obj);
std::unique_ptr<Event> event;
if (event_name == usb::OnDeviceAdded::kEventName) {
event.reset(new Event(events::USB_ON_DEVICE_ADDED,
usb::OnDeviceAdded::kEventName,
usb::OnDeviceAdded::Create(device_obj)));
} else {
DCHECK(event_name == usb::OnDeviceRemoved::kEventName);
event.reset(new Event(events::USB_ON_DEVICE_REMOVED,
usb::OnDeviceRemoved::kEventName,
usb::OnDeviceRemoved::Create(device_obj)));
}
event->will_dispatch_callback =
base::BindRepeating(&WillDispatchDeviceEvent, std::cref(device_info));
event_router->BroadcastEvent(std::move(event));
}
}
template <>
void BrowserContextKeyedAPIFactory<
UsbDeviceManager>::DeclareFactoryDependencies() {
DependsOn(DevicePermissionsManagerFactory::GetInstance());
DependsOn(EventRouterFactory::GetInstance());
}
} // namespace extensions