1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "extensions/browser/api/device_permissions_manager.h"
7 #include "base/memory/singleton.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/values.h"
12 #include "components/keyed_service/content/browser_context_dependency_manager.h"
13 #include "device/core/device_client.h"
14 #include "device/usb/usb_device.h"
15 #include "device/usb/usb_ids.h"
16 #include "extensions/browser/extension_host.h"
17 #include "extensions/browser/extension_prefs.h"
18 #include "extensions/browser/extensions_browser_client.h"
19 #include "extensions/browser/process_manager.h"
20 #include "extensions/browser/process_manager_factory.h"
21 #include "extensions/strings/grit/extensions_strings.h"
22 #include "ui/base/l10n/l10n_util.h"
24 namespace extensions
{
26 using content::BrowserContext
;
27 using device::UsbDevice
;
28 using extensions::APIPermission
;
29 using extensions::Extension
;
30 using extensions::ExtensionHost
;
31 using extensions::ExtensionPrefs
;
37 // The device that the app has permission to access.
38 const char kDevices
[] = "devices";
40 // The type of device saved.
41 const char kDeviceType
[] = "type";
43 // Type identifier for USB devices.
44 const char kDeviceTypeUsb
[] = "usb";
46 // The vendor ID of the device that the app had permission to access.
47 const char kDeviceVendorId
[] = "vendor_id";
49 // The product ID of the device that the app had permission to access.
50 const char kDeviceProductId
[] = "product_id";
52 // The serial number of the device that the app has permission to access.
53 const char kDeviceSerialNumber
[] = "serial_number";
55 // The manufacturer string read from the device that the app has permission to
57 const char kDeviceManufacturerString
[] = "manufacturer_string";
59 // The product string read from the device that the app has permission to
61 const char kDeviceProductString
[] = "product_string";
63 // Serialized timestamp of the last time when the device was opened by the app.
64 const char kDeviceLastUsed
[] = "last_used_time";
66 // Persists a DevicePermissionEntry in ExtensionPrefs.
67 void SaveDevicePermissionEntry(BrowserContext
* context
,
68 const std::string
& extension_id
,
69 scoped_refptr
<DevicePermissionEntry
> entry
) {
70 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(context
);
71 ExtensionPrefs::ScopedListUpdate
update(prefs
, extension_id
, kDevices
);
72 base::ListValue
* devices
= update
.Get();
74 devices
= update
.Create();
77 scoped_ptr
<base::Value
> device_entry(entry
->ToValue());
78 DCHECK(devices
->Find(*device_entry
.get()) == devices
->end());
79 devices
->Append(device_entry
.release());
82 bool MatchesDevicePermissionEntry(const base::DictionaryValue
* value
,
83 scoped_refptr
<DevicePermissionEntry
> entry
) {
85 if (!value
->GetStringWithoutPathExpansion(kDeviceType
, &type
) ||
86 type
!= kDeviceTypeUsb
) {
90 if (!value
->GetIntegerWithoutPathExpansion(kDeviceVendorId
, &vendor_id
) ||
91 vendor_id
!= entry
->vendor_id()) {
95 if (!value
->GetIntegerWithoutPathExpansion(kDeviceProductId
, &product_id
) ||
96 product_id
!= entry
->product_id()) {
99 base::string16 serial_number
;
100 if (!value
->GetStringWithoutPathExpansion(kDeviceSerialNumber
,
102 serial_number
!= entry
->serial_number()) {
108 // Updates the timestamp stored in ExtensionPrefs for the given
109 // DevicePermissionEntry.
110 void UpdateDevicePermissionEntry(BrowserContext
* context
,
111 const std::string
& extension_id
,
112 scoped_refptr
<DevicePermissionEntry
> entry
) {
113 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(context
);
114 ExtensionPrefs::ScopedListUpdate
update(prefs
, extension_id
, kDevices
);
115 base::ListValue
* devices
= update
.Get();
120 for (size_t i
= 0; i
< devices
->GetSize(); ++i
) {
121 base::DictionaryValue
* dict_value
;
122 if (!devices
->GetDictionary(i
, &dict_value
)) {
125 if (!MatchesDevicePermissionEntry(dict_value
, entry
)) {
128 devices
->Set(i
, entry
->ToValue().release());
133 // Removes the given DevicePermissionEntry from ExtensionPrefs.
134 void RemoveDevicePermissionEntry(BrowserContext
* context
,
135 const std::string
& extension_id
,
136 scoped_refptr
<DevicePermissionEntry
> entry
) {
137 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(context
);
138 ExtensionPrefs::ScopedListUpdate
update(prefs
, extension_id
, kDevices
);
139 base::ListValue
* devices
= update
.Get();
144 for (size_t i
= 0; i
< devices
->GetSize(); ++i
) {
145 base::DictionaryValue
* dict_value
;
146 if (!devices
->GetDictionary(i
, &dict_value
)) {
149 if (!MatchesDevicePermissionEntry(dict_value
, entry
)) {
152 devices
->Remove(i
, nullptr);
157 // Clears all DevicePermissionEntries for the app from ExtensionPrefs.
158 void ClearDevicePermissionEntries(ExtensionPrefs
* prefs
,
159 const std::string
& extension_id
) {
160 prefs
->UpdateExtensionPref(extension_id
, kDevices
, NULL
);
163 // Returns all DevicePermissionEntries for the app.
164 std::set
<scoped_refptr
<DevicePermissionEntry
>> GetDevicePermissionEntries(
165 ExtensionPrefs
* prefs
,
166 const std::string
& extension_id
) {
167 std::set
<scoped_refptr
<DevicePermissionEntry
>> result
;
168 const base::ListValue
* devices
= NULL
;
169 if (!prefs
->ReadPrefAsList(extension_id
, kDevices
, &devices
)) {
173 for (const base::Value
* entry
: *devices
) {
174 const base::DictionaryValue
* entry_dict
;
175 if (!entry
->GetAsDictionary(&entry_dict
)) {
179 if (!entry_dict
->GetStringWithoutPathExpansion(kDeviceType
, &type
) ||
180 type
!= kDeviceTypeUsb
) {
184 if (!entry_dict
->GetIntegerWithoutPathExpansion(kDeviceVendorId
,
186 vendor_id
< 0 || vendor_id
> UINT16_MAX
) {
190 if (!entry_dict
->GetIntegerWithoutPathExpansion(kDeviceProductId
,
192 product_id
< 0 || product_id
> UINT16_MAX
) {
195 base::string16 serial_number
;
196 if (!entry_dict
->GetStringWithoutPathExpansion(kDeviceSerialNumber
,
201 base::string16 manufacturer_string
;
202 // Ignore failure as this string is optional.
203 entry_dict
->GetStringWithoutPathExpansion(kDeviceManufacturerString
,
204 &manufacturer_string
);
206 base::string16 product_string
;
207 // Ignore failure as this string is optional.
208 entry_dict
->GetStringWithoutPathExpansion(kDeviceProductString
,
211 // If a last used time is not stored in ExtensionPrefs last_used.is_null()
213 std::string last_used_str
;
214 int64 last_used_i64
= 0;
215 base::Time last_used
;
216 if (entry_dict
->GetStringWithoutPathExpansion(kDeviceLastUsed
,
218 base::StringToInt64(last_used_str
, &last_used_i64
)) {
219 last_used
= base::Time::FromInternalValue(last_used_i64
);
222 result
.insert(new DevicePermissionEntry(vendor_id
, product_id
,
223 serial_number
, manufacturer_string
,
224 product_string
, last_used
));
231 DevicePermissionEntry::DevicePermissionEntry(
232 scoped_refptr
<device::UsbDevice
> device
,
233 const base::string16
& serial_number
,
234 const base::string16
& manufacturer_string
,
235 const base::string16
& product_string
)
237 vendor_id_(device
->vendor_id()),
238 product_id_(device
->product_id()),
239 serial_number_(serial_number
),
240 manufacturer_string_(manufacturer_string
),
241 product_string_(product_string
) {
244 DevicePermissionEntry::DevicePermissionEntry(
247 const base::string16
& serial_number
,
248 const base::string16
& manufacturer_string
,
249 const base::string16
& product_string
,
250 const base::Time
& last_used
)
251 : vendor_id_(vendor_id
),
252 product_id_(product_id
),
253 serial_number_(serial_number
),
254 manufacturer_string_(manufacturer_string
),
255 product_string_(product_string
),
256 last_used_(last_used
) {
259 DevicePermissionEntry::~DevicePermissionEntry() {
262 bool DevicePermissionEntry::IsPersistent() const {
263 return !serial_number_
.empty();
266 scoped_ptr
<base::Value
> DevicePermissionEntry::ToValue() const {
267 if (!IsPersistent()) {
271 scoped_ptr
<base::DictionaryValue
> entry_dict(new base::DictionaryValue());
272 entry_dict
->SetStringWithoutPathExpansion(kDeviceType
, kDeviceTypeUsb
);
273 entry_dict
->SetIntegerWithoutPathExpansion(kDeviceVendorId
, vendor_id_
);
274 entry_dict
->SetIntegerWithoutPathExpansion(kDeviceProductId
, product_id_
);
275 DCHECK(!serial_number_
.empty());
276 entry_dict
->SetStringWithoutPathExpansion(kDeviceSerialNumber
,
278 if (!manufacturer_string_
.empty()) {
279 entry_dict
->SetStringWithoutPathExpansion(kDeviceManufacturerString
,
280 manufacturer_string_
);
282 if (!product_string_
.empty()) {
283 entry_dict
->SetStringWithoutPathExpansion(kDeviceProductString
,
286 if (!last_used_
.is_null()) {
287 entry_dict
->SetStringWithoutPathExpansion(
288 kDeviceLastUsed
, base::Int64ToString(last_used_
.ToInternalValue()));
291 return entry_dict
.Pass();
294 base::string16
DevicePermissionEntry::GetPermissionMessageString() const {
295 if (serial_number_
.empty()) {
296 return l10n_util::GetStringFUTF16(IDS_DEVICE_PERMISSIONS_DEVICE_NAME
,
297 GetProduct(), GetManufacturer());
299 return l10n_util::GetStringFUTF16(
300 IDS_EXTENSION_PROMPT_WARNING_USB_DEVICE_SERIAL
, GetProduct(),
301 GetManufacturer(), serial_number_
);
305 base::string16
DevicePermissionEntry::GetManufacturer() const {
306 if (manufacturer_string_
.empty()) {
307 const char* vendor_name
= device::UsbIds::GetVendorName(vendor_id_
);
309 return base::UTF8ToUTF16(vendor_name
);
311 return l10n_util::GetStringFUTF16(
312 IDS_DEVICE_UNKNOWN_VENDOR
,
313 base::ASCIIToUTF16(base::StringPrintf("0x%04x", vendor_id_
)));
316 return manufacturer_string_
;
320 base::string16
DevicePermissionEntry::GetProduct() const {
321 if (product_string_
.empty()) {
322 const char* product_name
=
323 device::UsbIds::GetProductName(vendor_id_
, product_id_
);
325 return base::UTF8ToUTF16(product_name
);
327 return l10n_util::GetStringFUTF16(
328 IDS_DEVICE_UNKNOWN_PRODUCT
,
329 base::ASCIIToUTF16(base::StringPrintf("0x%04x", product_id_
)));
332 return product_string_
;
336 DevicePermissions::~DevicePermissions() {
339 scoped_refptr
<DevicePermissionEntry
> DevicePermissions::FindEntry(
340 scoped_refptr
<device::UsbDevice
> device
,
341 const base::string16
& serial_number
) const {
342 const auto& ephemeral_device_entry
= ephemeral_devices_
.find(device
);
343 if (ephemeral_device_entry
!= ephemeral_devices_
.end()) {
344 return ephemeral_device_entry
->second
;
347 if (serial_number
.empty()) {
351 for (const auto& entry
: entries_
) {
352 if (!entry
->IsPersistent()) {
355 if (entry
->vendor_id() != device
->vendor_id()) {
358 if (entry
->product_id() != device
->product_id()) {
361 if (entry
->serial_number() != serial_number
) {
369 DevicePermissions::DevicePermissions(BrowserContext
* context
,
370 const std::string
& extension_id
) {
371 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(context
);
372 entries_
= GetDevicePermissionEntries(prefs
, extension_id
);
375 DevicePermissions::DevicePermissions(const DevicePermissions
* original
)
376 : entries_(original
->entries_
),
377 ephemeral_devices_(original
->ephemeral_devices_
) {
381 DevicePermissionsManager
* DevicePermissionsManager::Get(
382 BrowserContext
* context
) {
383 return DevicePermissionsManagerFactory::GetForBrowserContext(context
);
386 scoped_ptr
<DevicePermissions
> DevicePermissionsManager::GetForExtension(
387 const std::string
& extension_id
) {
388 DCHECK(CalledOnValidThread());
389 return make_scoped_ptr(new DevicePermissions(GetOrInsert(extension_id
)));
392 std::vector
<base::string16
>
393 DevicePermissionsManager::GetPermissionMessageStrings(
394 const std::string
& extension_id
) const {
395 DCHECK(CalledOnValidThread());
396 std::vector
<base::string16
> messages
;
397 const DevicePermissions
* device_permissions
= Get(extension_id
);
398 if (device_permissions
) {
399 for (const scoped_refptr
<DevicePermissionEntry
>& entry
:
400 device_permissions
->entries()) {
401 messages
.push_back(entry
->GetPermissionMessageString());
407 void DevicePermissionsManager::AllowUsbDevice(
408 const std::string
& extension_id
,
409 scoped_refptr
<device::UsbDevice
> device
,
410 const base::string16
& product_string
,
411 const base::string16
& manufacturer_string
,
412 const base::string16
& serial_number
) {
413 DCHECK(CalledOnValidThread());
414 DevicePermissions
* device_permissions
= GetOrInsert(extension_id
);
416 scoped_refptr
<DevicePermissionEntry
> device_entry(new DevicePermissionEntry(
417 device
, serial_number
, manufacturer_string
, product_string
));
419 if (device_entry
->IsPersistent()) {
420 for (const auto& entry
: device_permissions
->entries()) {
421 if (entry
->vendor_id() != device_entry
->vendor_id()) {
424 if (entry
->product_id() != device_entry
->product_id()) {
427 if (entry
->serial_number() == device_entry
->serial_number()) {
432 device_permissions
->entries_
.insert(device_entry
);
433 SaveDevicePermissionEntry(context_
, extension_id
, device_entry
);
434 } else if (!ContainsKey(device_permissions
->ephemeral_devices_
, device
)) {
435 // Non-persistent devices cannot be reliably identified when they are
436 // reconnected so such devices are only remembered until disconnect.
437 // Register an observer here so that this set doesn't grow undefinitely.
438 device_permissions
->entries_
.insert(device_entry
);
439 device_permissions
->ephemeral_devices_
[device
] = device_entry
;
441 // Only start observing when an ephemeral device has been added so that
442 // UsbService is not automatically initialized on profile creation (which it
443 // would be if this call were in the constructor).
444 device::UsbService
* usb_service
=
445 device::DeviceClient::Get()->GetUsbService();
447 if (!usb_service_observer_
.IsObserving(usb_service
)) {
448 usb_service_observer_
.Add(usb_service
);
453 void DevicePermissionsManager::UpdateLastUsed(
454 const std::string
& extension_id
,
455 scoped_refptr
<DevicePermissionEntry
> entry
) {
456 DCHECK(CalledOnValidThread());
457 entry
->set_last_used(base::Time::Now());
458 if (entry
->IsPersistent()) {
459 UpdateDevicePermissionEntry(context_
, extension_id
, entry
);
463 void DevicePermissionsManager::RemoveEntry(
464 const std::string
& extension_id
,
465 scoped_refptr
<DevicePermissionEntry
> entry
) {
466 DCHECK(CalledOnValidThread());
467 DevicePermissions
* device_permissions
= Get(extension_id
);
468 DCHECK(device_permissions
);
469 DCHECK(ContainsKey(device_permissions
->entries_
, entry
));
470 device_permissions
->entries_
.erase(entry
);
471 if (entry
->IsPersistent()) {
472 RemoveDevicePermissionEntry(context_
, extension_id
, entry
);
474 device_permissions
->ephemeral_devices_
.erase(entry
->device_
);
478 void DevicePermissionsManager::Clear(const std::string
& extension_id
) {
479 DCHECK(CalledOnValidThread());
481 ClearDevicePermissionEntries(ExtensionPrefs::Get(context_
), extension_id
);
482 DevicePermissions
* device_permissions
= Get(extension_id
);
483 if (device_permissions
) {
484 extension_id_to_device_permissions_
.erase(extension_id
);
485 delete device_permissions
;
489 DevicePermissionsManager::DevicePermissionsManager(
490 content::BrowserContext
* context
)
492 process_manager_observer_(this),
493 usb_service_observer_(this) {
494 process_manager_observer_
.Add(ProcessManager::Get(context
));
497 DevicePermissionsManager::~DevicePermissionsManager() {
498 for (const auto& map_entry
: extension_id_to_device_permissions_
) {
499 DevicePermissions
* device_permissions
= map_entry
.second
;
500 delete device_permissions
;
504 DevicePermissions
* DevicePermissionsManager::Get(
505 const std::string
& extension_id
) const {
506 std::map
<std::string
, DevicePermissions
*>::const_iterator it
=
507 extension_id_to_device_permissions_
.find(extension_id
);
508 if (it
!= extension_id_to_device_permissions_
.end()) {
515 DevicePermissions
* DevicePermissionsManager::GetOrInsert(
516 const std::string
& extension_id
) {
517 DevicePermissions
* device_permissions
= Get(extension_id
);
518 if (!device_permissions
) {
519 device_permissions
= new DevicePermissions(context_
, extension_id
);
520 extension_id_to_device_permissions_
[extension_id
] = device_permissions
;
523 return device_permissions
;
526 void DevicePermissionsManager::OnBackgroundHostClose(
527 const std::string
& extension_id
) {
528 DCHECK(CalledOnValidThread());
530 DevicePermissions
* device_permissions
= Get(extension_id
);
531 if (device_permissions
) {
532 // When all of the app's windows are closed and the background page is
533 // suspended all ephemeral device permissions are cleared.
534 for (const auto& map_entry
: device_permissions
->ephemeral_devices_
) {
535 device_permissions
->entries_
.erase(map_entry
.second
);
537 device_permissions
->ephemeral_devices_
.clear();
541 void DevicePermissionsManager::OnDeviceRemoved(
542 scoped_refptr
<UsbDevice
> device
) {
543 for (const auto& map_entry
: extension_id_to_device_permissions_
) {
544 // An ephemeral device cannot be identified if it is reconnected and so
545 // permission to access it is cleared on disconnect.
546 DevicePermissions
* device_permissions
= map_entry
.second
;
547 const auto& device_entry
=
548 device_permissions
->ephemeral_devices_
.find(device
);
549 if (device_entry
!= device_permissions
->ephemeral_devices_
.end()) {
550 device_permissions
->entries_
.erase(device_entry
->second
);
551 device_permissions
->ephemeral_devices_
.erase(device
);
557 DevicePermissionsManager
* DevicePermissionsManagerFactory::GetForBrowserContext(
558 content::BrowserContext
* context
) {
559 return static_cast<DevicePermissionsManager
*>(
560 GetInstance()->GetServiceForBrowserContext(context
, true));
564 DevicePermissionsManagerFactory
*
565 DevicePermissionsManagerFactory::GetInstance() {
566 return Singleton
<DevicePermissionsManagerFactory
>::get();
569 DevicePermissionsManagerFactory::DevicePermissionsManagerFactory()
570 : BrowserContextKeyedServiceFactory(
571 "DevicePermissionsManager",
572 BrowserContextDependencyManager::GetInstance()) {
573 DependsOn(ProcessManagerFactory::GetInstance());
576 DevicePermissionsManagerFactory::~DevicePermissionsManagerFactory() {
579 KeyedService
* DevicePermissionsManagerFactory::BuildServiceInstanceFor(
580 content::BrowserContext
* context
) const {
581 return new DevicePermissionsManager(context
);
584 BrowserContext
* DevicePermissionsManagerFactory::GetBrowserContextToUse(
585 BrowserContext
* context
) const {
586 // Return the original (possibly off-the-record) browser context so that a
587 // separate instance of the DevicePermissionsManager is used in incognito
588 // mode. The parent class's implemenation returns NULL.
592 } // namespace extensions