зеркало из https://github.com/electron/electron.git
feat: add USB protected classes handler (#38263)
* feat: add USB protected classes handler * chore: apply review suggestions Co-authored-by: Charles Kerr <charles@charleskerr.com> * chore: update docs * chore: apply review suggestions * update doc per suggestion --------- Co-authored-by: Charles Kerr <charles@charleskerr.com>
This commit is contained in:
Родитель
71fb19ea14
Коммит
b4ec363b3d
|
@ -1091,6 +1091,55 @@ app.whenReady().then(() => {
|
|||
})
|
||||
```
|
||||
|
||||
#### `ses.setUSBProtectedClassesHandler(handler)`
|
||||
|
||||
* `handler` Function\<string[]> | null
|
||||
* `details` Object
|
||||
* `protectedClasses` string[] - The current list of protected USB classes. Possible class values are:
|
||||
* `audio`
|
||||
* `audio-video`
|
||||
* `hid`
|
||||
* `mass-storage`
|
||||
* `smart-card`
|
||||
* `video`
|
||||
* `wireless`
|
||||
|
||||
Sets the handler which can be used to override which [USB classes are protected](https://wicg.github.io/webusb/#usbinterface-interface).
|
||||
The return value for the handler is a string array of USB classes which should be considered protected (eg not available in the renderer). Valid values for the array are:
|
||||
|
||||
* `audio`
|
||||
* `audio-video`
|
||||
* `hid`
|
||||
* `mass-storage`
|
||||
* `smart-card`
|
||||
* `video`
|
||||
* `wireless`
|
||||
|
||||
Returning an empty string array from the handler will allow all USB classes; returning the passed in array will maintain the default list of protected USB classes (this is also the default behavior if a handler is not defined).
|
||||
To clear the handler, call `setUSBProtectedClassesHandler(null)`.
|
||||
|
||||
```javascript
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let win = null
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow()
|
||||
|
||||
win.webContents.session.setUSBProtectedClassesHandler((details) => {
|
||||
// Allow all classes:
|
||||
// return []
|
||||
// Keep the current set of protected classes:
|
||||
// return details.protectedClasses
|
||||
// Selectively remove classes:
|
||||
return details.protectedClasses.filter((usbClass) => {
|
||||
// Exclude classes except for audio classes
|
||||
return usbClass.indexOf('audio') === -1
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### `ses.setBluetoothPairingHandler(handler)` _Windows_ _Linux_
|
||||
|
||||
* `handler` Function | null
|
||||
|
|
|
@ -51,6 +51,13 @@ function createWindow () {
|
|||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setUSBProtectedClassesHandler((details) => {
|
||||
return details.protectedClasses.filter((usbClass) => {
|
||||
// Exclude classes except for audio classes
|
||||
return usbClass.indexOf('audio') === -1
|
||||
})
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
}
|
||||
|
||||
|
|
|
@ -142,6 +142,8 @@ Electron provides several APIs for working with the WebUSB API:
|
|||
`setDevicePermissionHandler`.
|
||||
* [`ses.setPermissionCheckHandler(handler)`](../api/session.md#sessetpermissioncheckhandlerhandler)
|
||||
can be used to disable USB access for specific origins.
|
||||
* [`ses.setUSBProtectedClassesHandler](../api/session.md#sessetusbprotectedclasseshandlerhandler)
|
||||
can be used to allow usage of [protected USB classes](https://wicg.github.io/webusb/#usbinterface-interface) that are not available by default.
|
||||
|
||||
### Example
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/media_converter.h"
|
||||
#include "shell/common/gin_converters/net_converter.h"
|
||||
#include "shell/common/gin_converters/usb_protected_classes_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
|
@ -699,6 +700,18 @@ void Session::SetDevicePermissionHandler(v8::Local<v8::Value> val,
|
|||
permission_manager->SetDevicePermissionHandler(handler);
|
||||
}
|
||||
|
||||
void Session::SetUSBProtectedClassesHandler(v8::Local<v8::Value> val,
|
||||
gin::Arguments* args) {
|
||||
ElectronPermissionManager::ProtectedUSBHandler handler;
|
||||
if (!(val->IsNull() || gin::ConvertFromV8(args->isolate(), val, &handler))) {
|
||||
args->ThrowTypeError("Must pass null or function");
|
||||
return;
|
||||
}
|
||||
auto* permission_manager = static_cast<ElectronPermissionManager*>(
|
||||
browser_context()->GetPermissionControllerDelegate());
|
||||
permission_manager->SetProtectedUSBHandler(handler);
|
||||
}
|
||||
|
||||
void Session::SetBluetoothPairingHandler(v8::Local<v8::Value> val,
|
||||
gin::Arguments* args) {
|
||||
ElectronPermissionManager::BluetoothPairingHandler handler;
|
||||
|
@ -1294,6 +1307,8 @@ void Session::FillObjectTemplate(v8::Isolate* isolate,
|
|||
&Session::SetDisplayMediaRequestHandler)
|
||||
.SetMethod("setDevicePermissionHandler",
|
||||
&Session::SetDevicePermissionHandler)
|
||||
.SetMethod("setUSBProtectedClassesHandler",
|
||||
&Session::SetUSBProtectedClassesHandler)
|
||||
.SetMethod("setBluetoothPairingHandler",
|
||||
&Session::SetBluetoothPairingHandler)
|
||||
.SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache)
|
||||
|
|
|
@ -118,6 +118,8 @@ class Session : public gin::Wrappable<Session>,
|
|||
gin::Arguments* args);
|
||||
void SetDevicePermissionHandler(v8::Local<v8::Value> val,
|
||||
gin::Arguments* args);
|
||||
void SetUSBProtectedClassesHandler(v8::Local<v8::Value> val,
|
||||
gin::Arguments* args);
|
||||
void SetBluetoothPairingHandler(v8::Local<v8::Value> val,
|
||||
gin::Arguments* args);
|
||||
v8::Local<v8::Promise> ClearHostResolverCache(gin::Arguments* args);
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "shell/browser/web_contents_preferences.h"
|
||||
#include "shell/common/gin_converters/content_converter.h"
|
||||
#include "shell/common/gin_converters/frame_converter.h"
|
||||
#include "shell/common/gin_converters/usb_protected_classes_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||
#include "third_party/blink/public/common/permissions/permission_utils.h"
|
||||
|
@ -130,6 +131,11 @@ void ElectronPermissionManager::SetDevicePermissionHandler(
|
|||
device_permission_handler_ = handler;
|
||||
}
|
||||
|
||||
void ElectronPermissionManager::SetProtectedUSBHandler(
|
||||
const ProtectedUSBHandler& handler) {
|
||||
protected_usb_handler_ = handler;
|
||||
}
|
||||
|
||||
void ElectronPermissionManager::SetBluetoothPairingHandler(
|
||||
const BluetoothPairingHandler& handler) {
|
||||
bluetooth_pairing_handler_ = handler;
|
||||
|
@ -362,6 +368,21 @@ void ElectronPermissionManager::RevokeDevicePermission(
|
|||
browser_context->RevokeDevicePermission(origin, device, permission);
|
||||
}
|
||||
|
||||
ElectronPermissionManager::USBProtectedClasses
|
||||
ElectronPermissionManager::CheckProtectedUSBClasses(
|
||||
const USBProtectedClasses& classes) const {
|
||||
if (protected_usb_handler_.is_null()) {
|
||||
return classes;
|
||||
} else {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
|
||||
.Set("protectedClasses", classes)
|
||||
.Build();
|
||||
return protected_usb_handler_.Run(details);
|
||||
}
|
||||
}
|
||||
|
||||
blink::mojom::PermissionStatus
|
||||
ElectronPermissionManager::GetPermissionStatusForCurrentDocument(
|
||||
blink::PermissionType permission,
|
||||
|
|
|
@ -35,6 +35,8 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
|
|||
ElectronPermissionManager& operator=(const ElectronPermissionManager&) =
|
||||
delete;
|
||||
|
||||
using USBProtectedClasses = std::vector<uint8_t>;
|
||||
|
||||
using StatusCallback =
|
||||
base::OnceCallback<void(blink::mojom::PermissionStatus)>;
|
||||
using StatusesCallback = base::OnceCallback<void(
|
||||
|
@ -52,6 +54,10 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
|
|||
|
||||
using DeviceCheckHandler =
|
||||
base::RepeatingCallback<bool(const v8::Local<v8::Object>&)>;
|
||||
|
||||
using ProtectedUSBHandler = base::RepeatingCallback<USBProtectedClasses(
|
||||
const v8::Local<v8::Object>&)>;
|
||||
|
||||
using BluetoothPairingHandler =
|
||||
base::RepeatingCallback<void(gin_helper::Dictionary, PairCallback)>;
|
||||
|
||||
|
@ -59,6 +65,7 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
|
|||
void SetPermissionRequestHandler(const RequestHandler& handler);
|
||||
void SetPermissionCheckHandler(const CheckHandler& handler);
|
||||
void SetDevicePermissionHandler(const DeviceCheckHandler& handler);
|
||||
void SetProtectedUSBHandler(const ProtectedUSBHandler& handler);
|
||||
void SetBluetoothPairingHandler(const BluetoothPairingHandler& handler);
|
||||
|
||||
// content::PermissionControllerDelegate:
|
||||
|
@ -109,6 +116,9 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
|
|||
const base::Value& object,
|
||||
ElectronBrowserContext* browser_context) const;
|
||||
|
||||
USBProtectedClasses CheckProtectedUSBClasses(
|
||||
const USBProtectedClasses& classes) const;
|
||||
|
||||
protected:
|
||||
void OnPermissionResponse(int request_id,
|
||||
int permission_id,
|
||||
|
@ -159,6 +169,7 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
|
|||
RequestHandler request_handler_;
|
||||
CheckHandler check_handler_;
|
||||
DeviceCheckHandler device_permission_handler_;
|
||||
ProtectedUSBHandler protected_usb_handler_;
|
||||
BluetoothPairingHandler bluetooth_pairing_handler_;
|
||||
|
||||
PendingRequestsMap pending_requests_;
|
||||
|
|
|
@ -152,37 +152,9 @@ void ElectronUsbDelegate::AdjustProtectedInterfaceClasses(
|
|||
const url::Origin& origin,
|
||||
content::RenderFrameHost* frame,
|
||||
std::vector<uint8_t>& classes) {
|
||||
// Isolated Apps have unrestricted access to any USB interface class.
|
||||
if (frame &&
|
||||
frame->GetWebExposedIsolationLevel() >=
|
||||
content::WebExposedIsolationLevel::kMaybeIsolatedApplication) {
|
||||
// TODO(https://crbug.com/1236706): Should the list of interface classes the
|
||||
// app expects to claim be encoded in the Web App Manifest?
|
||||
classes.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
// Don't enforce protected interface classes for Chrome Apps since the
|
||||
// chrome.usb API has no such restriction.
|
||||
if (origin.scheme() == extensions::kExtensionScheme) {
|
||||
auto* extension_registry =
|
||||
extensions::ExtensionRegistry::Get(browser_context);
|
||||
if (extension_registry) {
|
||||
const extensions::Extension* extension =
|
||||
extension_registry->enabled_extensions().GetByID(origin.host());
|
||||
if (extension && extension->is_platform_app()) {
|
||||
classes.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (origin.scheme() == extensions::kExtensionScheme &&
|
||||
base::Contains(kSmartCardPrivilegedExtensionIds, origin.host())) {
|
||||
base::Erase(classes, device::mojom::kUsbSmartCardClass);
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
auto* permission_manager = static_cast<ElectronPermissionManager*>(
|
||||
browser_context->GetPermissionControllerDelegate());
|
||||
classes = permission_manager->CheckProtectedUSBClasses(classes);
|
||||
}
|
||||
|
||||
std::unique_ptr<UsbChooser> ElectronUsbDelegate::RunChooser(
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2022 Microsoft, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_COMMON_GIN_CONVERTERS_USB_PROTECTED_CLASSES_CONVERTER_H_
|
||||
#define ELECTRON_SHELL_COMMON_GIN_CONVERTERS_USB_PROTECTED_CLASSES_CONVERTER_H_
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gin/converter.h"
|
||||
#include "services/device/public/mojom/usb_device.mojom-forward.h"
|
||||
#include "shell/browser/electron_permission_manager.h"
|
||||
|
||||
namespace gin {
|
||||
|
||||
static auto constexpr ClassMapping =
|
||||
std::array<std::pair<uint8_t, std::string_view>, 7>{
|
||||
{{device::mojom::kUsbAudioClass, "audio"},
|
||||
{device::mojom::kUsbHidClass, "hid"},
|
||||
{device::mojom::kUsbMassStorageClass, "mass-storage"},
|
||||
{device::mojom::kUsbSmartCardClass, "smart-card"},
|
||||
{device::mojom::kUsbVideoClass, "video"},
|
||||
{device::mojom::kUsbAudioVideoClass, "audio-video"},
|
||||
{device::mojom::kUsbWirelessClass, "wireless"}}};
|
||||
|
||||
template <>
|
||||
struct Converter<electron::ElectronPermissionManager::USBProtectedClasses> {
|
||||
static v8::Local<v8::Value> ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const electron::ElectronPermissionManager::USBProtectedClasses& classes) {
|
||||
std::vector<std::string> class_strings;
|
||||
class_strings.reserve(std::size(classes));
|
||||
for (const auto& itr : classes) {
|
||||
for (const auto& [usb_class, name] : ClassMapping) {
|
||||
if (usb_class == itr)
|
||||
class_strings.emplace_back(name);
|
||||
}
|
||||
}
|
||||
return gin::ConvertToV8(isolate, class_strings);
|
||||
}
|
||||
|
||||
static bool FromV8(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
electron::ElectronPermissionManager::USBProtectedClasses* out) {
|
||||
std::vector<std::string> class_strings;
|
||||
if (ConvertFromV8(isolate, val, &class_strings)) {
|
||||
out->reserve(std::size(class_strings));
|
||||
for (const auto& itr : class_strings) {
|
||||
for (const auto& [usb_class, name] : ClassMapping) {
|
||||
if (name == itr)
|
||||
out->emplace_back(usb_class);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
||||
#endif // ELECTRON_SHELL_COMMON_GIN_CONVERTERS_USB_PROTECTED_CLASSES_CONVERTER_H_
|
Загрузка…
Ссылка в новой задаче