blob: 4f79e478dac1782e2b5da90bd8df631548c567b8 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/hid/hid_chooser_context.h"
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "content/public/browser/device_service.h"
namespace {
constexpr char kHidDeviceNameKey[] = "name";
constexpr char kHidGuidKey[] = "guid";
base::Value DeviceInfoToValue(const device::mojom::HidDeviceInfo& device) {
base::Value value(base::Value::Type::DICTIONARY);
value.SetStringKey(kHidDeviceNameKey, device.product_name);
// The GUID is a temporary ID created on connection that remains valid until
// the device is disconnected. Ephemeral permissions are keyed by this ID and
// must be granted again each time the device is connected.
// TODO(crbug.com/958918): Extract a persistent identifier to allow device
// permissions to be retained after the device is disconnected.
value.SetStringKey(kHidGuidKey, device.guid);
return value;
}
} // namespace
HidChooserContext::HidChooserContext(Profile* profile)
: ChooserContextBase(ContentSettingsType::HID_GUARD,
ContentSettingsType::HID_CHOOSER_DATA,
HostContentSettingsMapFactory::GetForProfile(profile)),
is_incognito_(profile->IsOffTheRecord()) {}
HidChooserContext::~HidChooserContext() = default;
base::string16 HidChooserContext::GetObjectDisplayName(
const base::Value& object) {
const std::string* name = object.FindStringKey(kHidDeviceNameKey);
DCHECK(name);
return base::UTF8ToUTF16(*name);
}
bool HidChooserContext::IsValidObject(const base::Value& object) {
if (!object.is_dict() || object.DictSize() != 2 ||
!object.FindStringKey(kHidDeviceNameKey)) {
return false;
}
const std::string* guid = object.FindStringKey(kHidGuidKey);
return guid && !guid->empty();
}
std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
HidChooserContext::GetGrantedObjects(const url::Origin& requesting_origin,
const url::Origin& embedding_origin) {
// TODO(crbug.com/958918): Include devices with persistent permissions in the
// returned list.
if (!CanRequestObjectPermission(requesting_origin, embedding_origin))
return {};
auto origin_it = ephemeral_devices_.find(
std::make_pair(requesting_origin, embedding_origin));
if (origin_it == ephemeral_devices_.end())
return {};
const std::set<std::string> devices = origin_it->second;
std::vector<std::unique_ptr<Object>> objects;
for (const auto& guid : devices) {
auto it = device_info_.find(guid);
if (it == device_info_.end())
continue;
objects.push_back(std::make_unique<Object>(
requesting_origin, embedding_origin, it->second.Clone(),
content_settings::SettingSource::SETTING_SOURCE_USER, is_incognito_));
}
return objects;
}
std::vector<std::unique_ptr<permissions::ChooserContextBase::Object>>
HidChooserContext::GetAllGrantedObjects() {
// TODO(crbug.com/958918): Include devices with persistent permissions in the
// returned list.
std::vector<std::unique_ptr<Object>> objects;
for (const auto& map_entry : ephemeral_devices_) {
const url::Origin& requesting_origin = map_entry.first.first;
const url::Origin& embedding_origin = map_entry.first.second;
if (!CanRequestObjectPermission(requesting_origin, embedding_origin))
continue;
for (const auto& guid : map_entry.second) {
auto it = device_info_.find(guid);
if (it == device_info_.end())
continue;
objects.push_back(std::make_unique<Object>(
requesting_origin, embedding_origin, it->second.Clone(),
content_settings::SettingSource::SETTING_SOURCE_USER, is_incognito_));
}
}
return objects;
}
void HidChooserContext::RevokeObjectPermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const base::Value& object) {
// TODO(crbug.com/958918): Revoke persistent permissions if the device has a
// persistent ID.
auto origin_it = ephemeral_devices_.find(
std::make_pair(requesting_origin, embedding_origin));
if (origin_it == ephemeral_devices_.end())
return;
std::set<std::string>& devices = origin_it->second;
DCHECK(IsValidObject(object));
devices.erase(*object.FindStringKey(kHidGuidKey));
NotifyPermissionRevoked(requesting_origin, embedding_origin);
}
void HidChooserContext::GrantDevicePermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const device::mojom::HidDeviceInfo& device) {
// TODO(crbug.com/958918): Grant persistent permissions for eligible devices.
ephemeral_devices_[std::make_pair(requesting_origin, embedding_origin)]
.insert(device.guid);
device_info_[device.guid] = DeviceInfoToValue(device);
NotifyPermissionChanged();
}
bool HidChooserContext::HasDevicePermission(
const url::Origin& requesting_origin,
const url::Origin& embedding_origin,
const device::mojom::HidDeviceInfo& device) {
// TODO(crbug.com/958918): Also check if a persistent permission was granted
// for |device|.
if (!CanRequestObjectPermission(requesting_origin, embedding_origin)) {
return false;
}
auto origin_it = ephemeral_devices_.find(
std::make_pair(requesting_origin, embedding_origin));
if (origin_it == ephemeral_devices_.end())
return false;
const std::set<std::string> devices = origin_it->second;
auto device_it = devices.find(device.guid);
return device_it != devices.end();
}
device::mojom::HidManager* HidChooserContext::GetHidManager() {
EnsureHidManagerConnection();
return hid_manager_.get();
}
void HidChooserContext::SetHidManagerForTesting(
mojo::PendingRemote<device::mojom::HidManager> manager) {
SetUpHidManagerConnection(std::move(manager));
}
base::WeakPtr<HidChooserContext> HidChooserContext::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void HidChooserContext::EnsureHidManagerConnection() {
if (hid_manager_)
return;
mojo::PendingRemote<device::mojom::HidManager> manager;
content::GetDeviceService().BindHidManager(
manager.InitWithNewPipeAndPassReceiver());
SetUpHidManagerConnection(std::move(manager));
}
void HidChooserContext::SetUpHidManagerConnection(
mojo::PendingRemote<device::mojom::HidManager> manager) {
hid_manager_.Bind(std::move(manager));
hid_manager_.set_disconnect_handler(base::BindOnce(
&HidChooserContext::OnHidManagerConnectionError, base::Unretained(this)));
// TODO(mattreynolds): Register a HidManagerClient to be notified when devices
// are disconnected so that ephemeral permissions can be revoked.
}
void HidChooserContext::OnHidManagerConnectionError() {
device_info_.clear();
std::vector<std::pair<url::Origin, url::Origin>> revoked_origins;
revoked_origins.reserve(ephemeral_devices_.size());
for (const auto& map_entry : ephemeral_devices_)
revoked_origins.push_back(map_entry.first);
ephemeral_devices_.clear();
// Notify permission observers that all ephemeral permissions have been
// revoked.
for (auto& observer : permission_observer_list_) {
observer.OnChooserObjectPermissionChanged(guard_content_settings_type_,
data_content_settings_type_);
for (const auto& origin : revoked_origins)
observer.OnPermissionRevoked(origin.first, origin.second);
}
}