blob: 70f48095ae4a87d453f75ffb3e1ae5b16d5da678 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/permissions/contexts/bluetooth_chooser_context.h"
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/permissions_client.h"
#include "content/public/browser/browser_context.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
#include "url/origin.h"
using blink::WebBluetoothDeviceId;
using device::BluetoothUUID;
namespace permissions {
namespace {
// The Bluetooth device permission objects are dictionary type base::Values. The
// object contains keys for the device address, device name, services that can
// be accessed, and the generated web bluetooth device ID. Since base::Value
// does not have a set type, the services key contains another dictionary type
// base::Value object where each key is a UUID for a service and the value is a
// boolean that is never used. This allows for service permissions to be queried
// quickly and for new service permissions to added without duplicating existing
// service permissions. The following is an example of how a device permission
// is formatted using JSON notation:
// {
// "device-address": "00:00:00:00:00:00",
// "name": "Wireless Device",
// "services": {
// "0xabcd": "true",
// "0x1234": "true",
// },
// "web-bluetooth-device-id": "4ik7W0WVaGFY6zXxJqdAKw==",
// }
constexpr char kDeviceAddressKey[] = "device-address";
constexpr char kDeviceNameKey[] = "name";
constexpr char kManufacturerDataKey[] = "manufacturer-data";
constexpr char kServicesKey[] = "services";
constexpr char kWebBluetoothDeviceIdKey[] = "web-bluetooth-device-id";
// The Web Bluetooth API spec states that when the user selects a device to
// pair with the origin, the origin is allowed to access any service listed in
// |options->filters| and |options->optional_services|.
// https://webbluetoothcg.github.io/web-bluetooth/#bluetooth
void AddUnionOfServicesTo(
const blink::mojom::WebBluetoothRequestDeviceOptions* options,
base::Value::Dict& permission_object) {
if (!options)
return;
DCHECK(!!permission_object.FindDict(kServicesKey));
auto& services_dict = *permission_object.FindDict(kServicesKey);
if (options->filters) {
for (const blink::mojom::WebBluetoothLeScanFilterPtr& filter :
options->filters.value()) {
if (!filter->services)
continue;
for (const BluetoothUUID& uuid : filter->services.value())
services_dict.Set(uuid.canonical_value(), /*val=*/true);
}
}
for (const BluetoothUUID& uuid : options->optional_services)
services_dict.Set(uuid.canonical_value(), /*val=*/true);
}
void AddManufacturerDataTo(
const blink::mojom::WebBluetoothRequestDeviceOptions* options,
base::Value::Dict& permission_object) {
if (!options || options->optional_manufacturer_data.empty())
return;
CHECK(permission_object.FindDict(kManufacturerDataKey));
auto& manufacturer_data_dict =
*permission_object.FindDict(kManufacturerDataKey);
for (uint16_t manufacturer_code : options->optional_manufacturer_data) {
manufacturer_data_dict.Set(base::NumberToString(manufacturer_code),
/*value=*/true);
}
}
} // namespace
BluetoothChooserContext::BluetoothChooserContext(
content::BrowserContext* browser_context)
: ObjectPermissionContextBase(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA,
PermissionsClient::Get()->GetSettingsMap(browser_context)) {}
BluetoothChooserContext::~BluetoothChooserContext() = default;
// static
base::Value::Dict BluetoothChooserContext::DeviceInfoToValue(
const device::BluetoothDevice* device,
const blink::mojom::WebBluetoothRequestDeviceOptions* options,
const WebBluetoothDeviceId& device_id) {
base::Value::Dict device_value;
device_value.Set(kDeviceAddressKey, device->GetAddress());
device_value.Set(kWebBluetoothDeviceIdKey, device_id.str());
device_value.Set(kDeviceNameKey, device->GetNameForDisplay());
device_value.Set(kServicesKey, base::Value::Dict());
AddUnionOfServicesTo(options, device_value);
device_value.Set(kManufacturerDataKey, base::Value::Dict());
AddManufacturerDataTo(options, device_value);
return device_value;
}
WebBluetoothDeviceId BluetoothChooserContext::GetWebBluetoothDeviceId(
const url::Origin& origin,
const std::string& device_address) {
const std::vector<std::unique_ptr<Object>> object_list =
GetGrantedObjects(origin);
for (const auto& object : object_list) {
const base::Value::Dict& device = object->value;
DCHECK(IsValidObject(device));
if (device_address == *device.FindString(kDeviceAddressKey)) {
return WebBluetoothDeviceId(*device.FindString(kWebBluetoothDeviceIdKey));
}
}
// Check if the device has been assigned an ID through an LE scan.
auto scanned_devices_it = scanned_devices_.find(origin);
if (scanned_devices_it == scanned_devices_.end())
return {};
auto address_to_id_it = scanned_devices_it->second.find(device_address);
if (address_to_id_it != scanned_devices_it->second.end())
return address_to_id_it->second;
return {};
}
std::string BluetoothChooserContext::GetDeviceAddress(
const url::Origin& origin,
const WebBluetoothDeviceId& device_id) {
std::optional<base::Value::Dict> device = FindDeviceObject(origin, device_id);
if (device)
return *device->FindString(kDeviceAddressKey);
// Check if the device ID corresponds to a device detected via an LE scan.
auto scanned_devices_it = scanned_devices_.find(origin);
if (scanned_devices_it == scanned_devices_.end())
return std::string();
for (const auto& entry : scanned_devices_it->second) {
if (entry.second == device_id)
return entry.first;
}
return std::string();
}
WebBluetoothDeviceId BluetoothChooserContext::AddScannedDevice(
const url::Origin& origin,
const std::string& device_address) {
// Check if a WebBluetoothDeviceId already exists for the device with
// |device_address| for the current origin.
const auto granted_id = GetWebBluetoothDeviceId(origin, device_address);
if (granted_id.IsValid())
return granted_id;
DeviceAddressToIdMap& address_to_id_map = scanned_devices_[origin];
auto scanned_id = WebBluetoothDeviceId::Create();
address_to_id_map.emplace(device_address, scanned_id);
return scanned_id;
}
WebBluetoothDeviceId BluetoothChooserContext::GrantServiceAccessPermission(
const url::Origin& origin,
const device::BluetoothDevice* device,
const blink::mojom::WebBluetoothRequestDeviceOptions* options) {
// If |origin| already has permission to access the device with
// |device_address|, update the allowed GATT services by performing a union of
// |services|.
const std::vector<std::unique_ptr<Object>> object_list =
GetGrantedObjects(origin);
const std::string& device_address = device->GetAddress();
for (const auto& object : object_list) {
const base::Value::Dict& device_dict = object->value;
DCHECK(IsValidObject(device_dict));
if (device_address == *device_dict.FindString(kDeviceAddressKey)) {
auto new_device_dict = device_dict.Clone();
WebBluetoothDeviceId device_id(
*new_device_dict.FindString(kWebBluetoothDeviceIdKey));
AddUnionOfServicesTo(options, new_device_dict);
AddManufacturerDataTo(options, new_device_dict);
UpdateObjectPermission(origin, device_dict, std::move(new_device_dict));
return device_id;
}
}
// If the device has been detected through the Web Bluetooth Scanning API,
// grant permission using the WebBluetoothDeviceId generated through that API.
// Remove the ID from the temporary |scanned_devices_| map to avoid
// duplication, since the ID will now be stored in HostContentSettingsMap.
WebBluetoothDeviceId device_id;
auto scanned_devices_it = scanned_devices_.find(origin);
if (scanned_devices_it != scanned_devices_.end()) {
auto& address_to_id_map = scanned_devices_it->second;
auto address_to_id_it = address_to_id_map.find(device_address);
if (address_to_id_it != address_to_id_map.end()) {
device_id = address_to_id_it->second;
address_to_id_map.erase(address_to_id_it);
if (scanned_devices_it->second.empty())
scanned_devices_.erase(scanned_devices_it);
}
}
if (!device_id.IsValid())
device_id = WebBluetoothDeviceId::Create();
base::Value::Dict permission_object =
DeviceInfoToValue(device, options, device_id);
GrantObjectPermission(origin, std::move(permission_object));
return device_id;
}
bool BluetoothChooserContext::HasDevicePermission(
const url::Origin& origin,
const WebBluetoothDeviceId& device_id) {
std::optional<base::Value::Dict> device = FindDeviceObject(origin, device_id);
return device.has_value();
}
void BluetoothChooserContext::RevokeDevicePermissionWebInitiated(
const url::Origin& origin,
const WebBluetoothDeviceId& device_id) {
std::optional<base::Value::Dict> device = FindDeviceObject(origin, device_id);
if (device.has_value())
RevokeObjectPermission(origin, std::move(*device));
}
bool BluetoothChooserContext::IsAllowedToAccessAtLeastOneService(
const url::Origin& origin,
const WebBluetoothDeviceId& device_id) {
std::optional<base::Value::Dict> device = FindDeviceObject(origin, device_id);
if (!device.has_value())
return false;
return !device->FindDict(kServicesKey)->empty();
}
bool BluetoothChooserContext::IsAllowedToAccessService(
const url::Origin& origin,
const WebBluetoothDeviceId& device_id,
const BluetoothUUID& service) {
std::optional<base::Value::Dict> device = FindDeviceObject(origin, device_id);
if (!device.has_value())
return false;
const auto& services_dict = *device->FindDict(kServicesKey);
return !!services_dict.contains(service.canonical_value());
}
bool BluetoothChooserContext::IsAllowedToAccessManufacturerData(
const url::Origin& origin,
const WebBluetoothDeviceId& device_id,
uint16_t manufacturer_code) {
std::optional<base::Value::Dict> device = FindDeviceObject(origin, device_id);
if (!device.has_value())
return false;
const auto& manufacturer_data_list = *device->FindDict(kManufacturerDataKey);
return manufacturer_data_list.contains(
base::NumberToString(manufacturer_code));
}
// static
WebBluetoothDeviceId BluetoothChooserContext::GetObjectDeviceId(
const base::Value::Dict& object) {
std::string device_id_str = *object.FindString(kWebBluetoothDeviceIdKey);
return WebBluetoothDeviceId(device_id_str);
}
std::string BluetoothChooserContext::GetKeyForObject(
const base::Value::Dict& object) {
if (!IsValidObject(object))
return std::string();
return *(object.FindString(kWebBluetoothDeviceIdKey));
}
bool BluetoothChooserContext::IsValidObject(const base::Value::Dict& object) {
return IsValidDict(object);
}
std::u16string BluetoothChooserContext::GetObjectDisplayName(
const base::Value::Dict& object) {
return base::UTF8ToUTF16(*object.FindString(kDeviceNameKey));
}
void BluetoothChooserContext::Shutdown() {
FlushScheduledSaveSettingsCalls();
ObjectPermissionContextBase::Shutdown();
}
bool BluetoothChooserContext::IsValidDict(const base::Value::Dict& dict) {
return dict.FindString(kDeviceAddressKey) &&
dict.FindString(kDeviceNameKey) &&
dict.FindString(kWebBluetoothDeviceIdKey) &&
WebBluetoothDeviceId::IsValid(
*dict.FindString(kWebBluetoothDeviceIdKey)) &&
dict.FindDict(kServicesKey) && dict.FindDict(kManufacturerDataKey);
}
std::optional<base::Value::Dict> BluetoothChooserContext::FindDeviceObject(
const url::Origin& origin,
const blink::WebBluetoothDeviceId& device_id) {
const std::vector<std::unique_ptr<Object>> object_list =
GetGrantedObjects(origin);
for (const auto& object : object_list) {
base::Value::Dict device = std::move(object->value);
DCHECK(IsValidDict(device));
const WebBluetoothDeviceId web_bluetooth_device_id(
*device.FindString(kWebBluetoothDeviceIdKey));
if (device_id == web_bluetooth_device_id)
return device;
}
return std::nullopt;
}
} // namespace permissions