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