Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Show FIDO devices in the chooser if allowed #40275

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 21 additions & 6 deletions shell/browser/hid/electron_hid_delegate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ namespace {

electron::HidChooserContext* GetChooserContext(
content::BrowserContext* browser_context) {
if (!browser_context)
return nullptr;
return electron::HidChooserContextFactory::GetForBrowserContext(
browser_context);
}
Expand Down Expand Up @@ -110,6 +112,7 @@ std::unique_ptr<content::HidChooser> ElectronHidDelegate::RunChooser(
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
std::vector<blink::mojom::HidDeviceFilterPtr> exclusion_filters,
content::HidChooser::Callback callback) {
DCHECK(render_frame_host);
auto* browser_context = render_frame_host->GetBrowserContext();

// Start observing HidChooserContext for permission and device events.
Expand All @@ -133,6 +136,9 @@ std::unique_ptr<content::HidChooser> ElectronHidDelegate::RunChooser(
bool ElectronHidDelegate::CanRequestDevicePermission(
content::BrowserContext* browser_context,
const url::Origin& origin) {
if (!browser_context)
return false;

base::Value::Dict details;
details.Set("securityOrigin", origin.GetURL().spec());
auto* permission_manager = static_cast<ElectronPermissionManager*>(
Expand All @@ -147,31 +153,38 @@ bool ElectronHidDelegate::HasDevicePermission(
content::BrowserContext* browser_context,
const url::Origin& origin,
const device::mojom::HidDeviceInfo& device) {
return GetChooserContext(browser_context)
->HasDevicePermission(origin, device);
return browser_context && GetChooserContext(browser_context)
->HasDevicePermission(origin, device);
}

void ElectronHidDelegate::RevokeDevicePermission(
content::BrowserContext* browser_context,
const url::Origin& origin,
const device::mojom::HidDeviceInfo& device) {
return GetChooserContext(browser_context)
->RevokeDevicePermission(origin, device);
if (browser_context) {
GetChooserContext(browser_context)->RevokeDevicePermission(origin, device);
}
}

device::mojom::HidManager* ElectronHidDelegate::GetHidManager(
content::BrowserContext* browser_context) {
if (!browser_context)
return nullptr;
return GetChooserContext(browser_context)->GetHidManager();
}

void ElectronHidDelegate::AddObserver(content::BrowserContext* browser_context,
Observer* observer) {
if (!browser_context)
return;
GetContextObserver(browser_context)->AddObserver(observer);
}

void ElectronHidDelegate::RemoveObserver(
content::BrowserContext* browser_context,
content::HidDelegate::Observer* observer) {
if (!browser_context)
return;
DCHECK(base::Contains(observations_, browser_context));
GetContextObserver(browser_context)->RemoveObserver(observer);
}
Expand All @@ -180,14 +193,16 @@ const device::mojom::HidDeviceInfo* ElectronHidDelegate::GetDeviceInfo(
content::BrowserContext* browser_context,
const std::string& guid) {
auto* chooser_context = GetChooserContext(browser_context);
if (!chooser_context)
return nullptr;
return chooser_context->GetDeviceInfo(guid);
}

bool ElectronHidDelegate::IsFidoAllowedForOrigin(
content::BrowserContext* browser_context,
const url::Origin& origin) {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableHidBlocklist);
auto* chooser_context = GetChooserContext(browser_context);
return chooser_context && chooser_context->IsFidoAllowedForOrigin(origin);
}

bool ElectronHidDelegate::IsServiceWorkerAllowedForOrigin(
Expand Down
27 changes: 26 additions & 1 deletion shell/browser/hid/hid_chooser_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@
#include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"

#include "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "base/containers/fixed_flat_set.h"
#include "base/strings/string_piece.h"
#include "extensions/common/constants.h"
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)

namespace electron {

const char kHidDeviceNameKey[] = "name";
Expand Down Expand Up @@ -181,6 +186,26 @@ bool HidChooserContext::HasDevicePermission(
origin, DeviceInfoToValue(device), browser_context_);
}

bool HidChooserContext::IsFidoAllowedForOrigin(const url::Origin& origin) {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
static constexpr auto kPrivilegedExtensionIds =
base::MakeFixedFlatSet<base::StringPiece>({
"ckcendljdlmgnhghiaomidhiiclmapok", // gnubbyd-v3 dev
"lfboplenmmjcmpbkeemecobbadnmpfhi", // gnubbyd-v3 prod
});

if (origin.scheme() == extensions::kExtensionScheme &&
base::Contains(kPrivilegedExtensionIds, origin.host())) {
return true;
}
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)

// This differs from upstream - we want to allow users greater
// ability to communicate with FIDO devices in Electron.
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableHidBlocklist);
}

void HidChooserContext::AddDeviceObserver(DeviceObserver* observer) {
EnsureHidManagerConnection();
device_observer_list_.AddObserver(observer);
Expand Down
3 changes: 3 additions & 0 deletions shell/browser/hid/hid_chooser_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class HidChooserContext : public KeyedService,
bool HasDevicePermission(const url::Origin& origin,
const device::mojom::HidDeviceInfo& device);

// Returns true if `origin` is allowed to access FIDO reports.
bool IsFidoAllowedForOrigin(const url::Origin& origin);

// For ScopedObserver.
void AddDeviceObserver(DeviceObserver* observer);
void RemoveDeviceObserver(DeviceObserver* observer);
Expand Down
86 changes: 56 additions & 30 deletions shell/browser/hid/hid_chooser_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,16 @@ HidChooserController::HidChooserController(
filters_(std::move(filters)),
exclusion_filters_(std::move(exclusion_filters)),
callback_(std::move(callback)),
initiator_document_(render_frame_host->GetWeakDocumentPtr()),
origin_(content::WebContents::FromRenderFrameHost(render_frame_host)
->GetPrimaryMainFrame()
->GetLastCommittedOrigin()),
frame_tree_node_id_(render_frame_host->GetFrameTreeNodeId()),
hid_delegate_(hid_delegate),
render_frame_host_id_(render_frame_host->GetGlobalId()) {
// The use above of GetMainFrame is safe as content::HidService instances are
// not created for fenced frames.
DCHECK(!render_frame_host->IsNestedWithinFencedFrame());

chooser_context_ = HidChooserContextFactory::GetForBrowserContext(
web_contents->GetBrowserContext())
->AsWeakPtr();
Expand Down Expand Up @@ -129,6 +133,7 @@ void HidChooserController::OnDeviceAdded(
const device::mojom::HidDeviceInfo& device) {
if (!DisplayDevice(device))
return;

if (AddDeviceInfo(device)) {
api::Session* session = GetSession();
if (session) {
Expand All @@ -142,16 +147,15 @@ void HidChooserController::OnDeviceAdded(
session->Emit("hid-device-added", details);
}
}

return;
}

void HidChooserController::OnDeviceRemoved(
const device::mojom::HidDeviceInfo& device) {
if (!base::Contains(items_, PhysicalDeviceIdFromDeviceInfo(device)))
return;

if (api::Session* session = GetSession(); session != nullptr) {
api::Session* session = GetSession();
if (session) {
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
Expand Down Expand Up @@ -223,16 +227,16 @@ void HidChooserController::OnGotDevices(

for (auto& device : devices) {
if (DisplayDevice(*device)) {
if (AddDeviceInfo(*device)) {
if (AddDeviceInfo(*device))
devicesToDisplay.push_back(device->Clone());
}
}
}

// Listen to HidChooserContext for OnDeviceAdded/Removed events after the
// enumeration.
if (chooser_context_)
observation_.Observe(chooser_context_.get());

bool prevent_default = false;
api::Session* session = GetSession();
if (session) {
Expand All @@ -255,26 +259,41 @@ void HidChooserController::OnGotDevices(

bool HidChooserController::DisplayDevice(
const device::mojom::HidDeviceInfo& device) const {
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableHidBlocklist)) {
// Do not pass the device to the chooser if it is excluded by the blocklist.
if (device.is_excluded_by_blocklist)
return false;

// Do not pass the device to the chooser if it has a top-level collection
// with the FIDO usage page.
//
// Note: The HID blocklist also blocks top-level collections with the FIDO
// usage page, but will not block the device if it has other (non-FIDO)
// collections. The check below will exclude the device from the chooser
// if it has any top-level FIDO collection.
auto find_it =
std::find_if(device.collections.begin(), device.collections.end(),
[](const device::mojom::HidCollectionInfoPtr& c) {
return c->usage->usage_page == device::mojom::kPageFido;
});
if (find_it != device.collections.end())
return false;
// Check if `device` has a top-level collection with a FIDO usage. FIDO
// devices may be displayed if the origin is privileged or the blocklist is
// disabled.
const bool has_fido_collection =
base::Contains(device.collections, device::mojom::kPageFido,
[](const auto& c) { return c->usage->usage_page; });

if (has_fido_collection) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableHidBlocklist) ||
(chooser_context_ &&
chooser_context_->IsFidoAllowedForOrigin(origin_))) {
return FilterMatchesAny(device) && !IsExcluded(device);
}

AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kInfo,
base::StringPrintf(
"Chooser dialog is not displaying a FIDO HID device: vendorId=%d, "
"productId=%d, name='%s', serial='%s'",
device.vendor_id, device.product_id, device.product_name.c_str(),
device.serial_number.c_str()));
return false;
}

if (device.is_excluded_by_blocklist) {
AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kInfo,
base::StringPrintf(
"Chooser dialog is not displaying a device excluded by "
"the HID blocklist: vendorId=%d, "
"productId=%d, name='%s', serial='%s'",
device.vendor_id, device.product_id, device.product_name.c_str(),
device.serial_number.c_str()));
return false;
}

return FilterMatchesAny(device) && !IsExcluded(device);
Expand Down Expand Up @@ -303,6 +322,15 @@ bool HidChooserController::IsExcluded(
return false;
}

void HidChooserController::AddMessageToConsole(
blink::mojom::ConsoleMessageLevel level,
const std::string& message) const {
if (content::RenderFrameHost* rfh =
initiator_document_.AsRenderFrameHostIfValid()) {
rfh->AddMessageToConsole(level, message);
}
}

bool HidChooserController::AddDeviceInfo(
const device::mojom::HidDeviceInfo& device) {
const auto& id = PhysicalDeviceIdFromDeviceInfo(device);
Expand Down Expand Up @@ -340,10 +368,8 @@ void HidChooserController::UpdateDeviceInfo(
auto physical_device_it = device_map_.find(id);
DCHECK(physical_device_it != device_map_.end());
auto& device_infos = physical_device_it->second;
auto device_it = base::ranges::find_if(
device_infos, [&device](const device::mojom::HidDeviceInfoPtr& d) {
return d->guid == device.guid;
});
auto device_it = base::ranges::find(device_infos, device.guid,
&device::mojom::HidDeviceInfo::guid);
DCHECK(device_it != device_infos.end());
*device_it = device.Clone();
}
Expand Down
6 changes: 5 additions & 1 deletion shell/browser/hid/hid_chooser_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
#include "base/scoped_observation.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/hid_chooser.h"
#include "content/public/browser/weak_document_ptr.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "services/device/public/mojom/hid.mojom-forward.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/hid/electron_hid_delegate.h"
#include "shell/browser/hid/hid_chooser_context.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/mojom/hid/hid.mojom.h"

namespace content {
Expand Down Expand Up @@ -76,6 +78,8 @@ class HidChooserController
bool DisplayDevice(const device::mojom::HidDeviceInfo& device) const;
bool FilterMatchesAny(const device::mojom::HidDeviceInfo& device) const;
bool IsExcluded(const device::mojom::HidDeviceInfo& device) const;
void AddMessageToConsole(blink::mojom::ConsoleMessageLevel level,
const std::string& message) const;

// Add |device_info| to |device_map_|. The device is added to the chooser item
// representing the physical device. If the chooser item does not yet exist, a
Expand All @@ -98,8 +102,8 @@ class HidChooserController
std::vector<blink::mojom::HidDeviceFilterPtr> filters_;
std::vector<blink::mojom::HidDeviceFilterPtr> exclusion_filters_;
content::HidChooser::Callback callback_;
content::WeakDocumentPtr initiator_document_;
const url::Origin origin_;
const int frame_tree_node_id_;

// The lifetime of the chooser context is tied to the browser context used to
// create it, and may be destroyed while the chooser is still active.
Expand Down