fix: WebView permissions only for the app's own origin (#22)
Linux (WebKitGTK) allowed every permission request of every kind; Windows (WebView2) auto-allowed mic/camera/notifications without checking who asked. Now (src-tauri/src/webview_permissions.rs, unit-tested): - Linux: microphone/camera/screen, device labels, notifications and location are granted when the page in the window is the app (http://localhost:44548; debug builds also the bundled/dev page). Everything else is denied (WebKitGTK has no prompt of its own). WebKitGTK doesn't say which frame asked; frames are gated earlier by the Permissions Policy (cinny gives microphone/camera only to the same-origin call frame). - Windows: the same grants (minus location, which keeps WebView2's prompt), checked against the origin of the frame that asked (args.Uri()). Other origins are denied mic/camera/notifications/location; other kinds keep WebView2's default handling. - Denials are logged ("webview: denied …"). Tested on Linux with a release build under Xvfb + PulseAudio (no WebDriver: WebKit's automation mode bypasses the handler), before/after: - app page: mic, device labels, location allowed (unchanged) - same-origin call frame: mic allowed (unchanged) - cross-origin frame without allow=: blocked before the handler (unchanged) - foreign top-level page: mic, device labels, location now denied (were allowed) Real cinny build: boots, logs in, no denials. Windows code type-checked (x86_64-pc-windows-gnu). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
11e102f7da
commit
3e136d3729
+105
-39
@@ -13,6 +13,8 @@ use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
pub mod gpu_workarounds;
|
||||
mod native;
|
||||
#[cfg(any(target_os = "linux", target_os = "windows", test))]
|
||||
mod webview_permissions;
|
||||
|
||||
/// Bring the main window to the foreground from the tray / a hidden /
|
||||
/// minimized state. Shared by the tray, single-instance, and deep-link paths.
|
||||
@@ -1221,41 +1223,76 @@ pub fn run() {
|
||||
let _ = window_vibrancy::apply_mica(&window, Some(true));
|
||||
}
|
||||
|
||||
// Auto-grant camera, microphone, and notification permissions in WebView2.
|
||||
#[cfg(target_os = "windows")]
|
||||
window.with_webview(|webview| {
|
||||
use webview2_com::{
|
||||
Microsoft::Web::WebView2::Win32::{
|
||||
COREWEBVIEW2_PERMISSION_KIND,
|
||||
COREWEBVIEW2_PERMISSION_KIND_CAMERA,
|
||||
COREWEBVIEW2_PERMISSION_KIND_MICROPHONE,
|
||||
COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS,
|
||||
COREWEBVIEW2_PERMISSION_STATE_ALLOW,
|
||||
},
|
||||
PermissionRequestedEventHandler,
|
||||
};
|
||||
// cinny-desktop #22: the app's own page gets the microphone, camera
|
||||
// and notifications without a prompt; other origins (room widgets,
|
||||
// link-preview embeds) are refused them. See webview_permissions.
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
let app_origins = webview_permissions::AppOrigins::new(
|
||||
port,
|
||||
app.config().build.dev_url.as_ref(),
|
||||
);
|
||||
|
||||
let controller = webview.controller();
|
||||
if let Ok(core) = unsafe { controller.CoreWebView2() } {
|
||||
let handler = PermissionRequestedEventHandler::create(Box::new(
|
||||
|_sender, args| {
|
||||
if let Some(args) = args {
|
||||
let mut kind = COREWEBVIEW2_PERMISSION_KIND(0);
|
||||
unsafe { args.PermissionKind(&mut kind) }?;
|
||||
if kind == COREWEBVIEW2_PERMISSION_KIND_MICROPHONE
|
||||
|| kind == COREWEBVIEW2_PERMISSION_KIND_CAMERA
|
||||
|| kind == COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS
|
||||
{
|
||||
unsafe {
|
||||
args.SetState(COREWEBVIEW2_PERMISSION_STATE_ALLOW)
|
||||
}?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
#[cfg(target_os = "windows")]
|
||||
window.with_webview({
|
||||
let app_origins = app_origins.clone();
|
||||
move |webview| {
|
||||
use webview2_com::{
|
||||
Microsoft::Web::WebView2::Win32::{
|
||||
COREWEBVIEW2_PERMISSION_KIND,
|
||||
COREWEBVIEW2_PERMISSION_KIND_CAMERA,
|
||||
COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION,
|
||||
COREWEBVIEW2_PERMISSION_KIND_MICROPHONE,
|
||||
COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS,
|
||||
COREWEBVIEW2_PERMISSION_STATE_ALLOW,
|
||||
COREWEBVIEW2_PERMISSION_STATE_DENY,
|
||||
},
|
||||
));
|
||||
let mut token = Default::default();
|
||||
let _ = unsafe { core.add_PermissionRequested(&handler, &mut token) };
|
||||
PermissionRequestedEventHandler,
|
||||
};
|
||||
use webview_permissions::{decide, Decision, Kind, WINDOWS_GRANTS};
|
||||
|
||||
let controller = webview.controller();
|
||||
if let Ok(core) = unsafe { controller.CoreWebView2() } {
|
||||
let handler = PermissionRequestedEventHandler::create(Box::new(
|
||||
move |_sender, args| {
|
||||
if let Some(args) = args {
|
||||
let mut raw = COREWEBVIEW2_PERMISSION_KIND(0);
|
||||
unsafe { args.PermissionKind(&mut raw) }?;
|
||||
let kind = if raw == COREWEBVIEW2_PERMISSION_KIND_MICROPHONE
|
||||
|| raw == COREWEBVIEW2_PERMISSION_KIND_CAMERA
|
||||
{
|
||||
Kind::Media
|
||||
} else if raw == COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS {
|
||||
Kind::Notifications
|
||||
} else if raw == COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION {
|
||||
Kind::Geolocation
|
||||
} else {
|
||||
Kind::Other
|
||||
};
|
||||
// The origin of the frame that asked.
|
||||
let mut uri = windows::core::PWSTR::null();
|
||||
unsafe { args.Uri(&mut uri) }?;
|
||||
let uri = webview2_com::take_pwstr(uri);
|
||||
match decide(kind, &uri, &app_origins, WINDOWS_GRANTS) {
|
||||
Decision::Allow => unsafe {
|
||||
args.SetState(COREWEBVIEW2_PERMISSION_STATE_ALLOW)
|
||||
}?,
|
||||
Decision::Deny => {
|
||||
eprintln!(
|
||||
"webview: denied {kind:?} permission to {uri}"
|
||||
);
|
||||
unsafe {
|
||||
args.SetState(COREWEBVIEW2_PERMISSION_STATE_DENY)
|
||||
}?
|
||||
}
|
||||
Decision::Default => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
));
|
||||
let mut token = Default::default();
|
||||
let _ = unsafe { core.add_PermissionRequested(&handler, &mut token) };
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -1263,19 +1300,48 @@ pub fn run() {
|
||||
// default (unlike WebView2/WKWebView), which leaves
|
||||
// `navigator.mediaDevices` undefined and makes Element Call
|
||||
// report "browser does not support WebRTC". Turn them on and
|
||||
// auto-grant the resulting camera/mic permission prompt, mirroring
|
||||
// the WebView2 handling above.
|
||||
// answer the permission requests, mirroring the WebView2 handling
|
||||
// above. WebKitGTK doesn't say which frame asked, so the origin
|
||||
// checked is the page in the window (see webview_permissions).
|
||||
#[cfg(target_os = "linux")]
|
||||
window.with_webview(|webview| {
|
||||
use webkit2gtk::{PermissionRequestExt, SettingsExt, WebViewExt};
|
||||
window.with_webview(move |webview| {
|
||||
use webkit2gtk::glib::prelude::ObjectExt;
|
||||
use webkit2gtk::{
|
||||
DeviceInfoPermissionRequest, GeolocationPermissionRequest,
|
||||
NotificationPermissionRequest, PermissionRequestExt, SettingsExt,
|
||||
UserMediaPermissionRequest, WebViewExt,
|
||||
};
|
||||
use webview_permissions::{decide, Decision, Kind, LINUX_GRANTS};
|
||||
|
||||
let wv = webview.inner();
|
||||
if let Some(settings) = WebViewExt::settings(&wv) {
|
||||
settings.set_enable_media_stream(true);
|
||||
settings.set_enable_webrtc(true);
|
||||
}
|
||||
wv.connect_permission_request(|_webview, request| {
|
||||
request.allow();
|
||||
wv.connect_permission_request(move |wv, request| {
|
||||
let kind = if request.is::<UserMediaPermissionRequest>() {
|
||||
Kind::Media
|
||||
} else if request.is::<DeviceInfoPermissionRequest>() {
|
||||
Kind::DeviceInfo
|
||||
} else if request.is::<NotificationPermissionRequest>() {
|
||||
Kind::Notifications
|
||||
} else if request.is::<GeolocationPermissionRequest>() {
|
||||
Kind::Geolocation
|
||||
} else {
|
||||
Kind::Other
|
||||
};
|
||||
let uri = wv.uri().map(|u| u.to_string()).unwrap_or_default();
|
||||
match decide(kind, &uri, &app_origins, LINUX_GRANTS) {
|
||||
Decision::Allow => request.allow(),
|
||||
// No prompt of our own: anything not granted is denied.
|
||||
Decision::Deny | Decision::Default => {
|
||||
eprintln!(
|
||||
"webview: denied {} to {uri}",
|
||||
request.type_().name()
|
||||
);
|
||||
request.deny();
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
})?;
|
||||
|
||||
Reference in New Issue
Block a user