| // Copyright 2014 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/device_permissions_prompt.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/i18n/message_formatter.h" |
| #include "base/scoped_observer.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "device/usb/public/cpp/usb_utils.h" |
| #include "device/usb/public/mojom/device_enumeration_options.mojom.h" |
| #include "device/usb/usb_device.h" |
| #include "device/usb/usb_ids.h" |
| #include "device/usb/usb_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_binding.h" |
| #include "mojo/public/cpp/bindings/interface_request.h" |
| #include "services/device/public/cpp/hid/hid_device_filter.h" |
| #include "services/device/public/cpp/hid/hid_usage_and_page.h" |
| #include "services/device/public/mojom/constants.mojom.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "chromeos/dbus/permission_broker/permission_broker_client.h" |
| #endif // defined(OS_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(base::string16()), |
| device_->product_name.value_or(base::string16()), |
| base::string16(), // Serial number is displayed separately. |
| true); |
| serial_number_ = |
| device_->serial_number ? *(device_->serial_number) : base::string16(); |
| } |
| |
| ~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, |
| const DevicePermissionsPrompt::UsbDevicesCallback& callback) |
| : Prompt(extension, context, multiple), |
| filters_(std::move(filters)), |
| callback_(callback), |
| manager_observer_(this) {} |
| |
| private: |
| ~UsbDevicePermissionsPrompt() override { manager_observer_.RemoveAll(); } |
| |
| // 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_observer_.IsObserving(device_manager)) { |
| device_manager->GetDevices( |
| base::Bind(&UsbDevicePermissionsPrompt::OnDevicesEnumerated, this)); |
| manager_observer_.Add(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); |
| callback_.Run(std::move(devices)); |
| callback_.Reset(); |
| } |
| |
| // extensions::UsbDeviceManager::Observer implementation |
| void OnDeviceAdded(const device::mojom::UsbDeviceInfo& device) override { |
| if (!device::UsbDeviceFilterMatchesAny(filters_, device)) |
| return; |
| |
| auto device_info = std::make_unique<UsbDeviceInfo>(device.Clone()); |
| #if defined(OS_CHROMEOS) |
| auto* device_manager = UsbDeviceManager::Get(browser_context()); |
| DCHECK(device_manager); |
| device_manager->CheckAccess( |
| device.guid, |
| base::BindOnce(&UsbDevicePermissionsPrompt::AddCheckedDevice, this, |
| base::Passed(&device_info))); |
| #else |
| AddCheckedDevice(std::move(device_info), true); |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| // 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(); |
| base::string16 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) { |
| OnDeviceAdded(*device); |
| } |
| } |
| |
| std::vector<UsbDeviceFilterPtr> filters_; |
| DevicePermissionsPrompt::UsbDevicesCallback callback_; |
| ScopedObserver<UsbDeviceManager, UsbDeviceManager::Observer> |
| manager_observer_; |
| }; |
| |
| 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, |
| base::string16(), // HID devices include manufacturer in product name. |
| base::UTF8ToUTF16(device_->product_name), |
| base::string16(), // 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_; |
| }; |
| |
| class HidDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt, |
| public device::mojom::HidManagerClient { |
| public: |
| HidDevicePermissionsPrompt( |
| const Extension* extension, |
| content::BrowserContext* context, |
| bool multiple, |
| const std::vector<HidDeviceFilter>& filters, |
| const DevicePermissionsPrompt::HidDevicesCallback& callback) |
| : Prompt(extension, context, multiple), |
| initialized_(false), |
| filters_(filters), |
| callback_(callback), |
| binding_(this) {} |
| |
| 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); |
| DCHECK(content::ServiceManagerConnection::GetForProcess()); |
| |
| service_manager::Connector* connector = |
| content::ServiceManagerConnection::GetForProcess()->GetConnector(); |
| connector->BindInterface(device::mojom::kServiceName, |
| mojo::MakeRequest(&hid_manager_)); |
| |
| device::mojom::HidManagerClientAssociatedPtrInfo client; |
| binding_.Bind(mojo::MakeRequest(&client)); |
| |
| hid_manager_->GetDevicesAndSetClient( |
| std::move(client), |
| 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); |
| callback_.Run(std::move(devices)); |
| callback_.Reset(); |
| } |
| |
| // device::mojom::HidManagerClient implementation: |
| void DeviceAdded(device::mojom::HidDeviceInfoPtr device) override { |
| if (HasUnprotectedCollections(*device) && |
| (filters_.empty() || HidDeviceFilter::MatchesAny(*device, filters_))) { |
| auto device_info = std::make_unique<HidDeviceInfo>(std::move(device)); |
| #if defined(OS_CHROMEOS) |
| chromeos::PermissionBrokerClient::Get()->CheckPathAccess( |
| device_info.get()->device()->device_node, |
| base::BindOnce(&HidDevicePermissionsPrompt::AddCheckedDevice, this, |
| std::move(device_info))); |
| #else |
| AddCheckedDevice(std::move(device_info), true); |
| #endif // defined(OS_CHROMEOS) |
| } |
| } |
| |
| 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(); |
| base::string16 device_name = (*it)->name(); |
| devices_.erase(it); |
| if (observer()) |
| observer()->OnDeviceRemoved(index, device_name); |
| return; |
| } |
| } |
| } |
| |
| void OnDevicesEnumerated( |
| std::vector<device::mojom::HidDeviceInfoPtr> devices) { |
| for (auto& device : devices) |
| DeviceAdded(std::move(device)); |
| } |
| |
| bool HasUnprotectedCollections(const device::mojom::HidDeviceInfo& device) { |
| for (const auto& collection : device.collections) { |
| if (!device::IsProtected(*collection->usage)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool initialized_; |
| std::vector<HidDeviceFilter> filters_; |
| device::mojom::HidManagerPtr hid_manager_; |
| DevicePermissionsPrompt::HidDevicesCallback callback_; |
| mojo::AssociatedBinding<device::mojom::HidManagerClient> binding_; |
| }; |
| |
| } // 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; |
| } |
| |
| base::string16 DevicePermissionsPrompt::Prompt::GetDeviceName( |
| size_t index) const { |
| DCHECK_LT(index, devices_.size()); |
| return devices_[index]->name(); |
| } |
| |
| base::string16 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::AddCheckedDevice( |
| std::unique_ptr<DeviceInfo> device, |
| bool allowed) { |
| if (allowed) { |
| base::string16 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, |
| const UsbDevicesCallback& callback) { |
| prompt_ = new UsbDevicePermissionsPrompt(extension, context, multiple, |
| std::move(filters), callback); |
| ShowDialog(); |
| } |
| |
| void DevicePermissionsPrompt::AskForHidDevices( |
| const Extension* extension, |
| content::BrowserContext* context, |
| bool multiple, |
| const std::vector<HidDeviceFilter>& filters, |
| const HidDevicesCallback& callback) { |
| prompt_ = new HidDevicePermissionsPrompt(extension, context, multiple, |
| filters, 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()); |
| } |
| |
| } // namespace extensions |