diff options
-rw-r--r-- | apps/BUILD.gn | 5 | ||||
-rw-r--r-- | apps/DEPS | 1 | ||||
-rw-r--r-- | apps/apps.gypi | 4 | ||||
-rw-r--r-- | apps/saved_devices_service.cc | 316 | ||||
-rw-r--r-- | apps/saved_devices_service.h | 123 | ||||
-rw-r--r-- | apps/saved_devices_service_factory.cc | 39 | ||||
-rw-r--r-- | apps/saved_devices_service_factory.h | 35 | ||||
-rw-r--r-- | chrome/browser/extensions/api/device_permissions_manager_unittest.cc (renamed from apps/saved_devices_service_unittest.cc) | 96 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 2 | ||||
-rw-r--r-- | extensions/browser/BUILD.gn | 2 | ||||
-rw-r--r-- | extensions/browser/DEPS | 1 | ||||
-rw-r--r-- | extensions/browser/api/device_permissions_manager.cc | 384 | ||||
-rw-r--r-- | extensions/browser/api/device_permissions_manager.h | 151 | ||||
-rw-r--r-- | extensions/browser/api/usb/DEPS | 1 | ||||
-rw-r--r-- | extensions/browser/api/usb_private/DEPS | 1 | ||||
-rw-r--r-- | extensions/extensions.gyp | 2 | ||||
-rw-r--r-- | extensions/extensions_strings.grd | 9 |
17 files changed, 596 insertions, 576 deletions
diff --git a/apps/BUILD.gn b/apps/BUILD.gn index 4070818..d46feaa 100644 --- a/apps/BUILD.gn +++ b/apps/BUILD.gn @@ -26,10 +26,6 @@ static_library("apps") { "launcher.cc", "launcher.h", "metrics_names.h", - "saved_devices_service.cc", - "saved_devices_service.h", - "saved_devices_service_factory.cc", - "saved_devices_service_factory.h", "saved_files_service.cc", "saved_files_service.h", "saved_files_service_factory.cc", @@ -45,7 +41,6 @@ static_library("apps") { "//chrome/browser/extensions", "//chrome/common/extensions/api:api", "//components/web_modal", - "//device/usb", "//skia", ] @@ -9,7 +9,6 @@ include_rules = [ "+components/sessions", "+components/user_manager", "+components/web_modal", - "+device/usb", "+extensions", "+net/base", "+skia/ext", diff --git a/apps/apps.gypi b/apps/apps.gypi index 1b464ae..cb8478d 100644 --- a/apps/apps.gypi +++ b/apps/apps.gypi @@ -45,10 +45,6 @@ 'launcher.cc', 'launcher.h', 'metrics_names.h', - 'saved_devices_service.cc', - 'saved_devices_service.h', - 'saved_devices_service_factory.cc', - 'saved_devices_service_factory.h', 'saved_files_service.cc', 'saved_files_service.h', 'saved_files_service_factory.cc', diff --git a/apps/saved_devices_service.cc b/apps/saved_devices_service.cc deleted file mode 100644 index d7e894c..0000000 --- a/apps/saved_devices_service.cc +++ /dev/null @@ -1,316 +0,0 @@ -// 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 "apps/saved_devices_service.h" - -#include <set> -#include <vector> - -#include "apps/saved_devices_service_factory.h" -#include "base/basictypes.h" -#include "base/values.h" -#include "chrome/browser/chrome_notification_types.h" -#include "chrome/browser/profiles/profile.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/notification_service.h" -#include "device/usb/usb_device.h" -#include "device/usb/usb_device_handle.h" -#include "extensions/browser/extension_host.h" -#include "extensions/browser/extension_prefs.h" -#include "extensions/browser/extension_system.h" -#include "extensions/browser/extension_util.h" -#include "extensions/browser/notification_types.h" -#include "extensions/common/permissions/api_permission.h" -#include "extensions/common/permissions/permission_set.h" -#include "extensions/common/permissions/permissions_data.h" - -namespace apps { - -using device::UsbDevice; -using device::UsbDeviceHandle; -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 SavedDeviceEntry in ExtensionPrefs. -void AddSavedDeviceEntry(Profile* profile, - const std::string& extension_id, - const SavedDeviceEntry& device) { - ExtensionPrefs* prefs = ExtensionPrefs::Get(profile); - 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 SavedDeviceEntry for the app from ExtensionPrefs. -void ClearSavedDeviceEntries(ExtensionPrefs* prefs, - const std::string& extension_id) { - prefs->UpdateExtensionPref(extension_id, kDevices, NULL); -} - -// Returns all SavedDeviceEntries for the app. -std::vector<SavedDeviceEntry> GetSavedDeviceEntries( - ExtensionPrefs* prefs, - const std::string& extension_id) { - std::vector<SavedDeviceEntry> 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(SavedDeviceEntry(vendor_id, product_id, serial_number)); - } - return result; -} - -} // namespace - -SavedDeviceEntry::SavedDeviceEntry(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* SavedDeviceEntry::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; -} - -bool SavedDevicesService::SavedDevices::IsRegistered( - scoped_refptr<UsbDevice> device) const { - if (ephemeral_devices_.find(device) != ephemeral_devices_.end()) { - return true; - } - - bool have_serial_number = false; - base::string16 serial_number; - for (std::vector<SavedDeviceEntry>::const_iterator it = - persistent_devices_.begin(); - it != persistent_devices_.end(); - ++it) { - if (it->vendor_id != device->vendor_id()) { - continue; - } - if (it->product_id != device->product_id()) { - continue; - } - if (!have_serial_number) { - if (!device->GetSerialNumber(&serial_number)) { - break; - } - have_serial_number = true; - } - if (it->serial_number != serial_number) { - continue; - } - return true; - } - return false; -} - -void SavedDevicesService::SavedDevices::RegisterDevice( - scoped_refptr<device::UsbDevice> device, - base::string16* serial_number) { - if (serial_number) { - for (std::vector<SavedDeviceEntry>::const_iterator it = - persistent_devices_.begin(); - it != persistent_devices_.end(); - ++it) { - if (it->vendor_id != device->vendor_id()) { - continue; - } - if (it->product_id != device->product_id()) { - continue; - } - if (it->serial_number == *serial_number) { - return; - } - } - SavedDeviceEntry device_entry = SavedDeviceEntry( - device->vendor_id(), device->product_id(), *serial_number); - persistent_devices_.push_back(device_entry); - - content::BrowserThread::PostTask( - content::BrowserThread::UI, - FROM_HERE, - base::Bind(AddSavedDeviceEntry, profile_, 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. - ephemeral_devices_.insert(device); - device->AddObserver(this); - } -} - -SavedDevicesService::SavedDevices::SavedDevices(Profile* profile, - const std::string& extension_id) - : profile_(profile), extension_id_(extension_id) { - ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); - persistent_devices_ = GetSavedDeviceEntries(prefs, extension_id_); -} - -SavedDevicesService::SavedDevices::~SavedDevices() { - // Only ephemeral devices have an observer registered. - for (std::set<scoped_refptr<UsbDevice> >::iterator it = - ephemeral_devices_.begin(); - it != ephemeral_devices_.end(); - ++it) { - (*it)->RemoveObserver(this); - } -} - -void SavedDevicesService::SavedDevices::OnDisconnect( - scoped_refptr<UsbDevice> device) { - // Permission for an ephemeral device lasts only as long as the device is - // plugged in. - ephemeral_devices_.erase(device); - device->RemoveObserver(this); -} - -SavedDevicesService::SavedDevicesService(Profile* profile) : profile_(profile) { - registrar_.Add(this, - extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, - content::NotificationService::AllSources()); - registrar_.Add(this, - chrome::NOTIFICATION_APP_TERMINATING, - content::NotificationService::AllSources()); -} - -SavedDevicesService::~SavedDevicesService() { - for (std::map<std::string, SavedDevices*>::iterator it = - extension_id_to_saved_devices_.begin(); - it != extension_id_to_saved_devices_.end(); - ++it) { - delete it->second; - } -} - -// static -SavedDevicesService* SavedDevicesService::Get(Profile* profile) { - return SavedDevicesServiceFactory::GetForProfile(profile); -} - -SavedDevicesService::SavedDevices* SavedDevicesService::GetOrInsert( - const std::string& extension_id) { - SavedDevices* saved_devices = Get(extension_id); - if (saved_devices) { - return saved_devices; - } - - saved_devices = new SavedDevices(profile_, extension_id); - extension_id_to_saved_devices_[extension_id] = saved_devices; - return saved_devices; -} - -std::vector<SavedDeviceEntry> SavedDevicesService::GetAllDevices( - const std::string& extension_id) const { - ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); - return GetSavedDeviceEntries(prefs, extension_id); -} - -void SavedDevicesService::Clear(const std::string& extension_id) { - DCHECK(thread_checker_.CalledOnValidThread()); - ClearSavedDeviceEntries(ExtensionPrefs::Get(profile_), extension_id); - std::map<std::string, SavedDevices*>::iterator it = - extension_id_to_saved_devices_.find(extension_id); - if (it != extension_id_to_saved_devices_.end()) { - delete it->second; - extension_id_to_saved_devices_.erase(it); - } -} - -void SavedDevicesService::Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) { - switch (type) { - case extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED: { - Clear(content::Details<ExtensionHost>(details)->extension_id()); - break; - } - - case chrome::NOTIFICATION_APP_TERMINATING: { - // Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular - // as all extension hosts will be destroyed as a result of shutdown. - registrar_.RemoveAll(); - break; - } - } -} - -SavedDevicesService::SavedDevices* SavedDevicesService::Get( - const std::string& extension_id) const { - DCHECK(thread_checker_.CalledOnValidThread()); - std::map<std::string, SavedDevices*>::const_iterator it = - extension_id_to_saved_devices_.find(extension_id); - if (it != extension_id_to_saved_devices_.end()) { - return it->second; - } - - return NULL; -} - -} // namespace apps diff --git a/apps/saved_devices_service.h b/apps/saved_devices_service.h deleted file mode 100644 index 52a1874..0000000 --- a/apps/saved_devices_service.h +++ /dev/null @@ -1,123 +0,0 @@ -// 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. - -#ifndef APPS_SAVED_DEVICES_SERVICE_H_ -#define APPS_SAVED_DEVICES_SERVICE_H_ - -#include <map> -#include <set> -#include <string> -#include <vector> - -#include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/stl_util.h" -#include "base/strings/string16.h" -#include "base/threading/thread_checker.h" -#include "components/keyed_service/core/keyed_service.h" -#include "content/public/browser/notification_observer.h" -#include "content/public/browser/notification_registrar.h" -#include "device/usb/usb_device.h" - -class Profile; - -namespace base { -class Value; -} - -namespace extensions { -class Extension; -} - -namespace apps { - -// Represents a device that a user has given an app permission to access. It -// will be persisted to disk (in the Preferences file) and so should remain -// serializable. -struct SavedDeviceEntry { - SavedDeviceEntry(uint16_t vendor_id, - uint16_t product_id, - const base::string16& serial_number); - - base::Value* ToValue() const; - - // The vendor ID of this device. - uint16_t vendor_id; - - // The product ID of this device. - uint16_t product_id; - - // The serial number (possibly alphanumeric) of this device. - base::string16 serial_number; -}; - -// Tracks the devices that apps have retained access to both while running and -// when suspended. -class SavedDevicesService : public KeyedService, - public content::NotificationObserver { - public: - // Tracks the devices that a particular extension has retained access to. - // Unlike SavedDevicesService the functions of this class can be called from - // the FILE thread. - class SavedDevices : device::UsbDevice::Observer { - public: - bool IsRegistered(scoped_refptr<device::UsbDevice> device) const; - void RegisterDevice(scoped_refptr<device::UsbDevice> device, - /* optional */ base::string16* serial_number); - - private: - friend class SavedDevicesService; - - SavedDevices(Profile* profile, const std::string& extension_id); - virtual ~SavedDevices(); - - // device::UsbDevice::Observer - virtual void OnDisconnect(scoped_refptr<device::UsbDevice> device) OVERRIDE; - - Profile* profile_; - const std::string extension_id_; - - // Devices with serial numbers are written to the prefs file. - std::vector<SavedDeviceEntry> persistent_devices_; - // Other devices are ephemeral devices and cleared when the extension host - // is destroyed. - std::set<scoped_refptr<device::UsbDevice> > ephemeral_devices_; - - DISALLOW_COPY_AND_ASSIGN(SavedDevices); - }; - - explicit SavedDevicesService(Profile* profile); - virtual ~SavedDevicesService(); - - static SavedDevicesService* Get(Profile* profile); - - // Returns the SavedDevices for |extension_id|, creating it if necessary. - SavedDevices* GetOrInsert(const std::string& extension_id); - - std::vector<SavedDeviceEntry> GetAllDevices( - const std::string& extension_id) const; - - // Clears the SavedDevices for |extension_id|. - void Clear(const std::string& extension_id); - - private: - // content::NotificationObserver. - virtual void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) OVERRIDE; - - // Returns the SavedDevices for |extension_id| or NULL if one does not exist. - SavedDevices* Get(const std::string& extension_id) const; - - std::map<std::string, SavedDevices*> extension_id_to_saved_devices_; - Profile* profile_; - content::NotificationRegistrar registrar_; - base::ThreadChecker thread_checker_; - - DISALLOW_COPY_AND_ASSIGN(SavedDevicesService); -}; - -} // namespace apps - -#endif // APPS_SAVED_DEVICES_SERVICE_H_ diff --git a/apps/saved_devices_service_factory.cc b/apps/saved_devices_service_factory.cc deleted file mode 100644 index 1153eac..0000000 --- a/apps/saved_devices_service_factory.cc +++ /dev/null @@ -1,39 +0,0 @@ -// 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 "apps/saved_devices_service_factory.h" - -#include "apps/saved_devices_service.h" -#include "chrome/browser/profiles/profile.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" - -namespace apps { - -// static -SavedDevicesService* SavedDevicesServiceFactory::GetForProfile( - Profile* profile) { - return static_cast<SavedDevicesService*>( - GetInstance()->GetServiceForBrowserContext(profile, true)); -} - -// static -SavedDevicesServiceFactory* SavedDevicesServiceFactory::GetInstance() { - return Singleton<SavedDevicesServiceFactory>::get(); -} - -SavedDevicesServiceFactory::SavedDevicesServiceFactory() - : BrowserContextKeyedServiceFactory( - "SavedDevicesService", - BrowserContextDependencyManager::GetInstance()) { -} - -SavedDevicesServiceFactory::~SavedDevicesServiceFactory() { -} - -KeyedService* SavedDevicesServiceFactory::BuildServiceInstanceFor( - content::BrowserContext* profile) const { - return new SavedDevicesService(static_cast<Profile*>(profile)); -} - -} // namespace apps diff --git a/apps/saved_devices_service_factory.h b/apps/saved_devices_service_factory.h deleted file mode 100644 index d1b39a1..0000000 --- a/apps/saved_devices_service_factory.h +++ /dev/null @@ -1,35 +0,0 @@ -// 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. - -#ifndef APPS_SAVED_DEVICES_SERVICE_FACTORY_H_ -#define APPS_SAVED_DEVICES_SERVICE_FACTORY_H_ - -#include "base/memory/singleton.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" - -class Profile; - -namespace apps { - -class SavedDevicesService; - -// BrowserContextKeyedServiceFactory for SavedDevicesService. -class SavedDevicesServiceFactory : public BrowserContextKeyedServiceFactory { - public: - static SavedDevicesService* GetForProfile(Profile* profile); - - static SavedDevicesServiceFactory* GetInstance(); - - private: - SavedDevicesServiceFactory(); - virtual ~SavedDevicesServiceFactory(); - friend struct DefaultSingletonTraits<SavedDevicesServiceFactory>; - - virtual KeyedService* BuildServiceInstanceFor( - content::BrowserContext* profile) const OVERRIDE; -}; - -} // namespace apps - -#endif // APPS_SAVED_DEVICES_SERVICE_FACTORY_H_ diff --git a/apps/saved_devices_service_unittest.cc b/chrome/browser/extensions/api/device_permissions_manager_unittest.cc index b48e8a6..3990933 100644 --- a/apps/saved_devices_service_unittest.cc +++ b/chrome/browser/extensions/api/device_permissions_manager_unittest.cc @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "apps/saved_devices_service.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/test/values_test_util.h" @@ -10,19 +9,18 @@ #include "chrome/test/base/testing_profile.h" #include "device/usb/usb_device.h" #include "device/usb/usb_device_handle.h" +#include "extensions/browser/api/device_permissions_manager.h" #include "extensions/browser/extension_prefs.h" #include "extensions/common/extension.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -namespace apps { +namespace extensions { namespace { using device::UsbDevice; using device::UsbDeviceHandle; -using device::UsbEndpointDirection; -using device::UsbTransferCallback; using testing::Return; class MockUsbDevice : public UsbDevice { @@ -39,12 +37,12 @@ class MockUsbDevice : public UsbDevice { MOCK_METHOD1(GetManufacturer, bool(base::string16*)); MOCK_METHOD1(GetProduct, bool(base::string16*)); - bool GetSerialNumber(base::string16* serial) OVERRIDE { + virtual bool GetSerialNumber(base::string16* serial_number) OVERRIDE { if (serial_number_.empty()) { return false; } - *serial = base::UTF8ToUTF16(serial_number_); + *serial_number = base::UTF8ToUTF16(serial_number_); return true; } @@ -55,9 +53,10 @@ class MockUsbDevice : public UsbDevice { const std::string serial_number_; }; -} -class SavedDevicesServiceTest : public testing::Test { +} // namespace + +class DevicePermissionsManagerTest : public testing::Test { protected: virtual void SetUp() OVERRIDE { testing::Test::SetUp(); @@ -73,7 +72,6 @@ class SavedDevicesServiceTest : public testing::Test { " \"usb\"" " ]" "}")); - service_ = SavedDevicesService::Get(env_.profile()); device0 = new MockUsbDevice("ABCDE", 0); device1 = new MockUsbDevice("", 1); device2 = new MockUsbDevice("12345", 2); @@ -82,53 +80,49 @@ class SavedDevicesServiceTest : public testing::Test { extensions::TestExtensionEnvironment env_; const extensions::Extension* extension_; - SavedDevicesService* service_; scoped_refptr<MockUsbDevice> device0; scoped_refptr<MockUsbDevice> device1; scoped_refptr<MockUsbDevice> device2; scoped_refptr<MockUsbDevice> device3; }; -TEST_F(SavedDevicesServiceTest, RegisterDevices) { - SavedDevicesService::SavedDevices* saved_devices = - service_->GetOrInsert(extension_->id()); - - base::string16 serial_number(base::ASCIIToUTF16("ABCDE")); - saved_devices->RegisterDevice(device0, &serial_number); - saved_devices->RegisterDevice(device1, NULL); - - // This is necessary as writing out registered devices happens in a task on - // the UI thread. - base::RunLoop run_loop; - run_loop.RunUntilIdle(); - - ASSERT_TRUE(saved_devices->IsRegistered(device0)); - ASSERT_TRUE(saved_devices->IsRegistered(device1)); - ASSERT_FALSE(saved_devices->IsRegistered(device2)); - ASSERT_FALSE(saved_devices->IsRegistered(device3)); - - std::vector<SavedDeviceEntry> device_entries = - service_->GetAllDevices(extension_->id()); - ASSERT_EQ(1U, device_entries.size()); - ASSERT_EQ(base::ASCIIToUTF16("ABCDE"), device_entries[0].serial_number); +TEST_F(DevicePermissionsManagerTest, RegisterDevices) { + DevicePermissionsManager* manager = + DevicePermissionsManager::Get(env_.profile()); + manager->AllowUsbDevice( + extension_->id(), device0, base::ASCIIToUTF16("ABCDE")); + manager->AllowUsbDevice(extension_->id(), device1, base::string16()); + + scoped_ptr<DevicePermissions> device_permissions = + manager->GetForExtension(extension_->id()); + ASSERT_TRUE(device_permissions->CheckUsbDevice(device0)); + ASSERT_TRUE(device_permissions->CheckUsbDevice(device1)); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device2)); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device3)); + + std::vector<base::string16> device_messages = + manager->GetPermissionMessageStrings(extension_->id()); + ASSERT_EQ(1U, device_messages.size()); + ASSERT_NE(device_messages[0].find(base::ASCIIToUTF16("ABCDE")), + base::string16::npos); device1->NotifyDisconnect(); - ASSERT_TRUE(saved_devices->IsRegistered(device0)); - ASSERT_FALSE(saved_devices->IsRegistered(device1)); - ASSERT_FALSE(saved_devices->IsRegistered(device2)); - ASSERT_FALSE(saved_devices->IsRegistered(device3)); + device_permissions = manager->GetForExtension(extension_->id()); + ASSERT_TRUE(device_permissions->CheckUsbDevice(device0)); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device1)); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device2)); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device3)); - service_->Clear(extension_->id()); + manager->Clear(extension_->id()); - // App is normally restarted, clearing its reference to the SavedDevices. - saved_devices = service_->GetOrInsert(extension_->id()); - ASSERT_FALSE(saved_devices->IsRegistered(device0)); - device_entries = service_->GetAllDevices(extension_->id()); - ASSERT_EQ(0U, device_entries.size()); + device_permissions = manager->GetForExtension(extension_->id()); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device0)); + device_messages = manager->GetPermissionMessageStrings(extension_->id()); + ASSERT_EQ(0U, device_messages.size()); } -TEST_F(SavedDevicesServiceTest, LoadPrefs) { +TEST_F(DevicePermissionsManagerTest, LoadPrefs) { scoped_ptr<base::Value> prefs_value = base::test::ParseJson( "[" " {" @@ -141,12 +135,14 @@ TEST_F(SavedDevicesServiceTest, LoadPrefs) { env_.GetExtensionPrefs()->UpdateExtensionPref( extension_->id(), "devices", prefs_value.release()); - SavedDevicesService::SavedDevices* saved_devices = - service_->GetOrInsert(extension_->id()); - ASSERT_TRUE(saved_devices->IsRegistered(device0)); - ASSERT_FALSE(saved_devices->IsRegistered(device1)); - ASSERT_FALSE(saved_devices->IsRegistered(device2)); - ASSERT_FALSE(saved_devices->IsRegistered(device3)); + DevicePermissionsManager* manager = + DevicePermissionsManager::Get(env_.profile()); + scoped_ptr<DevicePermissions> device_permissions = + manager->GetForExtension(extension_->id()); + ASSERT_TRUE(device_permissions->CheckUsbDevice(device0)); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device1)); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device2)); + ASSERT_FALSE(device_permissions->CheckUsbDevice(device3)); } -} // namespace apps +} // namespace extensions diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index d982db2..ffff874 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -4,7 +4,6 @@ { 'variables': { 'chrome_unit_tests_sources': [ - '../apps/saved_devices_service_unittest.cc', '../apps/saved_files_service_unittest.cc', '../components/autofill/content/renderer/test_password_autofill_agent.cc', '../components/autofill/content/renderer/test_password_autofill_agent.h', @@ -317,6 +316,7 @@ 'browser/extensions/api/declarative_content/chrome_content_rules_registry_unittest.cc', 'browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc', 'browser/extensions/api/declarative_webrequest/webrequest_rules_registry_unittest.cc', + 'browser/extensions/api/device_permissions_manager_unittest.cc', 'browser/extensions/api/dial/dial_device_data_unittest.cc', 'browser/extensions/api/dial/dial_registry_unittest.cc', 'browser/extensions/api/dial/dial_service_unittest.cc', diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index 0b7e062..55dbed7 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn @@ -122,6 +122,8 @@ source_set("browser") { "api/declarative_webrequest/webrequest_constants.h", "api/declarative_webrequest/webrequest_rules_registry.cc", "api/declarative_webrequest/webrequest_rules_registry.h", + "api/device_permissions_manager.cc", + "api/device_permissions_manager.h", "api/dns/dns_api.cc", "api/dns/dns_api.h", "api/dns/host_resolver_wrapper.cc", diff --git a/extensions/browser/DEPS b/extensions/browser/DEPS index e540b13..c1d5be8 100644 --- a/extensions/browser/DEPS +++ b/extensions/browser/DEPS @@ -7,6 +7,7 @@ include_rules = [ "+components/web_modal", "+content/public/browser", "+device/bluetooth", + "+device/usb", "+grit/extensions_strings.h", "+net", "+skia/ext/image_operations.h", diff --git a/extensions/browser/api/device_permissions_manager.cc b/extensions/browser/api/device_permissions_manager.cc new file mode 100644 index 0000000..a005adb --- /dev/null +++ b/extensions/browser/api/device_permissions_manager.cc @@ -0,0 +1,384 @@ +// 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<DevicePermissionEntry> GetDevicePermissionEntries( + ExtensionPrefs* prefs, + const std::string& extension_id) { + std::vector<DevicePermissionEntry> 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::UsbDevice> 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<DevicePermissionEntry>& permission_entries, + const std::set<scoped_refptr<device::UsbDevice>>& ephemeral_devices) + : permission_entries_(permission_entries), + ephemeral_devices_(ephemeral_devices) { +} + +std::vector<DevicePermissionEntry>& DevicePermissions::permission_entries() { + return permission_entries_; +} + +std::set<scoped_refptr<device::UsbDevice>>& +DevicePermissions::ephemeral_devices() { + return ephemeral_devices_; +} + +// static +DevicePermissionsManager* DevicePermissionsManager::Get( + BrowserContext* context) { + return DevicePermissionsManagerFactory::GetForBrowserContext(context); +} + +scoped_ptr<DevicePermissions> 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<base::string16> +DevicePermissionsManager::GetPermissionMessageStrings( + const std::string& extension_id) { + DCHECK(CalledOnValidThread()); + + std::vector<base::string16> 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::UsbDevice> 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<std::string, DevicePermissions*>::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<std::string, DevicePermissions*>::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<ExtensionHost>(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<scoped_refptr<UsbDevice>>::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<UsbDevice> 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<DevicePermissionsManager*>( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +// static +DevicePermissionsManagerFactory* +DevicePermissionsManagerFactory::GetInstance() { + return Singleton<DevicePermissionsManagerFactory>::get(); +} + +DevicePermissionsManagerFactory::DevicePermissionsManagerFactory() + : BrowserContextKeyedServiceFactory( + "DevicePermissionsManager", + BrowserContextDependencyManager::GetInstance()) { +} + +DevicePermissionsManagerFactory::~DevicePermissionsManagerFactory() { +} + +KeyedService* DevicePermissionsManagerFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + return new DevicePermissionsManager(context); +} + +} // namespace extensions diff --git a/extensions/browser/api/device_permissions_manager.h b/extensions/browser/api/device_permissions_manager.h new file mode 100644 index 0000000..9b79233 --- /dev/null +++ b/extensions/browser/api/device_permissions_manager.h @@ -0,0 +1,151 @@ +// 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. + +#ifndef EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_ +#define EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/threading/thread_checker.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "components/keyed_service/core/keyed_service.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "device/usb/usb_device.h" + +template <typename T> +struct DefaultSingletonTraits; + +namespace base { +class Value; +} + +namespace content { +class BrowserContext; +} + +namespace device { +class UsbDevice; +} + +namespace extensions { + +// Stores information about a device saved with access granted. +struct DevicePermissionEntry { + DevicePermissionEntry(uint16_t vendor_id, + uint16_t product_id, + const base::string16& serial_number); + + base::Value* ToValue() const; + + // The vendor ID of this device. + uint16_t vendor_id; + + // The product ID of this device. + uint16_t product_id; + + // The serial number (possibly alphanumeric) of this device. + base::string16 serial_number; +}; + +// Stores a copy of device permissions associated with a particular extension. +class DevicePermissions { + public: + virtual ~DevicePermissions(); + + bool CheckUsbDevice(scoped_refptr<device::UsbDevice> device) const; + + private: + friend class DevicePermissionsManager; + + DevicePermissions(content::BrowserContext* context, + const std::string& extension_id); + DevicePermissions( + const std::vector<DevicePermissionEntry>& permission_entries, + const std::set<scoped_refptr<device::UsbDevice>>& ephemeral_devices); + + std::vector<DevicePermissionEntry>& permission_entries(); + std::set<scoped_refptr<device::UsbDevice>>& ephemeral_devices(); + + std::vector<DevicePermissionEntry> permission_entries_; + std::set<scoped_refptr<device::UsbDevice>> ephemeral_devices_; + + DISALLOW_COPY_AND_ASSIGN(DevicePermissions); +}; + +// Manages saved device permissions for all extensions. +class DevicePermissionsManager : public KeyedService, + public base::NonThreadSafe, + public content::NotificationObserver, + public device::UsbDevice::Observer { + public: + static DevicePermissionsManager* Get(content::BrowserContext* context); + + // Returns a copy of the DevicePermissions object for a given extension that + // can be used by any thread. + scoped_ptr<DevicePermissions> GetForExtension( + const std::string& extension_id); + + std::vector<base::string16> GetPermissionMessageStrings( + const std::string& extension_id); + + void AllowUsbDevice(const std::string& extension_id, + scoped_refptr<device::UsbDevice> device, + const base::string16& serial_number); + + void Clear(const std::string& extension_id); + + private: + friend class DevicePermissionsManagerFactory; + + DevicePermissionsManager(content::BrowserContext* context); + virtual ~DevicePermissionsManager(); + + DevicePermissions* Get(const std::string& extension_id) const; + DevicePermissions* GetOrInsert(const std::string& extension_id); + + // content::NotificationObserver. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // device::UsbDevice::Observer + virtual void OnDisconnect(scoped_refptr<device::UsbDevice> device) OVERRIDE; + + content::BrowserContext* context_; + std::map<std::string, DevicePermissions*> extension_id_to_device_permissions_; + content::NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(DevicePermissionsManager); +}; + +class DevicePermissionsManagerFactory + : public BrowserContextKeyedServiceFactory { + public: + static DevicePermissionsManager* GetForBrowserContext( + content::BrowserContext* context); + static DevicePermissionsManagerFactory* GetInstance(); + + private: + friend struct DefaultSingletonTraits<DevicePermissionsManagerFactory>; + + DevicePermissionsManagerFactory(); + virtual ~DevicePermissionsManagerFactory(); + + // BrowserContextKeyedServiceFactory + virtual KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(DevicePermissionsManagerFactory); +}; + +} // namespace extensions + +#endif // EXTENSIONS_DEVICE_PERMISSION_MANAGER_H_ diff --git a/extensions/browser/api/usb/DEPS b/extensions/browser/api/usb/DEPS index dce2c70..76a2fb6 100644 --- a/extensions/browser/api/usb/DEPS +++ b/extensions/browser/api/usb/DEPS @@ -1,4 +1,3 @@ include_rules = [ "+device/core", - "+device/usb", ] diff --git a/extensions/browser/api/usb_private/DEPS b/extensions/browser/api/usb_private/DEPS index dce2c70..76a2fb6 100644 --- a/extensions/browser/api/usb_private/DEPS +++ b/extensions/browser/api/usb_private/DEPS @@ -1,4 +1,3 @@ include_rules = [ "+device/core", - "+device/usb", ] diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index 94cc3c4..e2ef5d0 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -394,6 +394,8 @@ 'browser/api/declarative_webrequest/webrequest_constants.h', 'browser/api/declarative_webrequest/webrequest_rules_registry.cc', 'browser/api/declarative_webrequest/webrequest_rules_registry.h', + 'browser/api/device_permissions_manager.cc', + 'browser/api/device_permissions_manager.h', 'browser/api/dns/dns_api.cc', 'browser/api/dns/dns_api.h', 'browser/api/dns/host_resolver_wrapper.cc', diff --git a/extensions/extensions_strings.grd b/extensions/extensions_strings.grd index 7eb2f32..1553014 100644 --- a/extensions/extensions_strings.grd +++ b/extensions/extensions_strings.grd @@ -323,6 +323,15 @@ <message name="IDS_EXTENSION_TASK_MANAGER_WEBVIEW_TAG_PREFIX" desc="The prefix for a guest page loaded in a webview tag in the Task Manager"> Webview: <ph name="WEBVIEW_TAG_NAME">$1<ex>Google</ex></ph> </message> + <message name="IDS_EXTENSION_PROMPT_WARNING_USB_DEVICE_SERIAL" desc="Permission string for accessing a USB device with a known vendor name, product name and serial number."> + <ph name="PRODUCT_NAME">$2<ex>Nexus 5</ex></ph> from <ph name="VENDOR_NAME">$1<ex>Google Inc.</ex></ph> (serial number <ph name="SERIAL_NUMBER">$3<ex>ABCDEF123456</ex></ph>) + </message> + <message name="IDS_EXTENSION_PROMPT_WARNING_USB_DEVICE_PID_SERIAL" desc="Permission string for accessing a USB device with a known vendor name and serial number and a hexidecimal product ID."> + Product <ph name="PRODUCT_ID">$2<ex>0x1234</ex></ph> from <ph name="VENDOR_NAME">$1<ex>Google Inc.</ex></ph> (serial number <ph name="SERIAL_NUMBER">$3<ex>ABCDEF123456</ex></ph>) + </message> + <message name="IDS_EXTENSION_PROMPT_WARNING_USB_DEVICE_VID_PID_SERIAL" desc="Permission string for accessing a USB device with a known serial number and a hexidecimal vendor and product IDs."> + Product <ph name="PRODUCT_ID">$2<ex>0x5678</ex></ph> from Vendor <ph name="VENDOR_ID">$1<ex>0x1234</ex></ph> (serial number <ph name="SERIAL_NUMBER">$3<ex>ABCDEF123456</ex></ph>) + </message> <!-- Global error messages for extensions. Please keep alphabetized. --> <message name="IDS_EXTENSION_WARNINGS_NETWORK_DELAY" desc="Warning message indicating that an extension caused excessive network delays for web requests"> |