blob: 5737ad3ab0817568ab353a3b2069d297871837c3 [file] [log] [blame]
// 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 <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/memory/singleton.h"
#include "base/strings/string_number_conversions.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/browser_thread.h"
#include "device/core/device_client.h"
#include "device/hid/hid_device_info.h"
#include "device/hid/hid_service.h"
#include "device/usb/usb_device.h"
#include "device/usb/usb_ids.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/common/value_builder.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "ui/base/l10n/l10n_util.h"
namespace extensions {
using content::BrowserContext;
using content::BrowserThread;
using device::HidDeviceInfo;
using device::HidService;
using device::UsbDevice;
using device::UsbService;
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";
// Type identifier for HID devices.
const char kDeviceTypeHid[] = "hid";
// 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";
// The manufacturer string read from the device that the app has permission to
// access.
const char kDeviceManufacturerString[] = "manufacturer_string";
// The product string read from the device that the app has permission to
// access.
const char kDeviceProductString[] = "product_string";
// Serialized timestamp of the last time when the device was opened by the app.
const char kDeviceLastUsed[] = "last_used_time";
// Converts a DevicePermissionEntry::Type to a string for the prefs file.
const char* TypeToString(DevicePermissionEntry::Type type) {
switch (type) {
case DevicePermissionEntry::Type::USB:
return kDeviceTypeUsb;
case DevicePermissionEntry::Type::HID:
return kDeviceTypeHid;
}
NOTREACHED();
return "";
}
// Persists a DevicePermissionEntry in ExtensionPrefs.
void SaveDevicePermissionEntry(BrowserContext* context,
const std::string& extension_id,
scoped_refptr<DevicePermissionEntry> entry) {
ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
ExtensionPrefs::ScopedListUpdate update(prefs, extension_id, kDevices);
base::ListValue* devices = update.Get();
if (!devices) {
devices = update.Create();
}
std::unique_ptr<base::Value> device_entry(entry->ToValue());
DCHECK(devices->Find(*device_entry.get()) == devices->end());
devices->Append(device_entry.release());
}
bool MatchesDevicePermissionEntry(const base::DictionaryValue* value,
scoped_refptr<DevicePermissionEntry> entry) {
std::string type;
if (!value->GetStringWithoutPathExpansion(kDeviceType, &type) ||
type != TypeToString(entry->type())) {
return false;
}
int vendor_id;
if (!value->GetIntegerWithoutPathExpansion(kDeviceVendorId, &vendor_id) ||
vendor_id != entry->vendor_id()) {
return false;
}
int product_id;
if (!value->GetIntegerWithoutPathExpansion(kDeviceProductId, &product_id) ||
product_id != entry->product_id()) {
return false;
}
base::string16 serial_number;
if (!value->GetStringWithoutPathExpansion(kDeviceSerialNumber,
&serial_number) ||
serial_number != entry->serial_number()) {
return false;
}
return true;
}
// Updates the timestamp stored in ExtensionPrefs for the given
// DevicePermissionEntry.
void UpdateDevicePermissionEntry(BrowserContext* context,
const std::string& extension_id,
scoped_refptr<DevicePermissionEntry> entry) {
ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
ExtensionPrefs::ScopedListUpdate update(prefs, extension_id, kDevices);
base::ListValue* devices = update.Get();
if (!devices) {
return;
}
for (size_t i = 0; i < devices->GetSize(); ++i) {
base::DictionaryValue* dict_value;
if (!devices->GetDictionary(i, &dict_value)) {
continue;
}
if (!MatchesDevicePermissionEntry(dict_value, entry)) {
continue;
}
devices->Set(i, entry->ToValue().release());
break;
}
}
// Removes the given DevicePermissionEntry from ExtensionPrefs.
void RemoveDevicePermissionEntry(BrowserContext* context,
const std::string& extension_id,
scoped_refptr<DevicePermissionEntry> entry) {
ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
ExtensionPrefs::ScopedListUpdate update(prefs, extension_id, kDevices);
base::ListValue* devices = update.Get();
if (!devices) {
return;
}
for (size_t i = 0; i < devices->GetSize(); ++i) {
base::DictionaryValue* dict_value;
if (!devices->GetDictionary(i, &dict_value)) {
continue;
}
if (!MatchesDevicePermissionEntry(dict_value, entry)) {
continue;
}
devices->Remove(i, nullptr);
break;
}
}
// Clears all DevicePermissionEntries for the app from ExtensionPrefs.
void ClearDevicePermissionEntries(ExtensionPrefs* prefs,
const std::string& extension_id) {
prefs->UpdateExtensionPref(extension_id, kDevices, NULL);
}
scoped_refptr<DevicePermissionEntry> ReadDevicePermissionEntry(
const base::DictionaryValue* entry) {
int vendor_id;
if (!entry->GetIntegerWithoutPathExpansion(kDeviceVendorId, &vendor_id) ||
vendor_id < 0 || vendor_id > UINT16_MAX) {
return nullptr;
}
int product_id;
if (!entry->GetIntegerWithoutPathExpansion(kDeviceProductId, &product_id) ||
product_id < 0 || product_id > UINT16_MAX) {
return nullptr;
}
base::string16 serial_number;
if (!entry->GetStringWithoutPathExpansion(kDeviceSerialNumber,
&serial_number)) {
return nullptr;
}
base::string16 manufacturer_string;
// Ignore failure as this string is optional.
entry->GetStringWithoutPathExpansion(kDeviceManufacturerString,
&manufacturer_string);
base::string16 product_string;
// Ignore failure as this string is optional.
entry->GetStringWithoutPathExpansion(kDeviceProductString, &product_string);
// If a last used time is not stored in ExtensionPrefs last_used.is_null()
// will be true.
std::string last_used_str;
int64_t last_used_i64 = 0;
base::Time last_used;
if (entry->GetStringWithoutPathExpansion(kDeviceLastUsed, &last_used_str) &&
base::StringToInt64(last_used_str, &last_used_i64)) {
last_used = base::Time::FromInternalValue(last_used_i64);
}
std::string type;
if (!entry->GetStringWithoutPathExpansion(kDeviceType, &type)) {
return nullptr;
}
if (type == kDeviceTypeUsb) {
return new DevicePermissionEntry(
DevicePermissionEntry::Type::USB, vendor_id, product_id, serial_number,
manufacturer_string, product_string, last_used);
} else if (type == kDeviceTypeHid) {
return new DevicePermissionEntry(
DevicePermissionEntry::Type::HID, vendor_id, product_id, serial_number,
base::string16(), product_string, last_used);
}
return nullptr;
}
// Returns all DevicePermissionEntries for the app.
std::set<scoped_refptr<DevicePermissionEntry>> GetDevicePermissionEntries(
ExtensionPrefs* prefs,
const std::string& extension_id) {
std::set<scoped_refptr<DevicePermissionEntry>> result;
const base::ListValue* devices = NULL;
if (!prefs->ReadPrefAsList(extension_id, kDevices, &devices)) {
return result;
}
for (const auto& entry : *devices) {
const base::DictionaryValue* entry_dict;
if (entry->GetAsDictionary(&entry_dict)) {
scoped_refptr<DevicePermissionEntry> device_entry =
ReadDevicePermissionEntry(entry_dict);
if (entry_dict) {
result.insert(device_entry);
}
}
}
return result;
}
} // namespace
DevicePermissionEntry::DevicePermissionEntry(scoped_refptr<UsbDevice> device)
: usb_device_(device),
type_(Type::USB),
vendor_id_(device->vendor_id()),
product_id_(device->product_id()),
serial_number_(device->serial_number()),
manufacturer_string_(device->manufacturer_string()),
product_string_(device->product_string()) {
}
DevicePermissionEntry::DevicePermissionEntry(
scoped_refptr<HidDeviceInfo> device)
: hid_device_(device),
type_(Type::HID),
vendor_id_(device->vendor_id()),
product_id_(device->product_id()),
serial_number_(base::UTF8ToUTF16(device->serial_number())),
product_string_(base::UTF8ToUTF16(device->product_name())) {
}
DevicePermissionEntry::DevicePermissionEntry(
Type type,
uint16_t vendor_id,
uint16_t product_id,
const base::string16& serial_number,
const base::string16& manufacturer_string,
const base::string16& product_string,
const base::Time& last_used)
: type_(type),
vendor_id_(vendor_id),
product_id_(product_id),
serial_number_(serial_number),
manufacturer_string_(manufacturer_string),
product_string_(product_string),
last_used_(last_used) {
}
DevicePermissionEntry::~DevicePermissionEntry() {
}
bool DevicePermissionEntry::IsPersistent() const {
return !serial_number_.empty();
}
std::unique_ptr<base::Value> DevicePermissionEntry::ToValue() const {
if (!IsPersistent()) {
return nullptr;
}
DCHECK(!serial_number_.empty());
std::unique_ptr<base::DictionaryValue> entry_dict(
DictionaryBuilder()
.Set(kDeviceType, TypeToString(type_))
.Set(kDeviceVendorId, vendor_id_)
.Set(kDeviceProductId, product_id_)
.Set(kDeviceSerialNumber, serial_number_)
.Build());
if (!manufacturer_string_.empty()) {
entry_dict->SetStringWithoutPathExpansion(kDeviceManufacturerString,
manufacturer_string_);
}
if (!product_string_.empty()) {
entry_dict->SetStringWithoutPathExpansion(kDeviceProductString,
product_string_);
}
if (!last_used_.is_null()) {
entry_dict->SetStringWithoutPathExpansion(
kDeviceLastUsed, base::Int64ToString(last_used_.ToInternalValue()));
}
return std::move(entry_dict);
}
base::string16 DevicePermissionEntry::GetPermissionMessageString() const {
return DevicePermissionsManager::GetPermissionMessage(
vendor_id_, product_id_, manufacturer_string_, product_string_,
serial_number_, type_ == Type::USB);
}
DevicePermissions::~DevicePermissions() {
}
scoped_refptr<DevicePermissionEntry> DevicePermissions::FindUsbDeviceEntry(
scoped_refptr<UsbDevice> device) const {
const auto& ephemeral_device_entry =
ephemeral_usb_devices_.find(device.get());
if (ephemeral_device_entry != ephemeral_usb_devices_.end()) {
return ephemeral_device_entry->second;
}
if (device->serial_number().empty()) {
return nullptr;
}
for (const auto& entry : entries_) {
if (entry->IsPersistent() && entry->vendor_id() == device->vendor_id() &&
entry->product_id() == device->product_id() &&
entry->serial_number() == device->serial_number()) {
return entry;
}
}
return nullptr;
}
scoped_refptr<DevicePermissionEntry> DevicePermissions::FindHidDeviceEntry(
scoped_refptr<HidDeviceInfo> device) const {
const auto& ephemeral_device_entry =
ephemeral_hid_devices_.find(device.get());
if (ephemeral_device_entry != ephemeral_hid_devices_.end()) {
return ephemeral_device_entry->second;
}
if (device->serial_number().empty()) {
return nullptr;
}
base::string16 serial_number = base::UTF8ToUTF16(device->serial_number());
for (const auto& entry : entries_) {
if (entry->IsPersistent() && entry->vendor_id() == device->vendor_id() &&
entry->product_id() == device->product_id() &&
entry->serial_number() == serial_number) {
return entry;
}
}
return nullptr;
}
DevicePermissions::DevicePermissions(BrowserContext* context,
const std::string& extension_id) {
ExtensionPrefs* prefs = ExtensionPrefs::Get(context);
entries_ = GetDevicePermissionEntries(prefs, extension_id);
}
// static
DevicePermissionsManager* DevicePermissionsManager::Get(
BrowserContext* context) {
return DevicePermissionsManagerFactory::GetForBrowserContext(context);
}
// static
base::string16 DevicePermissionsManager::GetPermissionMessage(
uint16_t vendor_id,
uint16_t product_id,
const base::string16& manufacturer_string,
const base::string16& product_string,
const base::string16& serial_number,
bool always_include_manufacturer) {
base::string16 product = product_string;
if (product.empty()) {
const char* product_name =
device::UsbIds::GetProductName(vendor_id, product_id);
if (product_name) {
product = base::UTF8ToUTF16(product_name);
}
}
base::string16 manufacturer = manufacturer_string;
if (manufacturer_string.empty()) {
const char* vendor_name = device::UsbIds::GetVendorName(vendor_id);
if (vendor_name) {
manufacturer = base::UTF8ToUTF16(vendor_name);
}
}
if (serial_number.empty()) {
if (product.empty()) {
product = base::ASCIIToUTF16(base::StringPrintf("%04x", product_id));
if (manufacturer.empty()) {
manufacturer =
base::ASCIIToUTF16(base::StringPrintf("%04x", vendor_id));
return l10n_util::GetStringFUTF16(
IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_UNKNOWN_VENDOR, product,
manufacturer);
} else {
return l10n_util::GetStringFUTF16(
IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_VENDOR, product, manufacturer);
}
} else {
if (always_include_manufacturer) {
if (manufacturer.empty()) {
manufacturer =
base::ASCIIToUTF16(base::StringPrintf("%04x", vendor_id));
return l10n_util::GetStringFUTF16(
IDS_DEVICE_NAME_WITH_PRODUCT_UNKNOWN_VENDOR, product,
manufacturer);
} else {
return l10n_util::GetStringFUTF16(IDS_DEVICE_NAME_WITH_PRODUCT_VENDOR,
product, manufacturer);
}
} else {
return product;
}
}
} else {
if (product.empty()) {
product = base::ASCIIToUTF16(base::StringPrintf("%04x", product_id));
if (manufacturer.empty()) {
manufacturer =
base::ASCIIToUTF16(base::StringPrintf("%04x", vendor_id));
return l10n_util::GetStringFUTF16(
IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_UNKNOWN_VENDOR_SERIAL, product,
manufacturer, serial_number);
} else {
return l10n_util::GetStringFUTF16(
IDS_DEVICE_NAME_WITH_UNKNOWN_PRODUCT_VENDOR_SERIAL, product,
manufacturer, serial_number);
}
} else {
if (always_include_manufacturer) {
if (manufacturer.empty()) {
manufacturer =
base::ASCIIToUTF16(base::StringPrintf("%04x", vendor_id));
return l10n_util::GetStringFUTF16(
IDS_DEVICE_NAME_WITH_PRODUCT_UNKNOWN_VENDOR_SERIAL, product,
manufacturer, serial_number);
} else {
return l10n_util::GetStringFUTF16(
IDS_DEVICE_NAME_WITH_PRODUCT_VENDOR_SERIAL, product, manufacturer,
serial_number);
}
} else {
return l10n_util::GetStringFUTF16(IDS_DEVICE_NAME_WITH_PRODUCT_SERIAL,
product, serial_number);
}
}
}
}
DevicePermissions* DevicePermissionsManager::GetForExtension(
const std::string& extension_id) {
DCHECK(thread_checker_.CalledOnValidThread());
DevicePermissions* device_permissions = GetInternal(extension_id);
if (!device_permissions) {
device_permissions = new DevicePermissions(context_, extension_id);
extension_id_to_device_permissions_[extension_id] = device_permissions;
}
return device_permissions;
}
std::vector<base::string16>
DevicePermissionsManager::GetPermissionMessageStrings(
const std::string& extension_id) const {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<base::string16> messages;
const DevicePermissions* device_permissions = GetInternal(extension_id);
if (device_permissions) {
for (const scoped_refptr<DevicePermissionEntry>& entry :
device_permissions->entries()) {
messages.push_back(entry->GetPermissionMessageString());
}
}
return messages;
}
void DevicePermissionsManager::AllowUsbDevice(const std::string& extension_id,
scoped_refptr<UsbDevice> device) {
DCHECK(thread_checker_.CalledOnValidThread());
DevicePermissions* device_permissions = GetForExtension(extension_id);
scoped_refptr<DevicePermissionEntry> device_entry(
new DevicePermissionEntry(device));
if (device_entry->IsPersistent()) {
for (const auto& entry : device_permissions->entries()) {
if (entry->vendor_id() == device_entry->vendor_id() &&
entry->product_id() == device_entry->product_id() &&
entry->serial_number() == device_entry->serial_number()) {
return;
}
}
device_permissions->entries_.insert(device_entry);
SaveDevicePermissionEntry(context_, extension_id, device_entry);
} else if (!ContainsKey(device_permissions->ephemeral_usb_devices_,
device.get())) {
// Non-persistent devices cannot be reliably identified when they are
// reconnected so such devices are only remembered until disconnect.
// Register an observer here so that this set doesn't grow undefinitely.
device_permissions->entries_.insert(device_entry);
device_permissions->ephemeral_usb_devices_[device.get()] = device_entry;
// Only start observing when an ephemeral device has been added so that
// UsbService is not automatically initialized on profile creation (which it
// would be if this call were in the constructor).
UsbService* usb_service = device::DeviceClient::Get()->GetUsbService();
if (!usb_service_observer_.IsObserving(usb_service)) {
usb_service_observer_.Add(usb_service);
}
}
}
void DevicePermissionsManager::AllowHidDevice(
const std::string& extension_id,
scoped_refptr<HidDeviceInfo> device) {
DCHECK(thread_checker_.CalledOnValidThread());
DevicePermissions* device_permissions = GetForExtension(extension_id);
scoped_refptr<DevicePermissionEntry> device_entry(
new DevicePermissionEntry(device));
if (device_entry->IsPersistent()) {
for (const auto& entry : device_permissions->entries()) {
if (entry->vendor_id() == device_entry->vendor_id() &&
entry->product_id() == device_entry->product_id() &&
entry->serial_number() == device_entry->serial_number()) {
return;
}
}
device_permissions->entries_.insert(device_entry);
SaveDevicePermissionEntry(context_, extension_id, device_entry);
} else if (!ContainsKey(device_permissions->ephemeral_hid_devices_,
device.get())) {
// Non-persistent devices cannot be reliably identified when they are
// reconnected so such devices are only remembered until disconnect.
// Register an observer here so that this set doesn't grow undefinitely.
device_permissions->entries_.insert(device_entry);
device_permissions->ephemeral_hid_devices_[device.get()] = device_entry;
// Only start observing when an ephemeral device has been added so that
// HidService is not automatically initialized on profile creation (which it
// would be if this call were in the constructor).
HidService* hid_service = device::DeviceClient::Get()->GetHidService();
if (!hid_service_observer_.IsObserving(hid_service)) {
hid_service_observer_.Add(hid_service);
}
}
}
void DevicePermissionsManager::UpdateLastUsed(
const std::string& extension_id,
scoped_refptr<DevicePermissionEntry> entry) {
DCHECK(thread_checker_.CalledOnValidThread());
entry->set_last_used(base::Time::Now());
if (entry->IsPersistent()) {
UpdateDevicePermissionEntry(context_, extension_id, entry);
}
}
void DevicePermissionsManager::RemoveEntry(
const std::string& extension_id,
scoped_refptr<DevicePermissionEntry> entry) {
DCHECK(thread_checker_.CalledOnValidThread());
DevicePermissions* device_permissions = GetInternal(extension_id);
DCHECK(device_permissions);
DCHECK(ContainsKey(device_permissions->entries_, entry));
device_permissions->entries_.erase(entry);
if (entry->IsPersistent()) {
RemoveDevicePermissionEntry(context_, extension_id, entry);
} else if (entry->type_ == DevicePermissionEntry::Type::USB) {
device_permissions->ephemeral_usb_devices_.erase(entry->usb_device_.get());
} else if (entry->type_ == DevicePermissionEntry::Type::HID) {
device_permissions->ephemeral_hid_devices_.erase(entry->hid_device_.get());
} else {
NOTREACHED();
}
}
void DevicePermissionsManager::Clear(const std::string& extension_id) {
DCHECK(thread_checker_.CalledOnValidThread());
ClearDevicePermissionEntries(ExtensionPrefs::Get(context_), extension_id);
DevicePermissions* device_permissions = GetInternal(extension_id);
if (device_permissions) {
extension_id_to_device_permissions_.erase(extension_id);
delete device_permissions;
}
}
DevicePermissionsManager::DevicePermissionsManager(
content::BrowserContext* context)
: context_(context),
usb_service_observer_(this),
hid_service_observer_(this) {
}
DevicePermissionsManager::~DevicePermissionsManager() {
for (const auto& map_entry : extension_id_to_device_permissions_) {
DevicePermissions* device_permissions = map_entry.second;
delete device_permissions;
}
}
DevicePermissions* DevicePermissionsManager::GetInternal(
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;
}
void DevicePermissionsManager::OnDeviceRemovedCleanup(
scoped_refptr<UsbDevice> device) {
DCHECK(thread_checker_.CalledOnValidThread());
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.
DevicePermissions* device_permissions = map_entry.second;
const auto& device_entry =
device_permissions->ephemeral_usb_devices_.find(device.get());
if (device_entry != device_permissions->ephemeral_usb_devices_.end()) {
device_permissions->entries_.erase(device_entry->second);
device_permissions->ephemeral_usb_devices_.erase(device_entry);
}
}
}
void DevicePermissionsManager::OnDeviceRemovedCleanup(
scoped_refptr<device::HidDeviceInfo> device) {
DCHECK(thread_checker_.CalledOnValidThread());
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.
DevicePermissions* device_permissions = map_entry.second;
const auto& device_entry =
device_permissions->ephemeral_hid_devices_.find(device.get());
if (device_entry != device_permissions->ephemeral_hid_devices_.end()) {
device_permissions->entries_.erase(device_entry->second);
device_permissions->ephemeral_hid_devices_.erase(device_entry);
}
}
}
// static
DevicePermissionsManager* DevicePermissionsManagerFactory::GetForBrowserContext(
content::BrowserContext* context) {
return static_cast<DevicePermissionsManager*>(
GetInstance()->GetServiceForBrowserContext(context, true));
}
// static
DevicePermissionsManagerFactory*
DevicePermissionsManagerFactory::GetInstance() {
return base::Singleton<DevicePermissionsManagerFactory>::get();
}
DevicePermissionsManagerFactory::DevicePermissionsManagerFactory()
: BrowserContextKeyedServiceFactory(
"DevicePermissionsManager",
BrowserContextDependencyManager::GetInstance()) {
}
DevicePermissionsManagerFactory::~DevicePermissionsManagerFactory() {
}
KeyedService* DevicePermissionsManagerFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
return new DevicePermissionsManager(context);
}
BrowserContext* DevicePermissionsManagerFactory::GetBrowserContextToUse(
BrowserContext* context) const {
// Return the original (possibly off-the-record) browser context so that a
// separate instance of the DevicePermissionsManager is used in incognito
// mode. The parent class's implemenation returns NULL.
return context;
}
} // namespace extensions