Skip to content

Commit

Permalink
chore: Show FIDO devices in the chooser if allowed (electron#40216)
Browse files Browse the repository at this point in the history
* chore: Show FIDO devices in the chooser if allowed

* chore: tweak HidChooserContext::IsFidoAllowedForOrigin

* chore: feedback from review

---------

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
  • Loading branch information
2 people authored and felixrieseberg committed Oct 25, 2023
1 parent 037e329 commit fa8ee7c
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 38 deletions.
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

0 comments on commit fa8ee7c

Please sign in to comment.