diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs index 261851f..cd7a117 100644 --- a/apps/desktop/src-tauri/build.rs +++ b/apps/desktop/src-tauri/build.rs @@ -1,3 +1,24 @@ +const TERMINAL_COMMANDS: &[&str] = &[ + "app_status", + "active_paste_target", + "preview_clipboard_image_paste", + "preview_active_clipboard_image_paste", + "pty_spawn", + "pty_write", + "pty_interrupt", + "pty_resize", + "pty_kill", + "pty_ack", + "clipboard_write_text", + "clipboard_read_text", + "open_path", + "close_paste_session", +]; + fn main() { - tauri_build::build(); + tauri_build::try_build( + tauri_build::Attributes::new() + .app_manifest(tauri_build::AppManifest::new().commands(TERMINAL_COMMANDS)), + ) + .expect("failed to build the finite terminal command authority"); } diff --git a/apps/desktop/src-tauri/capabilities/linux.json b/apps/desktop/src-tauri/capabilities/linux.json new file mode 100644 index 0000000..63fb37f --- /dev/null +++ b/apps/desktop/src-tauri/capabilities/linux.json @@ -0,0 +1,22 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "main-terminal-linux", + "description": "Allows the main window to invoke the finite terminal command set on native Ubuntu and WSLg Linux.", + "windows": ["main"], + "platforms": ["linux"], + "permissions": [ + "allow-app-status", + "allow-active-paste-target", + "allow-preview-clipboard-image-paste", + "allow-preview-active-clipboard-image-paste", + "allow-pty-write", + "allow-pty-interrupt", + "allow-pty-resize", + "allow-pty-kill", + "allow-pty-ack", + "allow-clipboard-write-text", + "allow-clipboard-read-text", + "allow-open-path", + "allow-close-paste-session" + ] +} diff --git a/apps/desktop/src-tauri/capabilities/windows.json b/apps/desktop/src-tauri/capabilities/windows.json new file mode 100644 index 0000000..910bd8a --- /dev/null +++ b/apps/desktop/src-tauri/capabilities/windows.json @@ -0,0 +1,23 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "main-terminal-windows", + "description": "Allows the main window to invoke the finite terminal command set on Windows.", + "windows": ["main"], + "platforms": ["windows"], + "permissions": [ + "allow-app-status", + "allow-active-paste-target", + "allow-preview-clipboard-image-paste", + "allow-preview-active-clipboard-image-paste", + "allow-pty-spawn", + "allow-pty-write", + "allow-pty-interrupt", + "allow-pty-resize", + "allow-pty-kill", + "allow-pty-ack", + "allow-clipboard-write-text", + "allow-clipboard-read-text", + "allow-open-path", + "allow-close-paste-session" + ] +} diff --git a/apps/desktop/src-tauri/permissions/autogenerated/active_paste_target.toml b/apps/desktop/src-tauri/permissions/autogenerated/active_paste_target.toml new file mode 100644 index 0000000..30b378b --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/active_paste_target.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-active-paste-target" +description = "Enables the active_paste_target command without any pre-configured scope." +commands.allow = ["active_paste_target"] + +[[permission]] +identifier = "deny-active-paste-target" +description = "Denies the active_paste_target command without any pre-configured scope." +commands.deny = ["active_paste_target"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/app_status.toml b/apps/desktop/src-tauri/permissions/autogenerated/app_status.toml new file mode 100644 index 0000000..992969d --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/app_status.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-app-status" +description = "Enables the app_status command without any pre-configured scope." +commands.allow = ["app_status"] + +[[permission]] +identifier = "deny-app-status" +description = "Denies the app_status command without any pre-configured scope." +commands.deny = ["app_status"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/clipboard_read_text.toml b/apps/desktop/src-tauri/permissions/autogenerated/clipboard_read_text.toml new file mode 100644 index 0000000..ab9c3cb --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/clipboard_read_text.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-clipboard-read-text" +description = "Enables the clipboard_read_text command without any pre-configured scope." +commands.allow = ["clipboard_read_text"] + +[[permission]] +identifier = "deny-clipboard-read-text" +description = "Denies the clipboard_read_text command without any pre-configured scope." +commands.deny = ["clipboard_read_text"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/clipboard_write_text.toml b/apps/desktop/src-tauri/permissions/autogenerated/clipboard_write_text.toml new file mode 100644 index 0000000..ae1cdd6 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/clipboard_write_text.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-clipboard-write-text" +description = "Enables the clipboard_write_text command without any pre-configured scope." +commands.allow = ["clipboard_write_text"] + +[[permission]] +identifier = "deny-clipboard-write-text" +description = "Denies the clipboard_write_text command without any pre-configured scope." +commands.deny = ["clipboard_write_text"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/close_paste_session.toml b/apps/desktop/src-tauri/permissions/autogenerated/close_paste_session.toml new file mode 100644 index 0000000..4d9bceb --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/close_paste_session.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-close-paste-session" +description = "Enables the close_paste_session command without any pre-configured scope." +commands.allow = ["close_paste_session"] + +[[permission]] +identifier = "deny-close-paste-session" +description = "Denies the close_paste_session command without any pre-configured scope." +commands.deny = ["close_paste_session"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/open_path.toml b/apps/desktop/src-tauri/permissions/autogenerated/open_path.toml new file mode 100644 index 0000000..17d14d1 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/open_path.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-open-path" +description = "Enables the open_path command without any pre-configured scope." +commands.allow = ["open_path"] + +[[permission]] +identifier = "deny-open-path" +description = "Denies the open_path command without any pre-configured scope." +commands.deny = ["open_path"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/preview_active_clipboard_image_paste.toml b/apps/desktop/src-tauri/permissions/autogenerated/preview_active_clipboard_image_paste.toml new file mode 100644 index 0000000..59f984e --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/preview_active_clipboard_image_paste.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-preview-active-clipboard-image-paste" +description = "Enables the preview_active_clipboard_image_paste command without any pre-configured scope." +commands.allow = ["preview_active_clipboard_image_paste"] + +[[permission]] +identifier = "deny-preview-active-clipboard-image-paste" +description = "Denies the preview_active_clipboard_image_paste command without any pre-configured scope." +commands.deny = ["preview_active_clipboard_image_paste"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/preview_clipboard_image_paste.toml b/apps/desktop/src-tauri/permissions/autogenerated/preview_clipboard_image_paste.toml new file mode 100644 index 0000000..0aaf8d9 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/preview_clipboard_image_paste.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-preview-clipboard-image-paste" +description = "Enables the preview_clipboard_image_paste command without any pre-configured scope." +commands.allow = ["preview_clipboard_image_paste"] + +[[permission]] +identifier = "deny-preview-clipboard-image-paste" +description = "Denies the preview_clipboard_image_paste command without any pre-configured scope." +commands.deny = ["preview_clipboard_image_paste"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/pty_ack.toml b/apps/desktop/src-tauri/permissions/autogenerated/pty_ack.toml new file mode 100644 index 0000000..80cebe3 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/pty_ack.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-pty-ack" +description = "Enables the pty_ack command without any pre-configured scope." +commands.allow = ["pty_ack"] + +[[permission]] +identifier = "deny-pty-ack" +description = "Denies the pty_ack command without any pre-configured scope." +commands.deny = ["pty_ack"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/pty_interrupt.toml b/apps/desktop/src-tauri/permissions/autogenerated/pty_interrupt.toml new file mode 100644 index 0000000..4c9e531 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/pty_interrupt.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-pty-interrupt" +description = "Enables the pty_interrupt command without any pre-configured scope." +commands.allow = ["pty_interrupt"] + +[[permission]] +identifier = "deny-pty-interrupt" +description = "Denies the pty_interrupt command without any pre-configured scope." +commands.deny = ["pty_interrupt"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/pty_kill.toml b/apps/desktop/src-tauri/permissions/autogenerated/pty_kill.toml new file mode 100644 index 0000000..69f0b3e --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/pty_kill.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-pty-kill" +description = "Enables the pty_kill command without any pre-configured scope." +commands.allow = ["pty_kill"] + +[[permission]] +identifier = "deny-pty-kill" +description = "Denies the pty_kill command without any pre-configured scope." +commands.deny = ["pty_kill"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/pty_resize.toml b/apps/desktop/src-tauri/permissions/autogenerated/pty_resize.toml new file mode 100644 index 0000000..f34001b --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/pty_resize.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-pty-resize" +description = "Enables the pty_resize command without any pre-configured scope." +commands.allow = ["pty_resize"] + +[[permission]] +identifier = "deny-pty-resize" +description = "Denies the pty_resize command without any pre-configured scope." +commands.deny = ["pty_resize"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/pty_spawn.toml b/apps/desktop/src-tauri/permissions/autogenerated/pty_spawn.toml new file mode 100644 index 0000000..71c2eac --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/pty_spawn.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-pty-spawn" +description = "Enables the pty_spawn command without any pre-configured scope." +commands.allow = ["pty_spawn"] + +[[permission]] +identifier = "deny-pty-spawn" +description = "Denies the pty_spawn command without any pre-configured scope." +commands.deny = ["pty_spawn"] diff --git a/apps/desktop/src-tauri/permissions/autogenerated/pty_write.toml b/apps/desktop/src-tauri/permissions/autogenerated/pty_write.toml new file mode 100644 index 0000000..72aab29 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/autogenerated/pty_write.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-pty-write" +description = "Enables the pty_write command without any pre-configured scope." +commands.allow = ["pty_write"] + +[[permission]] +identifier = "deny-pty-write" +description = "Denies the pty_write command without any pre-configured scope." +commands.deny = ["pty_write"] diff --git a/apps/desktop/src-tauri/permissions/terminal.toml b/apps/desktop/src-tauri/permissions/terminal.toml new file mode 100644 index 0000000..7879f44 --- /dev/null +++ b/apps/desktop/src-tauri/permissions/terminal.toml @@ -0,0 +1,69 @@ +[[permission]] +identifier = "allow-app-status" +description = "Allows the app_status command." +commands.allow = ["app_status"] + +[[permission]] +identifier = "allow-active-paste-target" +description = "Allows the active_paste_target command." +commands.allow = ["active_paste_target"] + +[[permission]] +identifier = "allow-preview-clipboard-image-paste" +description = "Allows the preview_clipboard_image_paste command." +commands.allow = ["preview_clipboard_image_paste"] + +[[permission]] +identifier = "allow-preview-active-clipboard-image-paste" +description = "Allows the preview_active_clipboard_image_paste command." +commands.allow = ["preview_active_clipboard_image_paste"] + +[[permission]] +identifier = "allow-pty-spawn" +description = "Allows the pty_spawn command." +commands.allow = ["pty_spawn"] + +[[permission]] +identifier = "allow-pty-write" +description = "Allows the pty_write command." +commands.allow = ["pty_write"] + +[[permission]] +identifier = "allow-pty-interrupt" +description = "Allows the pty_interrupt command." +commands.allow = ["pty_interrupt"] + +[[permission]] +identifier = "allow-pty-resize" +description = "Allows the pty_resize command." +commands.allow = ["pty_resize"] + +[[permission]] +identifier = "allow-pty-kill" +description = "Allows the pty_kill command." +commands.allow = ["pty_kill"] + +[[permission]] +identifier = "allow-pty-ack" +description = "Allows the pty_ack command." +commands.allow = ["pty_ack"] + +[[permission]] +identifier = "allow-clipboard-write-text" +description = "Allows the clipboard_write_text command." +commands.allow = ["clipboard_write_text"] + +[[permission]] +identifier = "allow-clipboard-read-text" +description = "Allows the clipboard_read_text command." +commands.allow = ["clipboard_read_text"] + +[[permission]] +identifier = "allow-open-path" +description = "Allows the open_path command." +commands.allow = ["open_path"] + +[[permission]] +identifier = "allow-close-paste-session" +description = "Allows the close_paste_session command." +commands.allow = ["close_paste_session"] diff --git a/apps/desktop/src-tauri/tests/authority_manifest.rs b/apps/desktop/src-tauri/tests/authority_manifest.rs new file mode 100644 index 0000000..c984042 --- /dev/null +++ b/apps/desktop/src-tauri/tests/authority_manifest.rs @@ -0,0 +1,151 @@ +use serde_json::Value; + +const APP_COMMANDS: &[&str] = &[ + "app_status", + "active_paste_target", + "preview_clipboard_image_paste", + "preview_active_clipboard_image_paste", + "pty_spawn", + "pty_write", + "pty_interrupt", + "pty_resize", + "pty_kill", + "pty_ack", + "clipboard_write_text", + "clipboard_read_text", + "open_path", + "close_paste_session", +]; + +const TERMINAL_PERMISSIONS: &[&str] = &[ + "allow-app-status", + "allow-active-paste-target", + "allow-preview-clipboard-image-paste", + "allow-preview-active-clipboard-image-paste", + "allow-pty-spawn", + "allow-pty-write", + "allow-pty-interrupt", + "allow-pty-resize", + "allow-pty-kill", + "allow-pty-ack", + "allow-clipboard-write-text", + "allow-clipboard-read-text", + "allow-open-path", + "allow-close-paste-session", +]; + +fn handler_commands(source: &str) -> Vec<&str> { + source + .split("tauri::generate_handler![") + .nth(1) + .expect("application must register a finite command handler") + .lines() + .take_while(|line| line.trim() != "])") + .map(str::trim) + .map(|command| command.trim_end_matches(',')) + .filter(|command| !command.is_empty()) + .collect() +} + +fn manifest_commands(source: &str) -> Vec<&str> { + source + .split("const TERMINAL_COMMANDS: &[&str] = &[") + .nth(1) + .expect("build must declare a finite command manifest") + .lines() + .take_while(|line| line.trim() != "];") + .map(str::trim) + .map(|command| command.trim_matches(',').trim_matches('"')) + .filter(|command| !command.is_empty()) + .collect() +} + +#[test] +fn authority_manifest_parsing_accepts_crlf_source() { + let handler_source = include_str!("../src/lib.rs").replace('\n', "\r\n"); + let build_source = include_str!("../build.rs").replace('\n', "\r\n"); + + assert_eq!(handler_commands(&handler_source), APP_COMMANDS); + assert_eq!(manifest_commands(&build_source), APP_COMMANDS); +} + +#[test] +fn authority_manifest_matches_registered_handlers_and_target_capabilities() { + let handler_source = include_str!("../src/lib.rs"); + let build_source = include_str!("../build.rs"); + let permissions = include_str!("../permissions/terminal.toml"); + + assert_eq!(handler_commands(handler_source), APP_COMMANDS); + + assert_eq!(manifest_commands(build_source), APP_COMMANDS); + assert!(build_source + .contains(".app_manifest(tauri_build::AppManifest::new().commands(TERMINAL_COMMANDS))")); + assert_eq!( + permissions.matches("[[permission]]").count(), + APP_COMMANDS.len() + ); + + for (command, permission) in APP_COMMANDS.iter().zip(TERMINAL_PERMISSIONS) { + assert!( + build_source.contains(&format!("\"{command}\"")), + "build manifest must register {command}" + ); + assert!( + permissions.contains(&format!("identifier = \"{permission}\"")), + "terminal permission must name {command}" + ); + assert!( + permissions.contains(&format!("commands.allow = [\"{command}\"]")), + "terminal permission must allow only {command}" + ); + let generated = std::fs::read_to_string(format!( + "{}/permissions/autogenerated/{command}.toml", + env!("CARGO_MANIFEST_DIR") + )) + .expect("Tauri must generate a command authority contract"); + assert!(generated.contains(&format!("commands.allow = [\"{command}\"]"))); + assert!(generated.contains(&format!("commands.deny = [\"{command}\"]"))); + } +} + +#[test] +fn target_capabilities_grant_only_the_finite_terminal_authority() { + for (path, platform) in [ + ("../capabilities/windows.json", "windows"), + ("../capabilities/linux.json", "linux"), + ] { + let capability: Value = serde_json::from_str( + &std::fs::read_to_string(format!("{}/tests/{path}", env!("CARGO_MANIFEST_DIR"))) + .expect("target capability must exist"), + ) + .expect("target capability must be valid JSON"); + assert_eq!(capability["windows"], Value::from(["main"])); + assert_eq!(capability["platforms"], Value::from([platform])); + let expected_permissions: Vec<_> = TERMINAL_PERMISSIONS + .iter() + .copied() + .filter(|permission| platform == "windows" || *permission != "allow-pty-spawn") + .collect(); + assert_eq!( + capability["permissions"], + serde_json::to_value(expected_permissions).expect("permission list must serialize") + ); + assert_eq!( + capability["permissions"] + .as_array() + .expect("target permissions must be an array") + .contains(&Value::from("allow-pty-spawn")), + platform == "windows" + ); + } + + let default_capability: Value = + serde_json::from_str(include_str!("../capabilities/default.json")) + .expect("default capability must be valid JSON"); + let default_permissions = default_capability["permissions"] + .as_array() + .expect("default permissions must be an array"); + assert!(default_permissions.iter().all(|permission| !permission + .as_str() + .is_some_and(|value| value.starts_with("allow-")))); +}