1 // Copyright 2019 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.
4
5 #include "chrome/browser/bluetooth/bluetooth_chooser_context.h"
6
7 #include <memory>
8 #include <utility>
9 #include <vector>
10
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/values.h"
13 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "components/content_settings/core/common/content_settings_types.h"
16 #include "device/bluetooth/bluetooth_adapter.h"
17 #include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
18 #include "url/origin.h"
19
20 using blink::WebBluetoothDeviceId;
21 using device::BluetoothUUID;
22 using device::BluetoothUUIDHash;
23
24 namespace {
25
26 // The Bluetooth device permission objects are dictionary type base::Values. The
27 // object contains keys for the device address, device name, services that can
28 // be accessed, and the generated web bluetooth device ID. Since base::Value
29 // does not have a set type, the services key contains another dictionary type
30 // base::Value object where each key is a UUID for a service and the value is a
31 // boolean that is never used. This allows for service permissions to be queried
32 // quickly and for new service permissions to added without duplicating existing
33 // service permissions. The following is an example of how a device permission
34 // is formatted using JSON notation:
35 // {
36 // "device-address": "00:00:00:00:00:00",
37 // "name": "Wireless Device",
38 // "services": {
39 // "0xabcd": "true",
40 // "0x1234": "true",
41 // },
42 // "web-bluetooth-device-id": "4ik7W0WVaGFY6zXxJqdAKw==",
43 // }
44 constexpr char kDeviceAddressKey[] = "device-address";
45 constexpr char kDeviceNameKey[] = "name";
46 constexpr char kManufacturerDataKey[] = "manufacturer-data";
47 constexpr char kServicesKey[] = "services";
48 constexpr char kWebBluetoothDeviceIdKey[] = "web-bluetooth-device-id";
49
50 // The Web Bluetooth API spec states that when the user selects a device to
51 // pair with the origin, the origin is allowed to access any service listed in
52 // |options->filters| and |options->optional_services|.
53 // https://webbluetoothcg.github.io/web-bluetooth/#bluetooth
AddUnionOfServicesTo(const blink::mojom::WebBluetoothRequestDeviceOptions * options,base::Value * permission_object)54 void AddUnionOfServicesTo(
55 const blink::mojom::WebBluetoothRequestDeviceOptions* options,
56 base::Value* permission_object) {
57 if (!options)
58 return;
59
60 DCHECK(!!permission_object->FindDictKey(kServicesKey));
61 auto& services_dict = *permission_object->FindDictKey(kServicesKey);
62 if (options->filters) {
63 for (const blink::mojom::WebBluetoothLeScanFilterPtr& filter :
64 options->filters.value()) {
65 if (!filter->services)
66 continue;
67
68 for (const BluetoothUUID& uuid : filter->services.value())
69 services_dict.SetBoolKey(uuid.canonical_value(), /*val=*/true);
70 }
71 }
72
73 for (const BluetoothUUID& uuid : options->optional_services)
74 services_dict.SetBoolKey(uuid.canonical_value(), /*val=*/true);
75 }
76
AddManufacturerDataTo(const blink::mojom::WebBluetoothRequestDeviceOptions * options,base::Value * permission_object)77 void AddManufacturerDataTo(
78 const blink::mojom::WebBluetoothRequestDeviceOptions* options,
79 base::Value* permission_object) {
80 if (!options || options->optional_manufacturer_data.empty())
81 return;
82
83 base::flat_set<uint16_t> manufacturer_data_set(
84 options->optional_manufacturer_data);
85
86 if (!permission_object->FindListKey(kManufacturerDataKey)) {
87 base::Value manufacturer_data_value(base::Value::Type::LIST);
88 permission_object->SetKey(kManufacturerDataKey,
89 std::move(manufacturer_data_value));
90 }
91
92 auto& manufacturer_data_list =
93 *permission_object->FindListKey(kManufacturerDataKey);
94 for (const auto& manufacturer_data_permission :
95 manufacturer_data_list.GetList()) {
96 manufacturer_data_set.insert(
97 static_cast<uint16_t>(manufacturer_data_permission.GetInt()));
98 }
99
100 manufacturer_data_list.ClearList();
101 for (const uint16_t manufacturer_code : manufacturer_data_set)
102 manufacturer_data_list.Append(manufacturer_code);
103 }
104
DeviceInfoToDeviceObject(const device::BluetoothDevice * device,const blink::mojom::WebBluetoothRequestDeviceOptions * options,const WebBluetoothDeviceId & device_id)105 base::Value DeviceInfoToDeviceObject(
106 const device::BluetoothDevice* device,
107 const blink::mojom::WebBluetoothRequestDeviceOptions* options,
108 const WebBluetoothDeviceId& device_id) {
109 base::Value device_value(base::Value::Type::DICTIONARY);
110 device_value.SetStringKey(kDeviceAddressKey, device->GetAddress());
111 device_value.SetStringKey(kWebBluetoothDeviceIdKey, device_id.str());
112 device_value.SetStringKey(kDeviceNameKey, device->GetNameForDisplay());
113
114 base::Value services_value(base::Value::Type::DICTIONARY);
115 device_value.SetKey(kServicesKey, std::move(services_value));
116 AddUnionOfServicesTo(options, &device_value);
117
118 base::Value manufacturer_data_value(base::Value::Type::LIST);
119 device_value.SetKey(kManufacturerDataKey, std::move(manufacturer_data_value));
120 AddManufacturerDataTo(options, &device_value);
121
122 return device_value;
123 }
124
125 } // namespace
126
BluetoothChooserContext(Profile * profile)127 BluetoothChooserContext::BluetoothChooserContext(Profile* profile)
128 : ChooserContextBase(
129 ContentSettingsType::BLUETOOTH_GUARD,
130 ContentSettingsType::BLUETOOTH_CHOOSER_DATA,
131 HostContentSettingsMapFactory::GetForProfile(profile)) {}
132
133 BluetoothChooserContext::~BluetoothChooserContext() = default;
134
GetWebBluetoothDeviceId(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const std::string & device_address)135 WebBluetoothDeviceId BluetoothChooserContext::GetWebBluetoothDeviceId(
136 const url::Origin& requesting_origin,
137 const url::Origin& embedding_origin,
138 const std::string& device_address) {
139 const std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
140 object_list = GetGrantedObjects(requesting_origin, embedding_origin);
141 for (const auto& object : object_list) {
142 const base::Value& device = object->value;
143 DCHECK(IsValidObject(device));
144
145 if (device_address == *device.FindStringKey(kDeviceAddressKey)) {
146 return WebBluetoothDeviceId(
147 *device.FindStringKey(kWebBluetoothDeviceIdKey));
148 }
149 }
150
151 // Check if the device has been assigned an ID through an LE scan.
152 auto scanned_devices_it =
153 scanned_devices_.find({requesting_origin, embedding_origin});
154 if (scanned_devices_it == scanned_devices_.end())
155 return {};
156
157 auto address_to_id_it = scanned_devices_it->second.find(device_address);
158 if (address_to_id_it != scanned_devices_it->second.end())
159 return address_to_id_it->second;
160 return {};
161 }
162
GetDeviceAddress(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const WebBluetoothDeviceId & device_id)163 std::string BluetoothChooserContext::GetDeviceAddress(
164 const url::Origin& requesting_origin,
165 const url::Origin& embedding_origin,
166 const WebBluetoothDeviceId& device_id) {
167 base::Value device =
168 FindDeviceObject(requesting_origin, embedding_origin, device_id);
169 if (!device.is_none())
170 return *device.FindStringKey(kDeviceAddressKey);
171
172 // Check if the device ID corresponds to a device detected via an LE scan.
173 auto scanned_devices_it =
174 scanned_devices_.find({requesting_origin, embedding_origin});
175 if (scanned_devices_it == scanned_devices_.end())
176 return std::string();
177
178 for (const auto& entry : scanned_devices_it->second) {
179 if (entry.second == device_id)
180 return entry.first;
181 }
182 return std::string();
183 }
184
AddScannedDevice(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const std::string & device_address)185 WebBluetoothDeviceId BluetoothChooserContext::AddScannedDevice(
186 const url::Origin& requesting_origin,
187 const url::Origin& embedding_origin,
188 const std::string& device_address) {
189 // Check if a WebBluetoothDeviceId already exists for the device with
190 // |device_address| for the current origin pair.
191 const auto granted_id = GetWebBluetoothDeviceId(
192 requesting_origin, embedding_origin, device_address);
193 if (granted_id.IsValid())
194 return granted_id;
195
196 DeviceAddressToIdMap& address_to_id_map =
197 scanned_devices_[{requesting_origin, embedding_origin}];
198 auto scanned_id = WebBluetoothDeviceId::Create();
199 address_to_id_map.emplace(device_address, scanned_id);
200 return scanned_id;
201 }
202
GrantServiceAccessPermission(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const device::BluetoothDevice * device,const blink::mojom::WebBluetoothRequestDeviceOptions * options)203 WebBluetoothDeviceId BluetoothChooserContext::GrantServiceAccessPermission(
204 const url::Origin& requesting_origin,
205 const url::Origin& embedding_origin,
206 const device::BluetoothDevice* device,
207 const blink::mojom::WebBluetoothRequestDeviceOptions* options) {
208 // If |requesting_origin| and |embedding_origin| already have permission to
209 // access the device with |device_address|, update the allowed GATT services
210 // by performing a union of |services|.
211 const std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
212 object_list = GetGrantedObjects(requesting_origin, embedding_origin);
213 const std::string& device_address = device->GetAddress();
214 for (const auto& object : object_list) {
215 base::Value& device_object = object->value;
216 DCHECK(IsValidObject(device_object));
217 if (device_address == *device_object.FindStringKey(kDeviceAddressKey)) {
218 auto new_device_object = device_object.Clone();
219 WebBluetoothDeviceId device_id(
220 *new_device_object.FindStringKey(kWebBluetoothDeviceIdKey));
221
222 AddUnionOfServicesTo(options, &new_device_object);
223 AddManufacturerDataTo(options, &new_device_object);
224 UpdateObjectPermission(requesting_origin, embedding_origin, device_object,
225 std::move(new_device_object));
226 return device_id;
227 }
228 }
229
230 // If the device has been detected through the Web Bluetooth Scanning API,
231 // grant permission using the WebBluetoothDeviceId generated through that API.
232 // Remove the ID from the temporary |scanned_devices_| map to avoid
233 // duplication, since the ID will now be stored in HostContentSettingsMap.
234 WebBluetoothDeviceId device_id;
235 auto scanned_devices_it =
236 scanned_devices_.find({requesting_origin, embedding_origin});
237 if (scanned_devices_it != scanned_devices_.end()) {
238 auto& address_to_id_map = scanned_devices_it->second;
239 auto address_to_id_it = address_to_id_map.find(device_address);
240
241 if (address_to_id_it != address_to_id_map.end()) {
242 device_id = address_to_id_it->second;
243 address_to_id_map.erase(address_to_id_it);
244
245 if (scanned_devices_it->second.empty())
246 scanned_devices_.erase(scanned_devices_it);
247 }
248 }
249
250 if (!device_id.IsValid())
251 device_id = WebBluetoothDeviceId::Create();
252
253 base::Value permission_object =
254 DeviceInfoToDeviceObject(device, options, device_id);
255 GrantObjectPermission(requesting_origin, embedding_origin,
256 std::move(permission_object));
257 return device_id;
258 }
259
HasDevicePermission(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const WebBluetoothDeviceId & device_id)260 bool BluetoothChooserContext::HasDevicePermission(
261 const url::Origin& requesting_origin,
262 const url::Origin& embedding_origin,
263 const WebBluetoothDeviceId& device_id) {
264 base::Value device =
265 FindDeviceObject(requesting_origin, embedding_origin, device_id);
266 return !device.is_none();
267 }
268
IsAllowedToAccessAtLeastOneService(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const WebBluetoothDeviceId & device_id)269 bool BluetoothChooserContext::IsAllowedToAccessAtLeastOneService(
270 const url::Origin& requesting_origin,
271 const url::Origin& embedding_origin,
272 const WebBluetoothDeviceId& device_id) {
273 base::Value device =
274 FindDeviceObject(requesting_origin, embedding_origin, device_id);
275 if (device.is_none())
276 return false;
277 return !device.FindDictKey(kServicesKey)->DictEmpty();
278 }
279
IsAllowedToAccessService(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const WebBluetoothDeviceId & device_id,const BluetoothUUID & service)280 bool BluetoothChooserContext::IsAllowedToAccessService(
281 const url::Origin& requesting_origin,
282 const url::Origin& embedding_origin,
283 const WebBluetoothDeviceId& device_id,
284 const BluetoothUUID& service) {
285 base::Value device =
286 FindDeviceObject(requesting_origin, embedding_origin, device_id);
287 if (device.is_none())
288 return false;
289
290 const auto& services_dict = *device.FindDictKey(kServicesKey);
291 return !!services_dict.FindKey(service.canonical_value());
292 }
293
IsAllowedToAccessManufacturerData(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const WebBluetoothDeviceId & device_id,uint16_t manufacturer_code)294 bool BluetoothChooserContext::IsAllowedToAccessManufacturerData(
295 const url::Origin& requesting_origin,
296 const url::Origin& embedding_origin,
297 const WebBluetoothDeviceId& device_id,
298 uint16_t manufacturer_code) {
299 base::Value device =
300 FindDeviceObject(requesting_origin, embedding_origin, device_id);
301 if (device.is_none())
302 return false;
303
304 const auto* manufacturer_data_list = device.FindListKey(kManufacturerDataKey);
305 if (!manufacturer_data_list)
306 return false;
307
308 for (const auto& manufacturer_data : manufacturer_data_list->GetList()) {
309 if (manufacturer_code == manufacturer_data.GetInt())
310 return true;
311 }
312 return false;
313 }
314
315 // static
GetObjectDeviceId(const base::Value & object)316 WebBluetoothDeviceId BluetoothChooserContext::GetObjectDeviceId(
317 const base::Value& object) {
318 std::string device_id_str = *object.FindStringKey(kWebBluetoothDeviceIdKey);
319 return WebBluetoothDeviceId(device_id_str);
320 }
321
IsValidObject(const base::Value & object)322 bool BluetoothChooserContext::IsValidObject(const base::Value& object) {
323 return object.FindStringKey(kDeviceAddressKey) &&
324 object.FindStringKey(kDeviceNameKey) &&
325 object.FindStringKey(kWebBluetoothDeviceIdKey) &&
326 WebBluetoothDeviceId::IsValid(
327 *object.FindStringKey(kWebBluetoothDeviceIdKey)) &&
328 object.FindDictKey(kServicesKey);
329 }
330
GetObjectDisplayName(const base::Value & object)331 base::string16 BluetoothChooserContext::GetObjectDisplayName(
332 const base::Value& object) {
333 return base::UTF8ToUTF16(*object.FindStringKey(kDeviceNameKey));
334 }
335
FindDeviceObject(const url::Origin & requesting_origin,const url::Origin & embedding_origin,const blink::WebBluetoothDeviceId & device_id)336 base::Value BluetoothChooserContext::FindDeviceObject(
337 const url::Origin& requesting_origin,
338 const url::Origin& embedding_origin,
339 const blink::WebBluetoothDeviceId& device_id) {
340 const std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
341 object_list = GetGrantedObjects(requesting_origin, embedding_origin);
342 for (const auto& object : object_list) {
343 base::Value device = std::move(object->value);
344 DCHECK(IsValidObject(device));
345
346 const WebBluetoothDeviceId web_bluetooth_device_id(
347 *device.FindStringKey(kWebBluetoothDeviceIdKey));
348 if (device_id == web_bluetooth_device_id)
349 return device;
350 }
351 return base::Value(base::Value::Type::NONE);
352 }
353