Commit efc822a3 authored by Ovidio Henriquez's avatar Ovidio Henriquez Committed by Commit Bot

bluetooth: Chooser Context Implementation

This change implements the methods in BluetoothChooserContext and
creates a unit test for the class.

Design Doc:
https://docs.google.com/document/d/1h3uAVXJARHrNWaNACUPiQhLt7XI-fFFQoARSs1WgMDM/edit?usp=sharing

Bug: 589228
Change-Id: I1c564177063f9459081c45ddb4c0bb757fabd667
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1978819
Commit-Queue: Ovidio de Jesús Ruiz-Henríquez <odejesush@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#731730}
parent 6a796556
......@@ -169,6 +169,10 @@ jumbo_static_library("browser") {
"bitmap_fetcher/bitmap_fetcher_service.h",
"bitmap_fetcher/bitmap_fetcher_service_factory.cc",
"bitmap_fetcher/bitmap_fetcher_service_factory.h",
"bluetooth/bluetooth_chooser_context.cc",
"bluetooth/bluetooth_chooser_context.h",
"bluetooth/bluetooth_chooser_context_factory.cc",
"bluetooth/bluetooth_chooser_context_factory.h",
"bookmarks/bookmark_model_factory.cc",
"bookmarks/bookmark_model_factory.h",
"bookmarks/chrome_bookmark_client.cc",
......@@ -3056,10 +3060,6 @@ jumbo_static_library("browser") {
"badging/badge_manager_factory.h",
"banners/app_banner_manager_desktop.cc",
"banners/app_banner_manager_desktop.h",
"bluetooth/bluetooth_chooser_context.cc",
"bluetooth/bluetooth_chooser_context.h",
"bluetooth/bluetooth_chooser_context_factory.cc",
"bluetooth/bluetooth_chooser_context_factory.h",
"bookmarks/bookmark_html_writer.cc",
"bookmarks/bookmark_html_writer.h",
"certificate_viewer.h",
......
......@@ -4,15 +4,88 @@
#include "chrome/browser/bluetooth/bluetooth_chooser_context.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
#include "url/origin.h"
using blink::WebBluetoothDeviceId;
using device::BluetoothUUID;
using device::BluetoothUUIDHash;
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 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::WebBluetoothRequestDeviceOptionsPtr& options,
base::Value* permission_object) {
DCHECK(!!permission_object->FindDictKey(kServicesKey));
auto& services_dict = *permission_object->FindDictKey(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.SetBoolKey(uuid.canonical_value(), /*val=*/true);
}
}
for (const BluetoothUUID& uuid : options->optional_services)
services_dict.SetBoolKey(uuid.canonical_value(), /*val=*/true);
}
base::Value DeviceInfoToDeviceObject(
const device::BluetoothDevice* device,
const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options,
const WebBluetoothDeviceId& device_id) {
base::Value device_value(base::Value::Type::DICTIONARY);
device_value.SetStringKey(kDeviceAddressKey, device->GetAddress());
device_value.SetStringKey(kWebBluetoothDeviceIdKey, device_id.str());
device_value.SetStringKey(kDeviceNameKey, device->GetNameForDisplay());
base::Value services_value(base::Value::Type::DICTIONARY);
device_value.SetKey(kServicesKey, std::move(services_value));
AddUnionOfServicesTo(options, &device_value);
return device_value;
}
} // namespace
BluetoothChooserContext::BluetoothChooserContext(Profile* profile)
: ChooserContextBase(profile,
ContentSettingsType::BLUETOOTH_GUARD,
......@@ -20,36 +93,152 @@ BluetoothChooserContext::BluetoothChooserContext(Profile* profile)
BluetoothChooserContext::~BluetoothChooserContext() = default;
const WebBluetoothDeviceId BluetoothChooserContext::GetWebBluetoothDeviceId(
WebBluetoothDeviceId BluetoothChooserContext::GetWebBluetoothDeviceId(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const std::string& device_address) {
NOTIMPLEMENTED();
const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
GetGrantedObjects(requesting_origin, embedding_origin);
for (const auto& object : object_list) {
const base::Value& device = object->value;
DCHECK(IsValidObject(device));
if (device_address == *device.FindStringKey(kDeviceAddressKey)) {
return WebBluetoothDeviceId(
*device.FindStringKey(kWebBluetoothDeviceIdKey));
}
}
// Check if the device has been assigned an ID through an LE scan.
auto scanned_devices_it =
scanned_devices_.find({requesting_origin, embedding_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 {};
}
const std::string BluetoothChooserContext::GetDeviceAddress(
std::string BluetoothChooserContext::GetDeviceAddress(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const WebBluetoothDeviceId& device_id) {
NOTIMPLEMENTED();
return "";
const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
GetGrantedObjects(requesting_origin, embedding_origin);
for (const auto& object : object_list) {
const base::Value& device = object->value;
DCHECK(IsValidObject(device));
const WebBluetoothDeviceId web_bluetooth_device_id(
*device.FindStringKey(kWebBluetoothDeviceIdKey));
if (device_id == web_bluetooth_device_id)
return *device.FindStringKey(kDeviceAddressKey);
}
// Check if the device ID corresponds to a device detected via an LE scan.
auto scanned_devices_it =
scanned_devices_.find({requesting_origin, embedding_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();
}
const WebBluetoothDeviceId BluetoothChooserContext::GrantDevicePermission(
WebBluetoothDeviceId BluetoothChooserContext::AddScannedDevice(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const std::string& device_address,
base::flat_set<BluetoothUUID, BluetoothUUIDHash>& services) {
NOTIMPLEMENTED();
return {};
const std::string& device_address) {
// Check if a WebBluetoothDeviceId already exists for the device with
// |device_address| for the current origin pair.
const auto granted_id = GetWebBluetoothDeviceId(
requesting_origin, embedding_origin, device_address);
if (granted_id.IsValid())
return granted_id;
DeviceAddressToIdMap& address_to_id_map =
scanned_devices_[{requesting_origin, embedding_origin}];
auto scanned_id = WebBluetoothDeviceId::Create();
address_to_id_map.emplace(device_address, scanned_id);
return scanned_id;
}
WebBluetoothDeviceId BluetoothChooserContext::GrantServiceAccessPermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const device::BluetoothDevice* device,
const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options) {
// If |requesting_origin| and |embedding_origin| already have 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<ChooserContextBase::Object>> object_list =
GetGrantedObjects(requesting_origin, embedding_origin);
const std::string& device_address = device->GetAddress();
for (const auto& object : object_list) {
base::Value& device_object = object->value;
DCHECK(IsValidObject(device_object));
if (device_address == *device_object.FindStringKey(kDeviceAddressKey)) {
auto new_device_object = device_object.Clone();
WebBluetoothDeviceId device_id(
*new_device_object.FindStringKey(kWebBluetoothDeviceIdKey));
AddUnionOfServicesTo(options, &new_device_object);
UpdateObjectPermission(requesting_origin, embedding_origin, device_object,
std::move(new_device_object));
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({requesting_origin, embedding_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 permission_object =
DeviceInfoToDeviceObject(device, options, device_id);
GrantObjectPermission(requesting_origin, embedding_origin,
std::move(permission_object));
return device_id;
}
bool BluetoothChooserContext::HasDevicePermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const WebBluetoothDeviceId& device_id) {
NOTIMPLEMENTED();
const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
GetGrantedObjects(requesting_origin, embedding_origin);
for (const auto& object : object_list) {
const base::Value& device = object->value;
DCHECK(IsValidObject(device));
const WebBluetoothDeviceId web_bluetooth_device_id(
*device.FindStringKey(kWebBluetoothDeviceIdKey));
if (device_id == web_bluetooth_device_id)
return true;
}
return false;
}
......@@ -57,7 +246,17 @@ bool BluetoothChooserContext::IsAllowedToAccessAtLeastOneService(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const WebBluetoothDeviceId& device_id) {
NOTIMPLEMENTED();
const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
GetGrantedObjects(requesting_origin, embedding_origin);
for (const auto& object : object_list) {
const base::Value& device = object->value;
DCHECK(IsValidObject(device));
const WebBluetoothDeviceId web_bluetooth_device_id(
*device.FindStringKey(kWebBluetoothDeviceIdKey));
if (device_id == web_bluetooth_device_id)
return !device.FindDictKey(kServicesKey)->DictEmpty();
}
return false;
}
......@@ -66,32 +265,27 @@ bool BluetoothChooserContext::IsAllowedToAccessService(
const url::Origin& embedding_origin,
const WebBluetoothDeviceId& device_id,
BluetoothUUID service) {
NOTIMPLEMENTED();
return false;
}
const std::vector<std::unique_ptr<ChooserContextBase::Object>> object_list =
GetGrantedObjects(requesting_origin, embedding_origin);
for (const auto& object : object_list) {
const base::Value& device = object->value;
DCHECK(IsValidObject(device));
bool BluetoothChooserContext::IsValidObject(const base::Value& object) {
NOTIMPLEMENTED();
const WebBluetoothDeviceId web_bluetooth_device_id(
*device.FindStringKey(kWebBluetoothDeviceIdKey));
if (device_id == web_bluetooth_device_id) {
const auto& services_dict = *device.FindDictKey(kServicesKey);
return !!services_dict.FindKey(service.canonical_value());
}
}
return false;
}
std::vector<std::unique_ptr<ChooserContextBase::Object>>
BluetoothChooserContext::GetGrantedObjects(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin) {
NOTIMPLEMENTED();
return {};
}
std::vector<std::unique_ptr<ChooserContextBase::Object>>
BluetoothChooserContext::GetAllGrantedObjects() {
NOTIMPLEMENTED();
return {};
}
void BluetoothChooserContext::RevokeObjectPermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const base::Value& object) {
NOTIMPLEMENTED();
bool BluetoothChooserContext::IsValidObject(const base::Value& object) {
return object.FindStringKey(kDeviceAddressKey) &&
object.FindStringKey(kDeviceNameKey) &&
object.FindStringKey(kWebBluetoothDeviceIdKey) &&
WebBluetoothDeviceId::IsValid(
*object.FindStringKey(kWebBluetoothDeviceIdKey)) &&
object.FindDictKey(kServicesKey);
}
......@@ -5,12 +5,16 @@
#ifndef CHROME_BROWSER_BLUETOOTH_BLUETOOTH_CHOOSER_CONTEXT_H_
#define CHROME_BROWSER_BLUETOOTH_BLUETOOTH_CHOOSER_CONTEXT_H_
#include <map>
#include <string>
#include "base/containers/flat_set.h"
#include "chrome/browser/permissions/chooser_context_base.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "third_party/blink/public/common/bluetooth/web_bluetooth_device_id.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom-forward.h"
namespace base {
class Value;
......@@ -37,22 +41,27 @@ class BluetoothChooserContext : public ChooserContextBase {
// Helper methods for converting between a WebBluetoothDeviceId and a
// Bluetooth device address string for a given origin pair.
const blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
blink::WebBluetoothDeviceId GetWebBluetoothDeviceId(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const std::string& device_address);
const std::string GetDeviceAddress(
std::string GetDeviceAddress(const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const blink::WebBluetoothDeviceId& device_id);
// Bluetooth scanning specific interface for generating WebBluetoothDeviceIds
// for scanned devices.
blink::WebBluetoothDeviceId AddScannedDevice(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const blink::WebBluetoothDeviceId& device_id);
const std::string& device_address);
// Bluetooth-specific interface for granting and checking permissions.
const blink::WebBluetoothDeviceId GrantDevicePermission(
blink::WebBluetoothDeviceId GrantServiceAccessPermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const std::string& device_address,
base::flat_set<device::BluetoothUUID, device::BluetoothUUIDHash>&
services);
const device::BluetoothDevice* device,
const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options);
bool HasDevicePermission(const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const blink::WebBluetoothDeviceId& device_id);
......@@ -68,13 +77,15 @@ class BluetoothChooserContext : public ChooserContextBase {
protected:
// ChooserContextBase implementation;
bool IsValidObject(const base::Value& object) override;
std::vector<std::unique_ptr<Object>> GetGrantedObjects(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin) override;
std::vector<std::unique_ptr<Object>> GetAllGrantedObjects() override;
void RevokeObjectPermission(const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const base::Value& object) override;
private:
// This map records the generated Web Bluetooth IDs for devices discovered via
// the Scanning API. Each requesting/embedding origin pair has its own version
// of this map so that IDs cannot be correlated between cross-origin sites.
using DeviceAddressToIdMap =
std::map<std::string, blink::WebBluetoothDeviceId>;
std::map<std::pair<url::Origin, url::Origin>, DeviceAddressToIdMap>
scanned_devices_;
};
#endif // CHROME_BROWSER_BLUETOOTH_BLUETOOTH_CHOOSER_CONTEXT_H_
// Copyright 2020 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 <memory>
#include <utility>
#include <vector>
#include "base/containers/flat_set.h"
#include "chrome/browser/bluetooth/bluetooth_chooser_context.h"
#include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/chooser_context_base.h"
#include "chrome/browser/permissions/chooser_context_base_mock_permission_observer.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "content/public/test/browser_task_environment.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
using blink::mojom::WebBluetoothRequestDeviceOptionsPtr;
using device::BluetoothUUID;
using device::BluetoothUUIDHash;
namespace {
constexpr char kDeviceAddressKey[] = "device-address";
constexpr char kDeviceNameKey[] = "name";
constexpr char kServicesKey[] = "services";
constexpr char kWebBluetoothDeviceIdKey[] = "web-bluetooth-device-id";
const uint32_t kGamepadBluetoothClass = 0x0508;
constexpr char kDeviceAddress1[] = "00:00:00:00:00:00";
constexpr char kDeviceAddress2[] = "11:11:11:11:11:11";
constexpr char kGlucoseUUIDString[] = "00001808-0000-1000-8000-00805f9b34fb";
constexpr char kHeartRateUUIDString[] = "0000180d-0000-1000-8000-00805f9b34fb";
constexpr char kBatteryServiceUUIDString[] =
"0000180f-0000-1000-8000-00805f9b34fb";
constexpr char kBloodPressureUUIDString[] =
"00001813-0000-1000-8000-00805f9b34fb";
constexpr char kCyclingPowerUUIDString[] =
"00001818-0000-1000-8000-00805f9b34fb";
const BluetoothUUID kGlucoseUUID(kGlucoseUUIDString);
const BluetoothUUID kHeartRateUUID(kHeartRateUUIDString);
const BluetoothUUID kBatteryServiceUUID(kBatteryServiceUUIDString);
const BluetoothUUID kBloodPressureUUID(kBloodPressureUUIDString);
const BluetoothUUID kCyclingPowerUUID(kCyclingPowerUUIDString);
WebBluetoothRequestDeviceOptionsPtr CreateOptionsForServices(
const std::vector<BluetoothUUID>& filter_services,
const std::vector<BluetoothUUID>& optional_services) {
auto filter = blink::mojom::WebBluetoothLeScanFilter::New();
filter->services = filter_services;
std::vector<blink::mojom::WebBluetoothLeScanFilterPtr> scan_filters;
scan_filters.push_back(std::move(filter));
auto options = blink::mojom::WebBluetoothRequestDeviceOptions::New();
options->filters = std::move(scan_filters);
options->optional_services = optional_services;
return options;
}
WebBluetoothRequestDeviceOptionsPtr CreateOptionsForServices(
const std::vector<BluetoothUUID>& filter_services) {
return CreateOptionsForServices(filter_services, {});
}
} // namespace
class FakeBluetoothAdapter : public device::MockBluetoothAdapter {
public:
FakeBluetoothAdapter() = default;
// Move-only class.
FakeBluetoothAdapter(const FakeBluetoothAdapter&) = delete;
FakeBluetoothAdapter& operator=(const FakeBluetoothAdapter&) = delete;
private:
~FakeBluetoothAdapter() override = default;
};
class FakeBluetoothDevice : public device::MockBluetoothDevice {
public:
FakeBluetoothDevice(device::MockBluetoothAdapter* adapter,
const char* name,
const std::string& address)
: device::MockBluetoothDevice(adapter,
kGamepadBluetoothClass,
name,
address,
/*paired=*/true,
/*connected=*/true) {}
// Move-only class.
FakeBluetoothDevice(const FakeBluetoothDevice&) = delete;
FakeBluetoothDevice& operator=(const FakeBluetoothDevice&) = delete;
};
class BluetoothChooserContextTest : public testing::Test {
public:
BluetoothChooserContextTest()
: foo_url_("https://foo.com"),
bar_url_("https://bar.com"),
foo_origin_(url::Origin::Create(foo_url_)),
bar_origin_(url::Origin::Create(bar_url_)) {}
~BluetoothChooserContextTest() override = default;
// Move-only class.
BluetoothChooserContextTest(const BluetoothChooserContextTest&) = delete;
BluetoothChooserContextTest& operator=(const BluetoothChooserContextTest&) =
delete;
void SetUp() override {
fake_adapter_ = base::MakeRefCounted<FakeBluetoothAdapter>();
fake_device1_ = GetBluetoothDevice("Wireless Gizmo", kDeviceAddress1);
fake_device2_ = GetBluetoothDevice("Wireless Gadget", kDeviceAddress2);
}
protected:
Profile* profile() { return &profile_; }
BluetoothChooserContext* GetChooserContext(Profile* profile) {
auto* chooser_context =
BluetoothChooserContextFactory::GetForProfile(profile);
chooser_context->AddObserver(&mock_permission_observer_);
return chooser_context;
}
std::unique_ptr<FakeBluetoothDevice> GetBluetoothDevice(const char* name,
std::string address) {
return std::make_unique<FakeBluetoothDevice>(fake_adapter_.get(), name,
address);
}
// Mock Observer
MockPermissionObserver mock_permission_observer_;
const GURL foo_url_;
const GURL bar_url_;
const url::Origin foo_origin_;
const url::Origin bar_origin_;
std::unique_ptr<FakeBluetoothDevice> fake_device1_;
std::unique_ptr<FakeBluetoothDevice> fake_device2_;
private:
content::BrowserTaskEnvironment task_environment_;
scoped_refptr<FakeBluetoothAdapter> fake_adapter_;
TestingProfile profile_;
};
// Check that Web Bluetooth device permissions are granted and revoked properly,
// and that the WebBluetoothDeviceId and device address can be retrieved using
// each other.
TEST_F(BluetoothChooserContextTest, CheckGrantAndRevokePermission) {
const std::vector<BluetoothUUID> services = {kGlucoseUUID,
kBloodPressureUUID};
WebBluetoothRequestDeviceOptionsPtr options =
CreateOptionsForServices(services);
BluetoothChooserContext* context = GetChooserContext(profile());
EXPECT_FALSE(context
->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
fake_device1_->GetAddress())
.IsValid());
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
blink::WebBluetoothDeviceId device_id = context->GrantServiceAccessPermission(
foo_origin_, foo_origin_, fake_device1_.get(), options);
EXPECT_TRUE(
context->HasDevicePermission(foo_origin_, foo_origin_, device_id));
EXPECT_EQ(context->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
fake_device1_->GetAddress()),
device_id);
EXPECT_EQ(context->GetDeviceAddress(foo_origin_, foo_origin_, device_id),
fake_device1_->GetAddress());
EXPECT_TRUE(context->IsAllowedToAccessAtLeastOneService(
foo_origin_, foo_origin_, device_id));
for (const auto& service : services) {
EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
device_id, service));
}
base::Value expected_object(base::Value::Type::DICTIONARY);
expected_object.SetStringKey(kDeviceAddressKey, kDeviceAddress1);
expected_object.SetStringKey(kDeviceNameKey,
fake_device1_->GetNameForDisplay());
expected_object.SetStringKey(kWebBluetoothDeviceIdKey, device_id.str());
base::Value expected_services(base::Value::Type::DICTIONARY);
expected_services.SetBoolKey(kGlucoseUUIDString, /*val=*/true);
expected_services.SetBoolKey(kBloodPressureUUIDString, /*val=*/true);
expected_object.SetKey(kServicesKey, std::move(expected_services));
std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
context->GetGrantedObjects(foo_origin_, foo_origin_);
ASSERT_EQ(1u, origin_objects.size());
EXPECT_EQ(expected_object, origin_objects[0]->value);
EXPECT_FALSE(origin_objects[0]->incognito);
std::vector<std::unique_ptr<ChooserContextBase::Object>> all_origin_objects =
context->GetAllGrantedObjects();
ASSERT_EQ(1u, all_origin_objects.size());
EXPECT_EQ(foo_origin_.GetURL(), all_origin_objects[0]->requesting_origin);
EXPECT_EQ(foo_origin_.GetURL(), all_origin_objects[0]->embedding_origin);
EXPECT_EQ(expected_object, all_origin_objects[0]->value);
EXPECT_FALSE(all_origin_objects[0]->incognito);
testing::Mock::VerifyAndClearExpectations(&mock_permission_observer_);
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
EXPECT_CALL(mock_permission_observer_,
OnPermissionRevoked(foo_origin_, foo_origin_));
context->RevokeObjectPermission(foo_origin_, foo_origin_,
origin_objects[0]->value);
EXPECT_FALSE(
context->HasDevicePermission(foo_origin_, foo_origin_, device_id));
EXPECT_FALSE(context
->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
fake_device1_->GetAddress())
.IsValid());
origin_objects = context->GetGrantedObjects(foo_origin_, foo_origin_);
EXPECT_EQ(0u, origin_objects.size());
all_origin_objects = context->GetAllGrantedObjects();
EXPECT_EQ(0u, all_origin_objects.size());
}
// Check that Web Bluetooth permissions granted in incognito mode remain only
// in the incognito session.
TEST_F(BluetoothChooserContextTest, GrantPermissionInIncognito) {
const std::vector<BluetoothUUID> services{kGlucoseUUID, kBloodPressureUUID};
WebBluetoothRequestDeviceOptionsPtr options =
CreateOptionsForServices(services);
BluetoothChooserContext* context = GetChooserContext(profile());
BluetoothChooserContext* incognito_context =
GetChooserContext(profile()->GetOffTheRecordProfile());
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
blink::WebBluetoothDeviceId device_id = context->GrantServiceAccessPermission(
foo_origin_, foo_origin_, fake_device1_.get(), options);
EXPECT_TRUE(
context->HasDevicePermission(foo_origin_, foo_origin_, device_id));
EXPECT_EQ(device_id,
context->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
fake_device1_->GetAddress()));
EXPECT_EQ(context->GetDeviceAddress(foo_origin_, foo_origin_, device_id),
fake_device1_->GetAddress());
EXPECT_TRUE(context->IsAllowedToAccessAtLeastOneService(
foo_origin_, foo_origin_, device_id));
for (const auto& service : services) {
EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
device_id, service));
}
EXPECT_FALSE(incognito_context->HasDevicePermission(foo_origin_, foo_origin_,
device_id));
EXPECT_FALSE(incognito_context
->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
fake_device1_->GetAddress())
.IsValid());
testing::Mock::VerifyAndClearExpectations(&mock_permission_observer_);
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
blink::WebBluetoothDeviceId incognito_device_id =
incognito_context->GrantServiceAccessPermission(
foo_origin_, foo_origin_, fake_device1_.get(), options);
EXPECT_FALSE(context->HasDevicePermission(foo_origin_, foo_origin_,
incognito_device_id));
EXPECT_NE(incognito_device_id,
context->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
fake_device1_->GetAddress()));
EXPECT_TRUE(incognito_context->HasDevicePermission(foo_origin_, foo_origin_,
incognito_device_id));
EXPECT_EQ(incognito_device_id,
incognito_context->GetWebBluetoothDeviceId(
foo_origin_, foo_origin_, fake_device1_->GetAddress()));
EXPECT_EQ(incognito_context->GetDeviceAddress(foo_origin_, foo_origin_,
incognito_device_id),
fake_device1_->GetAddress());
EXPECT_TRUE(incognito_context->IsAllowedToAccessAtLeastOneService(
foo_origin_, foo_origin_, incognito_device_id));
for (const auto& service : services) {
EXPECT_TRUE(incognito_context->IsAllowedToAccessService(
foo_origin_, foo_origin_, incognito_device_id, service));
}
{
std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
context->GetGrantedObjects(foo_origin_, foo_origin_);
EXPECT_EQ(1u, origin_objects.size());
std::vector<std::unique_ptr<ChooserContextBase::Object>>
all_origin_objects = context->GetAllGrantedObjects();
ASSERT_EQ(1u, all_origin_objects.size());
EXPECT_FALSE(all_origin_objects[0]->incognito);
}
{
std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
incognito_context->GetGrantedObjects(foo_origin_, foo_origin_);
EXPECT_EQ(1u, origin_objects.size());
// GetAllGrantedObjects() on an incognito session also returns the
// permission objects granted in the non-incognito session.
std::vector<std::unique_ptr<ChooserContextBase::Object>>
all_origin_objects = incognito_context->GetAllGrantedObjects();
ASSERT_EQ(2u, all_origin_objects.size());
EXPECT_TRUE(all_origin_objects[0]->incognito ^
all_origin_objects[1]->incognito);
}
}
// Check that granting device permission with new services updates the
// permission.
TEST_F(BluetoothChooserContextTest, CheckGrantWithServiceUpdates) {
const std::vector<BluetoothUUID> services1{kGlucoseUUID, kBloodPressureUUID};
WebBluetoothRequestDeviceOptionsPtr options1 =
CreateOptionsForServices(services1);
BluetoothChooserContext* context = GetChooserContext(profile());
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
blink::WebBluetoothDeviceId device_id1 =
context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
fake_device1_.get(), options1);
EXPECT_TRUE(context->IsAllowedToAccessAtLeastOneService(
foo_origin_, foo_origin_, device_id1));
for (const auto& service : services1) {
EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
device_id1, service));
}
const std::vector<BluetoothUUID> services2{kHeartRateUUID, kBloodPressureUUID,
kCyclingPowerUUID};
WebBluetoothRequestDeviceOptionsPtr options2 =
CreateOptionsForServices(services2);
testing::Mock::VerifyAndClearExpectations(&mock_permission_observer_);
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
blink::WebBluetoothDeviceId device_id2 =
context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
fake_device1_.get(), options2);
EXPECT_EQ(device_id2, device_id1);
base::flat_set<BluetoothUUID> services_set(services1);
services_set.insert(services2.begin(), services2.end());
for (const auto& service : services_set) {
EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
device_id2, service));
}
}
// Check that permissions are granted to the union of filtered and optional
// services.
TEST_F(BluetoothChooserContextTest, CheckGrantWithOptionalServices) {
const std::vector<BluetoothUUID> services{kGlucoseUUID, kBloodPressureUUID};
const std::vector<BluetoothUUID> optional_services{kBatteryServiceUUID};
WebBluetoothRequestDeviceOptionsPtr options =
CreateOptionsForServices(services, optional_services);
BluetoothChooserContext* context = GetChooserContext(profile());
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
blink::WebBluetoothDeviceId device_id = context->GrantServiceAccessPermission(
foo_origin_, foo_origin_, fake_device1_.get(), options);
EXPECT_TRUE(context->IsAllowedToAccessAtLeastOneService(
foo_origin_, foo_origin_, device_id));
for (const auto& service : services) {
EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
device_id, service));
}
for (const auto& service : optional_services) {
EXPECT_TRUE(context->IsAllowedToAccessService(foo_origin_, foo_origin_,
device_id, service));
}
}
// Check that the Bluetooth guard permission prevents Web Bluetooth from being
// used even if permissions exist for a pair of origins.
TEST_F(BluetoothChooserContextTest, BluetoothGuardPermission) {
const std::vector<BluetoothUUID> services1{kGlucoseUUID, kBloodPressureUUID};
WebBluetoothRequestDeviceOptionsPtr options1 =
CreateOptionsForServices(services1);
const std::vector<BluetoothUUID> services2{kHeartRateUUID, kCyclingPowerUUID};
WebBluetoothRequestDeviceOptionsPtr options2 =
CreateOptionsForServices(services2);
auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
map->SetContentSettingDefaultScope(
foo_url_, foo_url_, ContentSettingsType::BLUETOOTH_GUARD,
/*resource_identifier=*/std::string(), CONTENT_SETTING_BLOCK);
BluetoothChooserContext* context = GetChooserContext(profile());
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA))
.Times(4);
blink::WebBluetoothDeviceId foo_device_id1 =
context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
fake_device1_.get(), options1);
blink::WebBluetoothDeviceId foo_device_id2 =
context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
fake_device1_.get(), options2);
blink::WebBluetoothDeviceId bar_device_id1 =
context->GrantServiceAccessPermission(bar_origin_, bar_origin_,
fake_device1_.get(), options1);
blink::WebBluetoothDeviceId bar_device_id2 =
context->GrantServiceAccessPermission(bar_origin_, bar_origin_,
fake_device2_.get(), options2);
{
std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
context->GetGrantedObjects(foo_origin_, foo_origin_);
EXPECT_EQ(0u, origin_objects.size());
}
{
std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
context->GetGrantedObjects(bar_origin_, bar_origin_);
EXPECT_EQ(2u, origin_objects.size());
}
std::vector<std::unique_ptr<ChooserContextBase::Object>> all_origin_objects =
context->GetAllGrantedObjects();
EXPECT_EQ(2u, all_origin_objects.size());
for (const auto& object : all_origin_objects) {
EXPECT_EQ(object->requesting_origin, bar_origin_.GetURL());
EXPECT_EQ(object->embedding_origin, bar_origin_.GetURL());
}
EXPECT_FALSE(
context->HasDevicePermission(foo_origin_, foo_origin_, foo_device_id1));
EXPECT_FALSE(
context->HasDevicePermission(foo_origin_, foo_origin_, foo_device_id2));
EXPECT_TRUE(
context->HasDevicePermission(bar_origin_, bar_origin_, bar_device_id1));
EXPECT_TRUE(
context->HasDevicePermission(bar_origin_, bar_origin_, bar_device_id2));
}
// Check that a valid WebBluetoothDeviceId is produced for Bluetooth LE
// scanned devices. When permission is granted to one of these devices, the
// previously generated WebBluetoothDeviceId should be remembered.
TEST_F(BluetoothChooserContextTest, BluetoothLEScannedDevices) {
BluetoothChooserContext* context = GetChooserContext(profile());
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA))
.Times(0);
blink::WebBluetoothDeviceId scanned_id = context->AddScannedDevice(
foo_origin_, foo_origin_, fake_device1_->GetAddress());
EXPECT_EQ(scanned_id,
context->GetWebBluetoothDeviceId(foo_origin_, foo_origin_,
fake_device1_->GetAddress()));
EXPECT_EQ(fake_device1_->GetAddress(),
context->GetDeviceAddress(foo_origin_, foo_origin_, scanned_id));
EXPECT_FALSE(
context->HasDevicePermission(foo_origin_, foo_origin_, scanned_id));
EXPECT_FALSE(context->IsAllowedToAccessAtLeastOneService(
foo_origin_, foo_origin_, scanned_id));
const std::vector<BluetoothUUID> services{kGlucoseUUID, kBloodPressureUUID};
WebBluetoothRequestDeviceOptionsPtr options =
CreateOptionsForServices(services);
testing::Mock::VerifyAndClearExpectations(&mock_permission_observer_);
EXPECT_CALL(mock_permission_observer_,
OnChooserObjectPermissionChanged(
ContentSettingsType::BLUETOOTH_GUARD,
ContentSettingsType::BLUETOOTH_CHOOSER_DATA));
blink::WebBluetoothDeviceId granted_id =
context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
fake_device1_.get(), options);
EXPECT_EQ(scanned_id, granted_id);
}
// Granted devices should return the same ID when detected via a Bluetooth LE
// scan. If the permission is revoked, then a new ID should be generated for the
// device when detected via a Bluetooth LE scan.
TEST_F(BluetoothChooserContextTest, BluetoothLEScanWithGrantedDevices) {
const std::vector<BluetoothUUID> services{kGlucoseUUID, kBloodPressureUUID};
WebBluetoothRequestDeviceOptionsPtr options =
CreateOptionsForServices(services);
BluetoothChooserContext* context = GetChooserContext(profile());
blink::WebBluetoothDeviceId granted_id =
context->GrantServiceAccessPermission(foo_origin_, foo_origin_,
fake_device1_.get(), options);
blink::WebBluetoothDeviceId scanned_id = context->AddScannedDevice(
foo_origin_, foo_origin_, fake_device1_->GetAddress());
EXPECT_EQ(granted_id, scanned_id);
std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
context->GetGrantedObjects(foo_origin_, foo_origin_);
ASSERT_EQ(1u, origin_objects.size());
context->RevokeObjectPermission(foo_origin_, foo_origin_,
origin_objects[0]->value);
scanned_id = context->AddScannedDevice(foo_origin_, foo_origin_,
fake_device1_->GetAddress());
EXPECT_NE(scanned_id, granted_id);
EXPECT_FALSE(
context->HasDevicePermission(foo_origin_, foo_origin_, scanned_id));
EXPECT_FALSE(
context->HasDevicePermission(foo_origin_, foo_origin_, granted_id));
}
......@@ -153,6 +153,27 @@ void ChooserContextBase::GrantObjectPermission(
NotifyPermissionChanged();
}
void ChooserContextBase::UpdateObjectPermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
base::Value& old_object,
base::Value new_object) {
base::Value setting =
GetWebsiteSetting(requesting_origin, embedding_origin, /*info=*/nullptr);
base::Value* objects = setting.FindListKey(kObjectListKey);
if (!objects)
return;
base::Value::ListView object_list = objects->GetList();
auto it = std::find(object_list.begin(), object_list.end(), old_object);
if (it == object_list.end())
return;
*it = std::move(new_object);
SetWebsiteSetting(requesting_origin, embedding_origin, std::move(setting));
NotifyPermissionChanged();
}
void ChooserContextBase::RevokeObjectPermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
......
......@@ -98,6 +98,14 @@ class ChooserContextBase : public KeyedService {
const url::Origin& embedding_origin,
base::Value object);
// Updates |old_object| with |new_object| for |requesting_origin| when
// embedded within |embedding_origin|, and writes the value into
// |host_content_settings_map_|.
void UpdateObjectPermission(const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
base::Value& old_object,
base::Value new_object);
// Revokes |requesting_origin|'s permission to access |object| when embedded
// within |embedding_origin|.
//
......
......@@ -141,6 +141,58 @@ TEST_F(ChooserContextBaseTest, GrantObjectPermissionEmbedded) {
EXPECT_EQ(0u, objects.size());
}
TEST_F(ChooserContextBaseTest, GrantAndUpdateObjectPermission) {
TestChooserContext context(profile());
MockPermissionObserver mock_observer;
context.AddObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _));
context.GrantObjectPermission(origin1_, origin1_, object1_.Clone());
std::vector<std::unique_ptr<ChooserContextBase::Object>> objects =
context.GetGrantedObjects(origin1_, origin1_);
EXPECT_EQ(1u, objects.size());
EXPECT_EQ(object1_, objects[0]->value);
testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _));
context.UpdateObjectPermission(origin1_, origin1_, objects[0]->value,
object2_.Clone());
objects = context.GetGrantedObjects(origin1_, origin1_);
EXPECT_EQ(1u, objects.size());
EXPECT_EQ(object2_, objects[0]->value);
}
// UpdateObjectPermission() should not grant new permissions.
TEST_F(ChooserContextBaseTest,
UpdateObjectPermissionWithNonExistentPermission) {
TestChooserContext context(profile());
MockPermissionObserver mock_observer;
context.AddObserver(&mock_observer);
// Attempt to update permission for non-existent |object1_| permission.
EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _)).Times(0);
context.UpdateObjectPermission(origin1_, origin1_, object1_,
object2_.Clone());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
std::vector<std::unique_ptr<ChooserContextBase::Object>> objects =
context.GetGrantedObjects(origin1_, origin1_);
EXPECT_TRUE(objects.empty());
// Grant permission for |object2_| but attempt to update permission for
// non-existent |object1_| permission again.
EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _));
context.GrantObjectPermission(origin1_, origin1_, object2_.Clone());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_CALL(mock_observer, OnChooserObjectPermissionChanged(_, _)).Times(0);
context.UpdateObjectPermission(origin1_, origin1_, object1_,
object2_.Clone());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
}
TEST_F(ChooserContextBaseTest, GetAllGrantedObjects) {
TestChooserContext context(profile());
MockPermissionObserver mock_observer;
......
......@@ -3025,6 +3025,7 @@ test("unit_tests") {
"../browser/background_sync/periodic_background_sync_permission_context_unittest.cc",
"../browser/banners/app_banner_settings_helper_unittest.cc",
"../browser/bitmap_fetcher/bitmap_fetcher_service_unittest.cc",
"../browser/bluetooth/bluetooth_chooser_context_unittest.cc",
"../browser/bookmarks/managed_bookmark_service_unittest.cc",
"../browser/browser_about_handler_unittest.cc",
"../browser/browsing_data/browsing_data_appcache_helper_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment