| // 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 <utility> |
| |
| #include "base/bind.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 "device/core/device_client.h" |
| #include "device/hid/hid_device_filter.h" |
| #include "device/hid/hid_device_info.h" |
| #include "device/hid/hid_service.h" |
| #include "device/usb/usb_device.h" |
| #include "device/usb/usb_device_filter.h" |
| #include "device/usb/usb_ids.h" |
| #include "device/usb/usb_service.h" |
| #include "extensions/browser/api/device_permissions_manager.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/strings/grit/extensions_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/permission_broker_client.h" |
| #include "device/hid/hid_device_info_linux.h" |
| #endif // defined(OS_CHROMEOS) |
| |
| using device::HidDeviceFilter; |
| using device::HidService; |
| using device::UsbDevice; |
| using device::UsbDeviceFilter; |
| using device::UsbService; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| void NoopHidCallback(const std::vector<scoped_refptr<device::HidDeviceInfo>>&) { |
| } |
| |
| void NoopUsbCallback(const std::vector<scoped_refptr<device::UsbDevice>>&) {} |
| |
| class UsbDeviceInfo : public DevicePermissionsPrompt::Prompt::DeviceInfo { |
| public: |
| UsbDeviceInfo(scoped_refptr<UsbDevice> device) : device_(device) { |
| name_ = DevicePermissionsManager::GetPermissionMessage( |
| device->vendor_id(), device->product_id(), |
| device->manufacturer_string(), device->product_string(), |
| base::string16(), // Serial number is displayed separately. |
| true); |
| serial_number_ = device->serial_number(); |
| } |
| |
| ~UsbDeviceInfo() override {} |
| |
| const scoped_refptr<UsbDevice>& device() const { return device_; } |
| |
| private: |
| // TODO(reillyg): Convert this to a weak reference when UsbDevice has a |
| // connected flag. |
| scoped_refptr<UsbDevice> device_; |
| }; |
| |
| class UsbDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt, |
| public device::UsbService::Observer { |
| public: |
| UsbDevicePermissionsPrompt( |
| const Extension* extension, |
| content::BrowserContext* context, |
| bool multiple, |
| const std::vector<UsbDeviceFilter>& filters, |
| const DevicePermissionsPrompt::UsbDevicesCallback& callback) |
| : Prompt(extension, context, multiple), |
| filters_(filters), |
| callback_(callback), |
| service_observer_(this) {} |
| |
| private: |
| ~UsbDevicePermissionsPrompt() override {} |
| |
| // DevicePermissionsPrompt::Prompt implementation: |
| void SetObserver( |
| DevicePermissionsPrompt::Prompt::Observer* observer) override { |
| DevicePermissionsPrompt::Prompt::SetObserver(observer); |
| |
| if (observer) { |
| UsbService* service = device::DeviceClient::Get()->GetUsbService(); |
| if (service && !service_observer_.IsObserving(service)) { |
| service->GetDevices( |
| base::Bind(&UsbDevicePermissionsPrompt::OnDevicesEnumerated, this)); |
| service_observer_.Add(service); |
| } |
| } |
| } |
| |
| base::string16 GetHeading() const override { |
| return l10n_util::GetSingleOrMultipleStringUTF16( |
| IDS_USB_DEVICE_PERMISSIONS_PROMPT_TITLE, multiple()); |
| } |
| |
| void Dismissed() override { |
| DevicePermissionsManager* permissions_manager = |
| DevicePermissionsManager::Get(browser_context()); |
| std::vector<scoped_refptr<UsbDevice>> devices; |
| for (const auto& device : devices_) { |
| if (device->granted()) { |
| const UsbDeviceInfo* usb_device = |
| static_cast<const UsbDeviceInfo*>(device.get()); |
| devices.push_back(usb_device->device()); |
| if (permissions_manager) { |
| permissions_manager->AllowUsbDevice(extension()->id(), |
| usb_device->device()); |
| } |
| } |
| } |
| DCHECK(multiple() || devices.size() <= 1); |
| callback_.Run(devices); |
| callback_.Reset(); |
| } |
| |
| // device::UsbService::Observer implementation: |
| void OnDeviceAdded(scoped_refptr<UsbDevice> device) override { |
| if (!UsbDeviceFilter::MatchesAny(device, filters_)) |
| return; |
| |
| std::unique_ptr<DeviceInfo> device_info(new UsbDeviceInfo(device)); |
| device->CheckUsbAccess( |
| base::Bind(&UsbDevicePermissionsPrompt::AddCheckedDevice, this, |
| base::Passed(&device_info))); |
| } |
| |
| void OnDeviceRemoved(scoped_refptr<UsbDevice> device) override { |
| for (auto it = devices_.begin(); it != devices_.end(); ++it) { |
| const UsbDeviceInfo* entry = |
| static_cast<const UsbDeviceInfo*>((*it).get()); |
| if (entry->device() == device) { |
| devices_.erase(it); |
| if (observer()) { |
| observer()->OnDevicesChanged(); |
| } |
| return; |
| } |
| } |
| } |
| |
| void OnDevicesEnumerated( |
| const std::vector<scoped_refptr<UsbDevice>>& devices) { |
| for (const auto& device : devices) { |
| OnDeviceAdded(device); |
| } |
| } |
| |
| std::vector<UsbDeviceFilter> filters_; |
| DevicePermissionsPrompt::UsbDevicesCallback callback_; |
| ScopedObserver<UsbService, UsbService::Observer> service_observer_; |
| }; |
| |
| class HidDeviceInfo : public DevicePermissionsPrompt::Prompt::DeviceInfo { |
| public: |
| HidDeviceInfo(scoped_refptr<device::HidDeviceInfo> device) : device_(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 {} |
| |
| const scoped_refptr<device::HidDeviceInfo>& device() const { return device_; } |
| |
| private: |
| scoped_refptr<device::HidDeviceInfo> device_; |
| }; |
| |
| class HidDevicePermissionsPrompt : public DevicePermissionsPrompt::Prompt, |
| public device::HidService::Observer { |
| public: |
| HidDevicePermissionsPrompt( |
| const Extension* extension, |
| content::BrowserContext* context, |
| bool multiple, |
| const std::vector<HidDeviceFilter>& filters, |
| const DevicePermissionsPrompt::HidDevicesCallback& callback) |
| : Prompt(extension, context, multiple), |
| filters_(filters), |
| callback_(callback), |
| service_observer_(this) {} |
| |
| private: |
| ~HidDevicePermissionsPrompt() override {} |
| |
| // DevicePermissionsPrompt::Prompt implementation: |
| void SetObserver( |
| DevicePermissionsPrompt::Prompt::Observer* observer) override { |
| DevicePermissionsPrompt::Prompt::SetObserver(observer); |
| |
| if (observer) { |
| HidService* service = device::DeviceClient::Get()->GetHidService(); |
| if (service && !service_observer_.IsObserving(service)) { |
| service->GetDevices( |
| base::Bind(&HidDevicePermissionsPrompt::OnDevicesEnumerated, this)); |
| service_observer_.Add(service); |
| } |
| } |
| } |
| |
| base::string16 GetHeading() const override { |
| return l10n_util::GetSingleOrMultipleStringUTF16( |
| IDS_HID_DEVICE_PERMISSIONS_PROMPT_TITLE, multiple()); |
| } |
| |
| void Dismissed() override { |
| DevicePermissionsManager* permissions_manager = |
| DevicePermissionsManager::Get(browser_context()); |
| std::vector<scoped_refptr<device::HidDeviceInfo>> devices; |
| for (const auto& device : devices_) { |
| if (device->granted()) { |
| const HidDeviceInfo* hid_device = |
| static_cast<const HidDeviceInfo*>(device.get()); |
| devices.push_back(hid_device->device()); |
| if (permissions_manager) { |
| permissions_manager->AllowHidDevice(extension()->id(), |
| hid_device->device()); |
| } |
| } |
| } |
| DCHECK(multiple() || devices.size() <= 1); |
| callback_.Run(devices); |
| callback_.Reset(); |
| } |
| |
| // device::HidService::Observer implementation: |
| void OnDeviceAdded(scoped_refptr<device::HidDeviceInfo> device) override { |
| if (HasUnprotectedCollections(device) && |
| (filters_.empty() || HidDeviceFilter::MatchesAny(device, filters_))) { |
| std::unique_ptr<DeviceInfo> device_info(new HidDeviceInfo(device)); |
| #if defined(OS_CHROMEOS) |
| chromeos::PermissionBrokerClient* client = |
| chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient(); |
| DCHECK(client) << "Could not get permission broker client."; |
| device::HidDeviceInfoLinux* linux_device_info = |
| static_cast<device::HidDeviceInfoLinux*>(device.get()); |
| client->CheckPathAccess( |
| linux_device_info->device_node(), |
| base::Bind(&HidDevicePermissionsPrompt::AddCheckedDevice, this, |
| base::Passed(&device_info))); |
| #else |
| AddCheckedDevice(std::move(device_info), true); |
| #endif // defined(OS_CHROMEOS) |
| } |
| } |
| |
| void OnDeviceRemoved(scoped_refptr<device::HidDeviceInfo> device) override { |
| for (auto it = devices_.begin(); it != devices_.end(); ++it) { |
| const HidDeviceInfo* entry = |
| static_cast<const HidDeviceInfo*>((*it).get()); |
| if (entry->device() == device) { |
| devices_.erase(it); |
| if (observer()) { |
| observer()->OnDevicesChanged(); |
| } |
| return; |
| } |
| } |
| } |
| |
| void OnDevicesEnumerated( |
| const std::vector<scoped_refptr<device::HidDeviceInfo>>& devices) { |
| for (const auto& device : devices) { |
| OnDeviceAdded(device); |
| } |
| } |
| |
| bool HasUnprotectedCollections(scoped_refptr<device::HidDeviceInfo> device) { |
| for (const auto& collection : device->collections()) { |
| if (!collection.usage.IsProtected()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::vector<HidDeviceFilter> filters_; |
| DevicePermissionsPrompt::HidDevicesCallback callback_; |
| ScopedObserver<HidService, HidService::Observer> service_observer_; |
| }; |
| |
| } // 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::GetPromptMessage() const { |
| return base::i18n::MessageFormatter::FormatWithNumberedArgs( |
| l10n_util::GetStringUTF16(IDS_DEVICE_PERMISSIONS_PROMPT), |
| multiple_ ? "multiple" : "single", extension_->name()); |
| } |
| |
| 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) { |
| devices_.push_back(std::move(device)); |
| if (observer_) { |
| observer_->OnDevicesChanged(); |
| } |
| } |
| } |
| |
| DevicePermissionsPrompt::DevicePermissionsPrompt( |
| content::WebContents* web_contents) |
| : web_contents_(web_contents) { |
| } |
| |
| DevicePermissionsPrompt::~DevicePermissionsPrompt() { |
| } |
| |
| void DevicePermissionsPrompt::AskForUsbDevices( |
| const Extension* extension, |
| content::BrowserContext* context, |
| bool multiple, |
| const std::vector<UsbDeviceFilter>& filters, |
| const UsbDevicesCallback& callback) { |
| prompt_ = new UsbDevicePermissionsPrompt(extension, context, multiple, |
| 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 make_scoped_refptr(new HidDevicePermissionsPrompt( |
| extension, nullptr, multiple, std::vector<HidDeviceFilter>(), |
| base::Bind(&NoopHidCallback))); |
| } |
| |
| // static |
| scoped_refptr<DevicePermissionsPrompt::Prompt> |
| DevicePermissionsPrompt::CreateUsbPromptForTest(const Extension* extension, |
| bool multiple) { |
| return make_scoped_refptr(new UsbDevicePermissionsPrompt( |
| extension, nullptr, multiple, std::vector<UsbDeviceFilter>(), |
| base::Bind(&NoopUsbCallback))); |
| } |
| |
| } // namespace extensions |