* 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>
197 lines
6.6 KiB
C++
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
|