// 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_manager.h" #include "base/memory/singleton.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "content/public/browser/notification_service.h" #include "device/usb/usb_ids.h" #include "extensions/browser/extension_host.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/notification_types.h" #include "extensions/strings/grit/extensions_strings.h" #include "ui/base/l10n/l10n_util.h" namespace extensions { using content::BrowserContext; using device::UsbDevice; using extensions::APIPermission; using extensions::Extension; using extensions::ExtensionHost; using extensions::ExtensionPrefs; namespace { // Preference keys // The device that the app has permission to access. const char kDevices[] = "devices"; // The type of device saved. const char kDeviceType[] = "type"; // Type identifier for USB devices. const char kDeviceTypeUsb[] = "usb"; // The vendor ID of the device that the app had permission to access. const char kDeviceVendorId[] = "vendor_id"; // The product ID of the device that the app had permission to access. const char kDeviceProductId[] = "product_id"; // The serial number of the device that the app has permission to access. const char kDeviceSerialNumber[] = "serial_number"; // Persists a DevicePermissionEntry in ExtensionPrefs. void SaveDevicePermissionEntry(BrowserContext* context, const std::string& extension_id, const DevicePermissionEntry& device) { ExtensionPrefs* prefs = ExtensionPrefs::Get(context); ExtensionPrefs::ScopedListUpdate update(prefs, extension_id, kDevices); base::ListValue* devices = update.Get(); if (!devices) { devices = update.Create(); } base::Value* device_entry = device.ToValue(); DCHECK(devices->Find(*device_entry) == devices->end()); devices->Append(device_entry); } // Clears all DevicePermissionEntries for the app from ExtensionPrefs. void ClearDevicePermissionEntries(ExtensionPrefs* prefs, const std::string& extension_id) { prefs->UpdateExtensionPref(extension_id, kDevices, NULL); } // Returns all DevicePermissionEntries for the app. std::vector GetDevicePermissionEntries( ExtensionPrefs* prefs, const std::string& extension_id) { std::vector result; const base::ListValue* devices = NULL; if (!prefs->ReadPrefAsList(extension_id, kDevices, &devices)) { return result; } for (base::ListValue::const_iterator it = devices->begin(); it != devices->end(); ++it) { const base::DictionaryValue* device_entry = NULL; if (!(*it)->GetAsDictionary(&device_entry)) { continue; } int vendor_id; if (!device_entry->GetIntegerWithoutPathExpansion(kDeviceVendorId, &vendor_id) || vendor_id < 0 || vendor_id > UINT16_MAX) { continue; } int product_id; if (!device_entry->GetIntegerWithoutPathExpansion(kDeviceProductId, &product_id) || product_id < 0 || product_id > UINT16_MAX) { continue; } base::string16 serial_number; if (!device_entry->GetStringWithoutPathExpansion(kDeviceSerialNumber, &serial_number)) { continue; } result.push_back( DevicePermissionEntry(vendor_id, product_id, serial_number)); } return result; } } DevicePermissionEntry::DevicePermissionEntry( uint16_t vendor_id, uint16_t product_id, const base::string16& serial_number) : vendor_id(vendor_id), product_id(product_id), serial_number(serial_number) { } base::Value* DevicePermissionEntry::ToValue() const { base::DictionaryValue* device_entry_dict = new base::DictionaryValue(); device_entry_dict->SetStringWithoutPathExpansion(kDeviceType, kDeviceTypeUsb); device_entry_dict->SetIntegerWithoutPathExpansion(kDeviceVendorId, vendor_id); device_entry_dict->SetIntegerWithoutPathExpansion(kDeviceProductId, product_id); device_entry_dict->SetStringWithoutPathExpansion(kDeviceSerialNumber, serial_number); return device_entry_dict; } DevicePermissions::~DevicePermissions() { } bool DevicePermissions::CheckUsbDevice( scoped_refptr device) const { if (ephemeral_devices_.find(device) != ephemeral_devices_.end()) { return true; } bool have_serial_number = false; base::string16 serial_number; for (const auto& entry : permission_entries_) { if (entry.vendor_id != device->vendor_id()) { continue; } if (entry.product_id != device->product_id()) { continue; } if (!have_serial_number) { if (!device->GetSerialNumber(&serial_number)) { break; } have_serial_number = true; } if (entry.serial_number != serial_number) { continue; } return true; } return false; } DevicePermissions::DevicePermissions(BrowserContext* context, const std::string& extension_id) { ExtensionPrefs* prefs = ExtensionPrefs::Get(context); permission_entries_ = GetDevicePermissionEntries(prefs, extension_id); } DevicePermissions::DevicePermissions( const std::vector& permission_entries, const std::set>& ephemeral_devices) : permission_entries_(permission_entries), ephemeral_devices_(ephemeral_devices) { } std::vector& DevicePermissions::permission_entries() { return permission_entries_; } std::set>& DevicePermissions::ephemeral_devices() { return ephemeral_devices_; } // static DevicePermissionsManager* DevicePermissionsManager::Get( BrowserContext* context) { return DevicePermissionsManagerFactory::GetForBrowserContext(context); } scoped_ptr DevicePermissionsManager::GetForExtension( const std::string& extension_id) { DCHECK(CalledOnValidThread()); DevicePermissions* device_permissions = GetOrInsert(extension_id); return make_scoped_ptr( new DevicePermissions(device_permissions->permission_entries(), device_permissions->ephemeral_devices())); } std::vector DevicePermissionsManager::GetPermissionMessageStrings( const std::string& extension_id) { DCHECK(CalledOnValidThread()); std::vector messages; DevicePermissions* device_permissions = Get(extension_id); if (!device_permissions) { return messages; } for (const auto& entry : device_permissions->permission_entries()) { const char* vendorName = device::UsbIds::GetVendorName(entry.vendor_id); const char* productName = device::UsbIds::GetProductName(entry.vendor_id, entry.product_id); if (vendorName) { if (productName) { messages.push_back(l10n_util::GetStringFUTF16( IDS_EXTENSION_PROMPT_WARNING_USB_DEVICE_SERIAL, base::UTF8ToUTF16(vendorName), base::UTF8ToUTF16(productName), entry.serial_number)); } else { messages.push_back(l10n_util::GetStringFUTF16( IDS_EXTENSION_PROMPT_WARNING_USB_DEVICE_PID_SERIAL, base::UTF8ToUTF16(vendorName), base::ASCIIToUTF16(base::StringPrintf("0x%04X", entry.product_id)), entry.serial_number)); } } else { messages.push_back(l10n_util::GetStringFUTF16( IDS_EXTENSION_PROMPT_WARNING_USB_DEVICE_VID_PID_SERIAL, base::ASCIIToUTF16(base::StringPrintf("0x%04X", entry.vendor_id)), base::ASCIIToUTF16(base::StringPrintf("0x%04X", entry.product_id)), entry.serial_number)); } } return messages; } void DevicePermissionsManager::AllowUsbDevice( const std::string& extension_id, scoped_refptr device, const base::string16& serial_number) { DCHECK(CalledOnValidThread()); DevicePermissions* device_permissions = GetOrInsert(extension_id); if (!serial_number.empty()) { for (const auto& entry : device_permissions->permission_entries()) { if (entry.vendor_id != device->vendor_id()) { continue; } if (entry.product_id != device->product_id()) { continue; } if (entry.serial_number == serial_number) { return; } } DevicePermissionEntry device_entry = DevicePermissionEntry( device->vendor_id(), device->product_id(), serial_number); device_permissions->permission_entries().push_back(device_entry); SaveDevicePermissionEntry(context_, extension_id, device_entry); } else { // Without a serial number a device cannot be reliably identified when it // is reconnected so such devices are only remembered until disconnect. // Register an observer here so that this set doesn't grow undefinitely. device_permissions->ephemeral_devices().insert(device); device->AddObserver(this); } } void DevicePermissionsManager::Clear(const std::string& extension_id) { DCHECK(CalledOnValidThread()); ClearDevicePermissionEntries(ExtensionPrefs::Get(context_), extension_id); std::map::iterator it = extension_id_to_device_permissions_.find(extension_id); if (it != extension_id_to_device_permissions_.end()) { delete it->second; extension_id_to_device_permissions_.erase(it); } } DevicePermissionsManager::DevicePermissionsManager( content::BrowserContext* context) : context_(context) { registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, content::NotificationService::AllSources()); } DevicePermissionsManager::~DevicePermissionsManager() { for (const auto& map_entry : extension_id_to_device_permissions_) { delete map_entry.second; } } DevicePermissions* DevicePermissionsManager::Get( const std::string& extension_id) const { std::map::const_iterator it = extension_id_to_device_permissions_.find(extension_id); if (it != extension_id_to_device_permissions_.end()) { return it->second; } return NULL; } DevicePermissions* DevicePermissionsManager::GetOrInsert( const std::string& extension_id) { DevicePermissions* device_permissions = Get(extension_id); if (!device_permissions) { device_permissions = new DevicePermissions(context_, extension_id); extension_id_to_device_permissions_[extension_id] = device_permissions; } return device_permissions; } void DevicePermissionsManager::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK(CalledOnValidThread()); DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, type); ExtensionHost* host = content::Details(details).ptr(); DevicePermissions* device_permissions = Get(host->extension_id()); if (device_permissions) { // When the extension is unloaded all ephemeral device permissions are // cleared. for (std::set>::iterator it = device_permissions->ephemeral_devices().begin(); it != device_permissions->ephemeral_devices().end(); ++it) { (*it)->RemoveObserver(this); } device_permissions->ephemeral_devices().clear(); } } void DevicePermissionsManager::OnDisconnect(scoped_refptr device) { for (const auto& map_entry : extension_id_to_device_permissions_) { // An ephemeral device cannot be identified if it is reconnected and so // permission to access it is cleared on disconnect. map_entry.second->ephemeral_devices().erase(device); device->RemoveObserver(this); } } // static DevicePermissionsManager* DevicePermissionsManagerFactory::GetForBrowserContext( content::BrowserContext* context) { return static_cast( GetInstance()->GetServiceForBrowserContext(context, true)); } // static DevicePermissionsManagerFactory* DevicePermissionsManagerFactory::GetInstance() { return Singleton::get(); } DevicePermissionsManagerFactory::DevicePermissionsManagerFactory() : BrowserContextKeyedServiceFactory( "DevicePermissionsManager", BrowserContextDependencyManager::GetInstance()) { } DevicePermissionsManagerFactory::~DevicePermissionsManagerFactory() { } KeyedService* DevicePermissionsManagerFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { return new DevicePermissionsManager(context); } } // namespace extensions