electron/shell/browser/usb/usb_chooser_controller.cc
Shelley Vohr 2c6dd1156c
fix: use requesting frame origin in permission helper and device choosers (#50052)
* fix: use requesting frame origin instead of top-level URL for permissions

`WebContentsPermissionHelper::RequestPermission` passes
`web_contents_->GetLastCommittedURL()` as the origin to the permission
manager instead of the actual requesting frame's origin. This enables
origin confusion when granting permissions to embedded third-party iframes,
since app permission handlers see the top-level origin instead of the
iframe's. The same pattern exists in the HID, USB, and Serial device
choosers, where grants are keyed to the primary main frame's origin rather
than the requesting frame's.

Fix this by using `requesting_frame->GetLastCommittedOrigin()` in all
affected code paths, renaming `details.requestingUrl` to
`details.requestingOrigin`, and populating it with the serialized
origin only.

* chore: keep requestingUrl name in permission handler details

The previous commit changed the details.requestingUrl field to
details.requestingOrigin in permission request/check handlers. That
field was already populated from the requesting frame's RFH, so the
rename was unnecessary and would break apps that read the existing
property. Revert to requestingUrl to preserve the existing API shape.

The functional changes to use the requesting frame in
WebContentsPermissionHelper and the HID/USB/Serial choosers remain.

---------

Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-03-09 09:20:22 -07:00

197 lines
6.6 KiB
C++

// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/usb/usb_chooser_controller.h"
#include <algorithm>
#include <cstddef>
#include <utility>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "chrome/browser/usb/usb_blocklist.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "gin/data_object_builder.h"
#include "services/device/public/cpp/usb/usb_utils.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/usb/electron_usb_delegate.h"
#include "shell/browser/usb/usb_chooser_context_factory.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/content_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/usb_device_info_converter.h"
#include "shell/common/node_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
using content::RenderFrameHost;
using content::WebContents;
namespace electron {
UsbChooserController::UsbChooserController(
RenderFrameHost* render_frame_host,
blink::mojom::WebUsbRequestDeviceOptionsPtr options,
blink::mojom::WebUsbService::GetPermissionCallback callback,
content::WebContents* web_contents,
base::WeakPtr<ElectronUsbDelegate> usb_delegate)
: WebContentsObserver(web_contents),
options_(std::move(options)),
callback_(std::move(callback)),
origin_(render_frame_host->GetLastCommittedOrigin()),
usb_delegate_(usb_delegate),
render_frame_host_id_(render_frame_host->GetGlobalId()) {
chooser_context_ = UsbChooserContextFactory::GetForBrowserContext(
web_contents->GetBrowserContext())
->AsWeakPtr();
DCHECK(chooser_context_);
chooser_context_->GetDevices(base::BindOnce(
&UsbChooserController::GotUsbDeviceList, weak_factory_.GetWeakPtr()));
}
UsbChooserController::~UsbChooserController() {
RunCallback(/*device_info=*/nullptr);
}
gin::WeakCell<api::Session>* UsbChooserController::GetSession() {
if (!web_contents()) {
return nullptr;
}
return api::Session::FromBrowserContext(web_contents()->GetBrowserContext());
}
void UsbChooserController::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {
if (DisplayDevice(device_info)) {
devices_.push_back(device_info.Clone());
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
session->Get()->Emit("usb-device-added", device_info.Clone(),
web_contents());
}
}
}
void UsbChooserController::OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) {
std::erase_if(devices_, [&device_info](const auto& device) {
return device->guid == device_info.guid;
});
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
session->Get()->Emit("usb-device-removed", device_info.Clone(),
web_contents());
}
}
void UsbChooserController::OnDeviceChosen(gin::Arguments* args) {
std::string device_id;
if (!args->GetNext(&device_id) || device_id.empty()) {
RunCallback(/*device_info=*/nullptr);
} else {
const auto it = std::ranges::find_if(
devices_,
[&device_id](const auto& device) { return device->guid == device_id; });
if (it != devices_.end()) {
RunCallback((*it)->Clone());
} else {
util::EmitWarning(
base::StrCat({"The device id ", device_id, " was not found."}),
"UnknownUsbDeviceId");
RunCallback(/*device_info=*/nullptr);
}
}
}
void UsbChooserController::OnBrowserContextShutdown() {
observation_.Reset();
}
// Get a list of devices that can be shown in the chooser bubble UI for
// user to grant permission.
void UsbChooserController::GotUsbDeviceList(
std::vector<::device::mojom::UsbDeviceInfoPtr> devices) {
// Listen to UsbChooserContext for OnDeviceAdded/Removed events after the
// enumeration.
if (chooser_context_)
observation_.Observe(chooser_context_.get());
bool prevent_default = false;
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope{isolate};
// "select-usb-device" should respect |filters|.
std::erase_if(devices, [this](const auto& device_info) {
return !DisplayDevice(*device_info);
});
devices_.clear();
for (const auto& device : devices) {
devices_.push_back(device->Clone());
}
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
.Set("deviceList", devices)
.Set("frame", rfh)
.Build();
prevent_default = session->Get()->Emit(
"select-usb-device", details,
base::BindRepeating(&UsbChooserController::OnDeviceChosen,
weak_factory_.GetWeakPtr()));
}
if (!prevent_default) {
RunCallback(/*device_info=*/nullptr);
}
}
bool UsbChooserController::DisplayDevice(
const device::mojom::UsbDeviceInfo& device_info) const {
bool blocklist_disabled =
base::CommandLine::ForCurrentProcess()->HasSwitch(kDisableUSBBlocklist);
if (!device::UsbDeviceFilterMatchesAny(options_->filters, device_info)) {
return false;
}
if (std::ranges::any_of(
options_->exclusion_filters, [&device_info](const auto& filter) {
return device::UsbDeviceFilterMatches(*filter, device_info);
})) {
return false;
}
if (!blocklist_disabled && UsbBlocklist::Get().IsExcluded(device_info)) {
return false;
}
return true;
}
void UsbChooserController::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (usb_delegate_) {
usb_delegate_->DeleteControllerForFrame(render_frame_host);
}
}
void UsbChooserController::RunCallback(
device::mojom::UsbDeviceInfoPtr device_info) {
if (callback_) {
if (!chooser_context_ || !device_info) {
std::move(callback_).Run(nullptr);
} else {
chooser_context_->GrantDevicePermission(origin_, *device_info);
std::move(callback_).Run(std::move(device_info));
}
}
}
} // namespace electron