Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2be36d14a9 | ||
|
|
ea69cd770e | ||
|
|
3cd429d45c | ||
|
|
32a71ebb73 | ||
|
|
884877a55b | ||
|
|
bf5b6d7b45 | ||
|
|
29f83df53c |
+1
-1
Submodule cinny updated: be8e49a2bb...747400ea25
+11
-1
@@ -25,5 +25,15 @@
|
|||||||
},
|
},
|
||||||
"gifApiKey": "",
|
"gifApiKey": "",
|
||||||
"webAppUrl": "https://chat.lotusguild.org",
|
"webAppUrl": "https://chat.lotusguild.org",
|
||||||
"desktopCallOrigin": "http://127.0.0.1:44548"
|
"statusPages": {
|
||||||
|
"matrix.lotusguild.org": {
|
||||||
|
"url": "https://isitup.lotusguild.org",
|
||||||
|
"slug": "matrix",
|
||||||
|
"groups": {
|
||||||
|
"homeserver": "Homeserver",
|
||||||
|
"calls": "Voice calls",
|
||||||
|
"login": "Login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-115
@@ -13,8 +13,6 @@ use tauri_plugin_opener::OpenerExt;
|
|||||||
|
|
||||||
pub mod gpu_workarounds;
|
pub mod gpu_workarounds;
|
||||||
mod native;
|
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 /
|
/// Bring the main window to the foreground from the tray / a hidden /
|
||||||
/// minimized state. Shared by the tray, single-instance, and deep-link paths.
|
/// minimized state. Shared by the tray, single-instance, and deep-link paths.
|
||||||
@@ -955,17 +953,7 @@ pub fn run() {
|
|||||||
native::hotkeys::global_hotkeys_supported,
|
native::hotkeys::global_hotkeys_supported,
|
||||||
native::hotkeys::set_global_hotkeys,
|
native::hotkeys::set_global_hotkeys,
|
||||||
])
|
])
|
||||||
// Bound to 127.0.0.1 explicitly (cinny #43). The app is still loaded as
|
.plugin(tauri_plugin_localhost::Builder::new(port).build())
|
||||||
// http://localhost:{port} (its storage lives under that origin, and the
|
|
||||||
// engines try 127.0.0.1 for `localhost`); the bundled call page is
|
|
||||||
// loaded as http://127.0.0.1:{port}, a separate origin on the same
|
|
||||||
// server. Binding the name `localhost` could pick ::1 only (Windows
|
|
||||||
// lists it first), and then the call page wouldn't load.
|
|
||||||
.plugin(
|
|
||||||
tauri_plugin_localhost::Builder::new(port)
|
|
||||||
.host("127.0.0.1")
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.plugin(
|
.plugin(
|
||||||
// DECORATIONS is excluded: the custom-chrome toggle (set_custom_chrome)
|
// DECORATIONS is excluded: the custom-chrome toggle (set_custom_chrome)
|
||||||
// owns the decorated flag. Letting window-state restore a saved
|
// owns the decorated flag. Letting window-state restore a saved
|
||||||
@@ -1233,76 +1221,41 @@ pub fn run() {
|
|||||||
let _ = window_vibrancy::apply_mica(&window, Some(true));
|
let _ = window_vibrancy::apply_mica(&window, Some(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// cinny-desktop #22: the app's own page gets the microphone, camera
|
// Auto-grant camera, microphone, and notification permissions in WebView2.
|
||||||
// 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(),
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
window.with_webview({
|
window.with_webview(|webview| {
|
||||||
let app_origins = app_origins.clone();
|
use webview2_com::{
|
||||||
move |webview| {
|
Microsoft::Web::WebView2::Win32::{
|
||||||
use webview2_com::{
|
COREWEBVIEW2_PERMISSION_KIND,
|
||||||
Microsoft::Web::WebView2::Win32::{
|
COREWEBVIEW2_PERMISSION_KIND_CAMERA,
|
||||||
COREWEBVIEW2_PERMISSION_KIND,
|
COREWEBVIEW2_PERMISSION_KIND_MICROPHONE,
|
||||||
COREWEBVIEW2_PERMISSION_KIND_CAMERA,
|
COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS,
|
||||||
COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION,
|
COREWEBVIEW2_PERMISSION_STATE_ALLOW,
|
||||||
COREWEBVIEW2_PERMISSION_KIND_MICROPHONE,
|
},
|
||||||
COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS,
|
PermissionRequestedEventHandler,
|
||||||
COREWEBVIEW2_PERMISSION_STATE_ALLOW,
|
};
|
||||||
COREWEBVIEW2_PERMISSION_STATE_DENY,
|
|
||||||
},
|
|
||||||
PermissionRequestedEventHandler,
|
|
||||||
};
|
|
||||||
use webview_permissions::{decide, Decision, Kind, WINDOWS_GRANTS};
|
|
||||||
|
|
||||||
let controller = webview.controller();
|
let controller = webview.controller();
|
||||||
if let Ok(core) = unsafe { controller.CoreWebView2() } {
|
if let Ok(core) = unsafe { controller.CoreWebView2() } {
|
||||||
let handler = PermissionRequestedEventHandler::create(Box::new(
|
let handler = PermissionRequestedEventHandler::create(Box::new(
|
||||||
move |_sender, args| {
|
|_sender, args| {
|
||||||
if let Some(args) = args {
|
if let Some(args) = args {
|
||||||
let mut raw = COREWEBVIEW2_PERMISSION_KIND(0);
|
let mut kind = COREWEBVIEW2_PERMISSION_KIND(0);
|
||||||
unsafe { args.PermissionKind(&mut raw) }?;
|
unsafe { args.PermissionKind(&mut kind) }?;
|
||||||
let kind = if raw == COREWEBVIEW2_PERMISSION_KIND_MICROPHONE
|
if kind == COREWEBVIEW2_PERMISSION_KIND_MICROPHONE
|
||||||
|| raw == COREWEBVIEW2_PERMISSION_KIND_CAMERA
|
|| kind == COREWEBVIEW2_PERMISSION_KIND_CAMERA
|
||||||
{
|
|| kind == COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS
|
||||||
Kind::Media
|
{
|
||||||
} else if raw == COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS {
|
unsafe {
|
||||||
Kind::Notifications
|
args.SetState(COREWEBVIEW2_PERMISSION_STATE_ALLOW)
|
||||||
} 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(())
|
}
|
||||||
},
|
Ok(())
|
||||||
));
|
},
|
||||||
let mut token = Default::default();
|
));
|
||||||
let _ = unsafe { core.add_PermissionRequested(&handler, &mut token) };
|
let mut token = Default::default();
|
||||||
}
|
let _ = unsafe { core.add_PermissionRequested(&handler, &mut token) };
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -1310,48 +1263,19 @@ pub fn run() {
|
|||||||
// default (unlike WebView2/WKWebView), which leaves
|
// default (unlike WebView2/WKWebView), which leaves
|
||||||
// `navigator.mediaDevices` undefined and makes Element Call
|
// `navigator.mediaDevices` undefined and makes Element Call
|
||||||
// report "browser does not support WebRTC". Turn them on and
|
// report "browser does not support WebRTC". Turn them on and
|
||||||
// answer the permission requests, mirroring the WebView2 handling
|
// auto-grant the resulting camera/mic permission prompt, mirroring
|
||||||
// above. WebKitGTK doesn't say which frame asked, so the origin
|
// the WebView2 handling above.
|
||||||
// checked is the page in the window (see webview_permissions).
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
window.with_webview(move |webview| {
|
window.with_webview(|webview| {
|
||||||
use webkit2gtk::glib::prelude::ObjectExt;
|
use webkit2gtk::{PermissionRequestExt, SettingsExt, WebViewExt};
|
||||||
use webkit2gtk::{
|
|
||||||
DeviceInfoPermissionRequest, GeolocationPermissionRequest,
|
|
||||||
NotificationPermissionRequest, PermissionRequestExt, SettingsExt,
|
|
||||||
UserMediaPermissionRequest, WebViewExt,
|
|
||||||
};
|
|
||||||
use webview_permissions::{decide, Decision, Kind, LINUX_GRANTS};
|
|
||||||
|
|
||||||
let wv = webview.inner();
|
let wv = webview.inner();
|
||||||
if let Some(settings) = WebViewExt::settings(&wv) {
|
if let Some(settings) = WebViewExt::settings(&wv) {
|
||||||
settings.set_enable_media_stream(true);
|
settings.set_enable_media_stream(true);
|
||||||
settings.set_enable_webrtc(true);
|
settings.set_enable_webrtc(true);
|
||||||
}
|
}
|
||||||
wv.connect_permission_request(move |wv, request| {
|
wv.connect_permission_request(|_webview, request| {
|
||||||
let kind = if request.is::<UserMediaPermissionRequest>() {
|
request.allow();
|
||||||
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
|
true
|
||||||
});
|
});
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -1,273 +0,0 @@
|
|||||||
//! Which WebView permission requests the app grants (cinny-desktop #22).
|
|
||||||
//!
|
|
||||||
//! The web client asks for the microphone/camera/screen (calls, voice
|
|
||||||
//! messages), the device list (audio-output picker), notifications and the
|
|
||||||
//! location (location sharing). Those are granted without a prompt, but only
|
|
||||||
//! to the app's own origin. Everything else is left alone (Windows: WebView2's
|
|
||||||
//! own prompt) or denied (Linux: WebKitGTK has no prompt of its own).
|
|
||||||
//!
|
|
||||||
//! What "the requesting origin" means differs per engine:
|
|
||||||
//! - WebView2 reports the origin of the frame that asked (`args.Uri()`), so a
|
|
||||||
//! room widget or link-preview embed is refused here.
|
|
||||||
//! - WebKitGTK doesn't say which frame asked; the check is on the page loaded
|
|
||||||
//! in the window. Frames are gated before the request gets this far by the
|
|
||||||
//! Permissions Policy: cinny only puts `microphone; camera` in the `allow=`
|
|
||||||
//! of the call frame, and cross-origin frames get neither location nor
|
|
||||||
//! notifications.
|
|
||||||
//!
|
|
||||||
//! The call frame (cinny #43): the bundled Element Call page is loaded from
|
|
||||||
//! `http://127.0.0.1:{port}`, the same local server on a second origin, so it
|
|
||||||
//! can't reach the app's storage. WebView2 reports that origin for the call's
|
|
||||||
//! microphone/camera requests; it gets media and nothing else.
|
|
||||||
|
|
||||||
use tauri::Url;
|
|
||||||
|
|
||||||
/// A permission request, reduced to what the policy cares about.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub(crate) enum Kind {
|
|
||||||
/// Microphone, camera or screen capture (getUserMedia/getDisplayMedia).
|
|
||||||
Media,
|
|
||||||
/// Device labels/ids from enumerateDevices (WebKitGTK only).
|
|
||||||
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
|
||||||
DeviceInfo,
|
|
||||||
Notifications,
|
|
||||||
Geolocation,
|
|
||||||
/// Anything else: clipboard read, storage access, pointer lock, DRM, …
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub(crate) enum Decision {
|
|
||||||
Allow,
|
|
||||||
Deny,
|
|
||||||
/// Let the engine decide (WebView2 prompts; WebKitGTK denies).
|
|
||||||
Default,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What the Linux (WebKitGTK) handler grants to the app.
|
|
||||||
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
|
||||||
pub(crate) const LINUX_GRANTS: &[Kind] = &[
|
|
||||||
Kind::Media,
|
|
||||||
Kind::DeviceInfo,
|
|
||||||
Kind::Notifications,
|
|
||||||
Kind::Geolocation,
|
|
||||||
];
|
|
||||||
/// What the Windows (WebView2) handler grants to the app. Location keeps
|
|
||||||
/// WebView2's own prompt, as before.
|
|
||||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
||||||
pub(crate) const WINDOWS_GRANTS: &[Kind] = &[Kind::Media, Kind::Notifications];
|
|
||||||
|
|
||||||
/// The decision for a request of `kind` from `uri`.
|
|
||||||
pub(crate) fn decide(kind: Kind, uri: &str, app: &AppOrigins, grants: &[Kind]) -> Decision {
|
|
||||||
if kind == Kind::Other {
|
|
||||||
return Decision::Default;
|
|
||||||
}
|
|
||||||
if app.is_call_frame(uri) {
|
|
||||||
// The call page: microphone/camera/screen only.
|
|
||||||
return if kind == Kind::Media && grants.contains(&kind) {
|
|
||||||
Decision::Allow
|
|
||||||
} else {
|
|
||||||
Decision::Deny
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if !app.contains(uri) {
|
|
||||||
return Decision::Deny;
|
|
||||||
}
|
|
||||||
if grants.contains(&kind) {
|
|
||||||
Decision::Allow
|
|
||||||
} else {
|
|
||||||
Decision::Default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// scheme, host, port (explicit or the scheme's default).
|
|
||||||
type Origin = (String, String, Option<u16>);
|
|
||||||
|
|
||||||
fn origin_of(uri: &str) -> Option<Origin> {
|
|
||||||
let url = Url::parse(uri).ok()?;
|
|
||||||
let host = url.host_str()?.to_ascii_lowercase();
|
|
||||||
Some((url.scheme().to_owned(), host, url.port_or_known_default()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The origins the app's own page is served from, and the call page's.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub(crate) struct AppOrigins {
|
|
||||||
app: Vec<Origin>,
|
|
||||||
call: Option<Origin>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppOrigins {
|
|
||||||
/// Release builds load `http://localhost:{port}` (tauri-plugin-localhost).
|
|
||||||
/// Debug builds load the bundled page (`tauri://localhost`, or
|
|
||||||
/// `http://tauri.localhost` on Windows) or, under `tauri dev`, `dev_url`.
|
|
||||||
pub(crate) fn new(port: u16, dev_url: Option<&Url>) -> Self {
|
|
||||||
let mut uris = vec![format!("http://localhost:{port}/")];
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
uris.push("tauri://localhost/".into());
|
|
||||||
uris.push("http://tauri.localhost/".into());
|
|
||||||
if let Some(dev) = dev_url {
|
|
||||||
uris.push(dev.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
app: uris.iter().filter_map(|u| origin_of(u)).collect(),
|
|
||||||
call: origin_of(&format!("http://127.0.0.1:{port}/")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The app's own page.
|
|
||||||
pub(crate) fn contains(&self, uri: &str) -> bool {
|
|
||||||
origin_of(uri).is_some_and(|o| self.app.contains(&o))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The call page on its own origin (`http://127.0.0.1:{port}`).
|
|
||||||
pub(crate) fn is_call_frame(&self, uri: &str) -> bool {
|
|
||||||
origin_of(uri).is_some_and(|o| self.call.as_ref() == Some(&o))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn app() -> AppOrigins {
|
|
||||||
AppOrigins::new(44548, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn app_origin_matches_only_the_app() {
|
|
||||||
let app = app();
|
|
||||||
assert!(app.contains("http://localhost:44548/"));
|
|
||||||
assert!(app.contains("http://localhost:44548/#/home/!room:server"));
|
|
||||||
assert!(app.contains("http://LOCALHOST:44548/public/element-call/index.html"));
|
|
||||||
for other in [
|
|
||||||
"http://localhost:44549/",
|
|
||||||
"https://localhost:44548/",
|
|
||||||
"http://127.0.0.1:44548/",
|
|
||||||
"http://localhost/",
|
|
||||||
"http://localhost.evil.example:44548/",
|
|
||||||
"http://evil.example/?http://localhost:44548/",
|
|
||||||
"https://www.youtube-nocookie.com/embed/x",
|
|
||||||
"https://chat.lotusguild.org/",
|
|
||||||
"about:blank",
|
|
||||||
"data:text/html,hi",
|
|
||||||
"null",
|
|
||||||
"",
|
|
||||||
] {
|
|
||||||
assert!(!app.contains(other), "{other}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn debug_builds_also_accept_the_bundled_and_dev_pages() {
|
|
||||||
let dev = Url::parse("http://localhost:8080").unwrap();
|
|
||||||
let app = AppOrigins::new(44548, Some(&dev));
|
|
||||||
assert_eq!(
|
|
||||||
app.contains("tauri://localhost/index.html"),
|
|
||||||
cfg!(debug_assertions)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
app.contains("http://tauri.localhost/"),
|
|
||||||
cfg!(debug_assertions)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
app.contains("http://localhost:8080/"),
|
|
||||||
cfg!(debug_assertions)
|
|
||||||
);
|
|
||||||
assert!(app.contains("http://localhost:44548/"));
|
|
||||||
assert!(!app.contains("tauri://evil/"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn app_gets_its_grants_without_a_prompt() {
|
|
||||||
let app = app();
|
|
||||||
let uri = "http://localhost:44548/";
|
|
||||||
for kind in [
|
|
||||||
Kind::Media,
|
|
||||||
Kind::DeviceInfo,
|
|
||||||
Kind::Notifications,
|
|
||||||
Kind::Geolocation,
|
|
||||||
] {
|
|
||||||
assert_eq!(
|
|
||||||
decide(kind, uri, &app, LINUX_GRANTS),
|
|
||||||
Decision::Allow,
|
|
||||||
"{kind:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
assert_eq!(
|
|
||||||
decide(Kind::Media, uri, &app, WINDOWS_GRANTS),
|
|
||||||
Decision::Allow
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decide(Kind::Notifications, uri, &app, WINDOWS_GRANTS),
|
|
||||||
Decision::Allow
|
|
||||||
);
|
|
||||||
// Location on Windows keeps WebView2's prompt.
|
|
||||||
assert_eq!(
|
|
||||||
decide(Kind::Geolocation, uri, &app, WINDOWS_GRANTS),
|
|
||||||
Decision::Default
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn other_origins_are_refused() {
|
|
||||||
let app = app();
|
|
||||||
for uri in [
|
|
||||||
"https://widget.example/",
|
|
||||||
"https://www.youtube-nocookie.com/embed/x",
|
|
||||||
"",
|
|
||||||
] {
|
|
||||||
for kind in [
|
|
||||||
Kind::Media,
|
|
||||||
Kind::DeviceInfo,
|
|
||||||
Kind::Notifications,
|
|
||||||
Kind::Geolocation,
|
|
||||||
] {
|
|
||||||
assert_eq!(decide(kind, uri, &app, LINUX_GRANTS), Decision::Deny);
|
|
||||||
assert_eq!(decide(kind, uri, &app, WINDOWS_GRANTS), Decision::Deny);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn call_frame_gets_media_only() {
|
|
||||||
let app = app();
|
|
||||||
let call = "http://127.0.0.1:44548/public/element-call/index.html?widgetId=x";
|
|
||||||
assert!(app.is_call_frame(call));
|
|
||||||
assert!(!app.contains(call));
|
|
||||||
for grants in [LINUX_GRANTS, WINDOWS_GRANTS] {
|
|
||||||
assert_eq!(decide(Kind::Media, call, &app, grants), Decision::Allow);
|
|
||||||
for kind in [Kind::DeviceInfo, Kind::Notifications, Kind::Geolocation] {
|
|
||||||
assert_eq!(decide(kind, call, &app, grants), Decision::Deny, "{kind:?}");
|
|
||||||
}
|
|
||||||
assert_eq!(decide(Kind::Other, call, &app, grants), Decision::Default);
|
|
||||||
}
|
|
||||||
for not_call in [
|
|
||||||
"http://127.0.0.1:44549/",
|
|
||||||
"https://127.0.0.1:44548/",
|
|
||||||
"http://127.0.0.2:44548/",
|
|
||||||
"http://[::1]:44548/",
|
|
||||||
] {
|
|
||||||
assert!(!app.is_call_frame(not_call), "{not_call}");
|
|
||||||
assert_eq!(
|
|
||||||
decide(Kind::Media, not_call, &app, WINDOWS_GRANTS),
|
|
||||||
Decision::Deny
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn other_kinds_are_left_to_the_engine() {
|
|
||||||
let app = app();
|
|
||||||
for uri in ["http://localhost:44548/", "https://widget.example/"] {
|
|
||||||
assert_eq!(
|
|
||||||
decide(Kind::Other, uri, &app, LINUX_GRANTS),
|
|
||||||
Decision::Default
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decide(Kind::Other, uri, &app, WINDOWS_GRANTS),
|
|
||||||
Decision::Default
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self'; script-src 'self' 'unsafe-eval' 'sha256-dT6noyex1I8o5CS9Sx/y8UOqwpZYIridpGz92gcObIM='; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob: http: https:; media-src 'self' blob: data: mediastream: http: https:; worker-src 'self' blob:; frame-src 'self' blob: http://127.0.0.1:44548 https://www.openstreetmap.org https://www.youtube-nocookie.com https://www.youtube.com https://player.vimeo.com https://www.tiktok.com https://www.dailymotion.com https://geo.dailymotion.com https://streamable.com https://player.twitch.tv https://clips.twitch.tv https://open.spotify.com https://w.soundcloud.com https://embed.music.apple.com https://platform.twitter.com https://www.instagram.com https://embed.tidal.com https://www.redditmedia.com https://embed.reddit.com https://embed.bsky.app https://www.loom.com https://player.kick.com https://www.mixcloud.com https://widget.deezer.com https://store.steampowered.com; connect-src 'self' blob: data: ipc: ws: wss: http: https: http://ipc.localhost; object-src 'none'; base-uri 'self'"
|
"csp": "default-src 'self'; script-src 'self' 'unsafe-eval' 'sha256-dT6noyex1I8o5CS9Sx/y8UOqwpZYIridpGz92gcObIM='; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob: http: https:; media-src 'self' blob: data: mediastream: http: https:; worker-src 'self' blob:; frame-src 'self' blob: https://www.openstreetmap.org https://www.youtube-nocookie.com https://www.youtube.com https://player.vimeo.com https://www.tiktok.com https://www.dailymotion.com https://geo.dailymotion.com https://streamable.com https://player.twitch.tv https://clips.twitch.tv https://open.spotify.com https://w.soundcloud.com https://embed.music.apple.com https://platform.twitter.com https://www.instagram.com https://embed.tidal.com https://www.redditmedia.com https://embed.reddit.com https://embed.bsky.app https://www.loom.com https://player.kick.com https://www.mixcloud.com https://widget.deezer.com https://store.steampowered.com; connect-src 'self' blob: data: ipc: ws: wss: http: https: http://ipc.localhost; object-src 'none'; base-uri 'self'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user