diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 0dbaabef..60b06d31 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -3,7 +3,32 @@ use std::process::Command; fn main() { add_command_line_tools_swift_library_path(); - let mut attributes = tauri_build::Attributes::new(); + let app_manifest = tauri_build::AppManifest::new().commands(&[ + "get_app_state", + "check_accessibility", + "approve_pairing", + "reject_pairing", + "disconnect_all", + "modifier_overlay_ready", + "modifier_overlay_present", + "forget_device", + "save_settings", + "set_telemetry_consent", + "mark_setup_shown", + "complete_setup", + "list_switch_profiles", + "save_switch_profile", + "delete_switch_profile", + "complete_profile_exit", + "cancel_profile_exit", + "take_navigation_request", + "check_for_updates", + "download_update", + "cancel_update_download", + "install_update", + "export_diagnostics", + ]); + let mut attributes = tauri_build::Attributes::new().app_manifest(app_manifest); if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { let manifest = if std::env::var("PROFILE").as_deref() == Ok("release") { include_str!("windows/uiaccess.manifest") diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json deleted file mode 100644 index fc1ada7e..00000000 --- a/src-tauri/capabilities/default.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Default capability for the Switchify PC window", - "windows": ["main", "modifier-overlay"], - "permissions": ["core:default"] -} diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json new file mode 100644 index 00000000..7162bed2 --- /dev/null +++ b/src-tauri/capabilities/main.json @@ -0,0 +1,30 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "main", + "description": "Application capability for the main Switchify PC window", + "windows": ["main"], + "permissions": [ + "core:default", + "allow-get-app-state", + "allow-check-accessibility", + "allow-approve-pairing", + "allow-reject-pairing", + "allow-disconnect-all", + "allow-forget-device", + "allow-save-settings", + "allow-set-telemetry-consent", + "allow-mark-setup-shown", + "allow-complete-setup", + "allow-list-switch-profiles", + "allow-save-switch-profile", + "allow-delete-switch-profile", + "allow-complete-profile-exit", + "allow-cancel-profile-exit", + "allow-take-navigation-request", + "allow-check-for-updates", + "allow-download-update", + "allow-cancel-update-download", + "allow-install-update", + "allow-export-diagnostics" + ] +} diff --git a/src-tauri/capabilities/modifier-overlay.json b/src-tauri/capabilities/modifier-overlay.json new file mode 100644 index 00000000..66f845d5 --- /dev/null +++ b/src-tauri/capabilities/modifier-overlay.json @@ -0,0 +1,12 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "modifier-overlay", + "description": "Minimal capability for the non-interactive modifier overlay", + "windows": ["modifier-overlay"], + "permissions": [ + "core:event:allow-listen", + "core:event:allow-unlisten", + "allow-modifier-overlay-ready", + "allow-modifier-overlay-present" + ] +} diff --git a/src-tauri/permissions/autogenerated/approve_pairing.toml b/src-tauri/permissions/autogenerated/approve_pairing.toml new file mode 100644 index 00000000..6ad1b75a --- /dev/null +++ b/src-tauri/permissions/autogenerated/approve_pairing.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-approve-pairing" +description = "Enables the approve_pairing command without any pre-configured scope." +commands.allow = ["approve_pairing"] + +[[permission]] +identifier = "deny-approve-pairing" +description = "Denies the approve_pairing command without any pre-configured scope." +commands.deny = ["approve_pairing"] diff --git a/src-tauri/permissions/autogenerated/cancel_profile_exit.toml b/src-tauri/permissions/autogenerated/cancel_profile_exit.toml new file mode 100644 index 00000000..bff7dc73 --- /dev/null +++ b/src-tauri/permissions/autogenerated/cancel_profile_exit.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-cancel-profile-exit" +description = "Enables the cancel_profile_exit command without any pre-configured scope." +commands.allow = ["cancel_profile_exit"] + +[[permission]] +identifier = "deny-cancel-profile-exit" +description = "Denies the cancel_profile_exit command without any pre-configured scope." +commands.deny = ["cancel_profile_exit"] diff --git a/src-tauri/permissions/autogenerated/cancel_update_download.toml b/src-tauri/permissions/autogenerated/cancel_update_download.toml new file mode 100644 index 00000000..d5dea0c1 --- /dev/null +++ b/src-tauri/permissions/autogenerated/cancel_update_download.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-cancel-update-download" +description = "Enables the cancel_update_download command without any pre-configured scope." +commands.allow = ["cancel_update_download"] + +[[permission]] +identifier = "deny-cancel-update-download" +description = "Denies the cancel_update_download command without any pre-configured scope." +commands.deny = ["cancel_update_download"] diff --git a/src-tauri/permissions/autogenerated/check_accessibility.toml b/src-tauri/permissions/autogenerated/check_accessibility.toml new file mode 100644 index 00000000..06dbc72a --- /dev/null +++ b/src-tauri/permissions/autogenerated/check_accessibility.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-check-accessibility" +description = "Enables the check_accessibility command without any pre-configured scope." +commands.allow = ["check_accessibility"] + +[[permission]] +identifier = "deny-check-accessibility" +description = "Denies the check_accessibility command without any pre-configured scope." +commands.deny = ["check_accessibility"] diff --git a/src-tauri/permissions/autogenerated/check_for_updates.toml b/src-tauri/permissions/autogenerated/check_for_updates.toml new file mode 100644 index 00000000..f24151fe --- /dev/null +++ b/src-tauri/permissions/autogenerated/check_for_updates.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-check-for-updates" +description = "Enables the check_for_updates command without any pre-configured scope." +commands.allow = ["check_for_updates"] + +[[permission]] +identifier = "deny-check-for-updates" +description = "Denies the check_for_updates command without any pre-configured scope." +commands.deny = ["check_for_updates"] diff --git a/src-tauri/permissions/autogenerated/complete_profile_exit.toml b/src-tauri/permissions/autogenerated/complete_profile_exit.toml new file mode 100644 index 00000000..e6e1683d --- /dev/null +++ b/src-tauri/permissions/autogenerated/complete_profile_exit.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-complete-profile-exit" +description = "Enables the complete_profile_exit command without any pre-configured scope." +commands.allow = ["complete_profile_exit"] + +[[permission]] +identifier = "deny-complete-profile-exit" +description = "Denies the complete_profile_exit command without any pre-configured scope." +commands.deny = ["complete_profile_exit"] diff --git a/src-tauri/permissions/autogenerated/complete_setup.toml b/src-tauri/permissions/autogenerated/complete_setup.toml new file mode 100644 index 00000000..408a5907 --- /dev/null +++ b/src-tauri/permissions/autogenerated/complete_setup.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-complete-setup" +description = "Enables the complete_setup command without any pre-configured scope." +commands.allow = ["complete_setup"] + +[[permission]] +identifier = "deny-complete-setup" +description = "Denies the complete_setup command without any pre-configured scope." +commands.deny = ["complete_setup"] diff --git a/src-tauri/permissions/autogenerated/delete_switch_profile.toml b/src-tauri/permissions/autogenerated/delete_switch_profile.toml new file mode 100644 index 00000000..34dca3c8 --- /dev/null +++ b/src-tauri/permissions/autogenerated/delete_switch_profile.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-delete-switch-profile" +description = "Enables the delete_switch_profile command without any pre-configured scope." +commands.allow = ["delete_switch_profile"] + +[[permission]] +identifier = "deny-delete-switch-profile" +description = "Denies the delete_switch_profile command without any pre-configured scope." +commands.deny = ["delete_switch_profile"] diff --git a/src-tauri/permissions/autogenerated/disconnect_all.toml b/src-tauri/permissions/autogenerated/disconnect_all.toml new file mode 100644 index 00000000..3f02705d --- /dev/null +++ b/src-tauri/permissions/autogenerated/disconnect_all.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-disconnect-all" +description = "Enables the disconnect_all command without any pre-configured scope." +commands.allow = ["disconnect_all"] + +[[permission]] +identifier = "deny-disconnect-all" +description = "Denies the disconnect_all command without any pre-configured scope." +commands.deny = ["disconnect_all"] diff --git a/src-tauri/permissions/autogenerated/download_update.toml b/src-tauri/permissions/autogenerated/download_update.toml new file mode 100644 index 00000000..5b327656 --- /dev/null +++ b/src-tauri/permissions/autogenerated/download_update.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-download-update" +description = "Enables the download_update command without any pre-configured scope." +commands.allow = ["download_update"] + +[[permission]] +identifier = "deny-download-update" +description = "Denies the download_update command without any pre-configured scope." +commands.deny = ["download_update"] diff --git a/src-tauri/permissions/autogenerated/export_diagnostics.toml b/src-tauri/permissions/autogenerated/export_diagnostics.toml new file mode 100644 index 00000000..600a87d1 --- /dev/null +++ b/src-tauri/permissions/autogenerated/export_diagnostics.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-export-diagnostics" +description = "Enables the export_diagnostics command without any pre-configured scope." +commands.allow = ["export_diagnostics"] + +[[permission]] +identifier = "deny-export-diagnostics" +description = "Denies the export_diagnostics command without any pre-configured scope." +commands.deny = ["export_diagnostics"] diff --git a/src-tauri/permissions/autogenerated/forget_device.toml b/src-tauri/permissions/autogenerated/forget_device.toml new file mode 100644 index 00000000..8286f089 --- /dev/null +++ b/src-tauri/permissions/autogenerated/forget_device.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-forget-device" +description = "Enables the forget_device command without any pre-configured scope." +commands.allow = ["forget_device"] + +[[permission]] +identifier = "deny-forget-device" +description = "Denies the forget_device command without any pre-configured scope." +commands.deny = ["forget_device"] diff --git a/src-tauri/permissions/autogenerated/get_app_state.toml b/src-tauri/permissions/autogenerated/get_app_state.toml new file mode 100644 index 00000000..da30299a --- /dev/null +++ b/src-tauri/permissions/autogenerated/get_app_state.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-get-app-state" +description = "Enables the get_app_state command without any pre-configured scope." +commands.allow = ["get_app_state"] + +[[permission]] +identifier = "deny-get-app-state" +description = "Denies the get_app_state command without any pre-configured scope." +commands.deny = ["get_app_state"] diff --git a/src-tauri/permissions/autogenerated/install_update.toml b/src-tauri/permissions/autogenerated/install_update.toml new file mode 100644 index 00000000..e82a7538 --- /dev/null +++ b/src-tauri/permissions/autogenerated/install_update.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-install-update" +description = "Enables the install_update command without any pre-configured scope." +commands.allow = ["install_update"] + +[[permission]] +identifier = "deny-install-update" +description = "Denies the install_update command without any pre-configured scope." +commands.deny = ["install_update"] diff --git a/src-tauri/permissions/autogenerated/list_switch_profiles.toml b/src-tauri/permissions/autogenerated/list_switch_profiles.toml new file mode 100644 index 00000000..bade9de5 --- /dev/null +++ b/src-tauri/permissions/autogenerated/list_switch_profiles.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-list-switch-profiles" +description = "Enables the list_switch_profiles command without any pre-configured scope." +commands.allow = ["list_switch_profiles"] + +[[permission]] +identifier = "deny-list-switch-profiles" +description = "Denies the list_switch_profiles command without any pre-configured scope." +commands.deny = ["list_switch_profiles"] diff --git a/src-tauri/permissions/autogenerated/mark_setup_shown.toml b/src-tauri/permissions/autogenerated/mark_setup_shown.toml new file mode 100644 index 00000000..d427e4d5 --- /dev/null +++ b/src-tauri/permissions/autogenerated/mark_setup_shown.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-mark-setup-shown" +description = "Enables the mark_setup_shown command without any pre-configured scope." +commands.allow = ["mark_setup_shown"] + +[[permission]] +identifier = "deny-mark-setup-shown" +description = "Denies the mark_setup_shown command without any pre-configured scope." +commands.deny = ["mark_setup_shown"] diff --git a/src-tauri/permissions/autogenerated/modifier_overlay_present.toml b/src-tauri/permissions/autogenerated/modifier_overlay_present.toml new file mode 100644 index 00000000..e1dd0d16 --- /dev/null +++ b/src-tauri/permissions/autogenerated/modifier_overlay_present.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-modifier-overlay-present" +description = "Enables the modifier_overlay_present command without any pre-configured scope." +commands.allow = ["modifier_overlay_present"] + +[[permission]] +identifier = "deny-modifier-overlay-present" +description = "Denies the modifier_overlay_present command without any pre-configured scope." +commands.deny = ["modifier_overlay_present"] diff --git a/src-tauri/permissions/autogenerated/modifier_overlay_ready.toml b/src-tauri/permissions/autogenerated/modifier_overlay_ready.toml new file mode 100644 index 00000000..7b162f09 --- /dev/null +++ b/src-tauri/permissions/autogenerated/modifier_overlay_ready.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-modifier-overlay-ready" +description = "Enables the modifier_overlay_ready command without any pre-configured scope." +commands.allow = ["modifier_overlay_ready"] + +[[permission]] +identifier = "deny-modifier-overlay-ready" +description = "Denies the modifier_overlay_ready command without any pre-configured scope." +commands.deny = ["modifier_overlay_ready"] diff --git a/src-tauri/permissions/autogenerated/reject_pairing.toml b/src-tauri/permissions/autogenerated/reject_pairing.toml new file mode 100644 index 00000000..e637e68e --- /dev/null +++ b/src-tauri/permissions/autogenerated/reject_pairing.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-reject-pairing" +description = "Enables the reject_pairing command without any pre-configured scope." +commands.allow = ["reject_pairing"] + +[[permission]] +identifier = "deny-reject-pairing" +description = "Denies the reject_pairing command without any pre-configured scope." +commands.deny = ["reject_pairing"] diff --git a/src-tauri/permissions/autogenerated/save_settings.toml b/src-tauri/permissions/autogenerated/save_settings.toml new file mode 100644 index 00000000..7cf8c7da --- /dev/null +++ b/src-tauri/permissions/autogenerated/save_settings.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-save-settings" +description = "Enables the save_settings command without any pre-configured scope." +commands.allow = ["save_settings"] + +[[permission]] +identifier = "deny-save-settings" +description = "Denies the save_settings command without any pre-configured scope." +commands.deny = ["save_settings"] diff --git a/src-tauri/permissions/autogenerated/save_switch_profile.toml b/src-tauri/permissions/autogenerated/save_switch_profile.toml new file mode 100644 index 00000000..fc485115 --- /dev/null +++ b/src-tauri/permissions/autogenerated/save_switch_profile.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-save-switch-profile" +description = "Enables the save_switch_profile command without any pre-configured scope." +commands.allow = ["save_switch_profile"] + +[[permission]] +identifier = "deny-save-switch-profile" +description = "Denies the save_switch_profile command without any pre-configured scope." +commands.deny = ["save_switch_profile"] diff --git a/src-tauri/permissions/autogenerated/set_telemetry_consent.toml b/src-tauri/permissions/autogenerated/set_telemetry_consent.toml new file mode 100644 index 00000000..c1f69c8c --- /dev/null +++ b/src-tauri/permissions/autogenerated/set_telemetry_consent.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-set-telemetry-consent" +description = "Enables the set_telemetry_consent command without any pre-configured scope." +commands.allow = ["set_telemetry_consent"] + +[[permission]] +identifier = "deny-set-telemetry-consent" +description = "Denies the set_telemetry_consent command without any pre-configured scope." +commands.deny = ["set_telemetry_consent"] diff --git a/src-tauri/permissions/autogenerated/take_navigation_request.toml b/src-tauri/permissions/autogenerated/take_navigation_request.toml new file mode 100644 index 00000000..e6fd95c8 --- /dev/null +++ b/src-tauri/permissions/autogenerated/take_navigation_request.toml @@ -0,0 +1,11 @@ +# Automatically generated - DO NOT EDIT! + +[[permission]] +identifier = "allow-take-navigation-request" +description = "Enables the take_navigation_request command without any pre-configured scope." +commands.allow = ["take_navigation_request"] + +[[permission]] +identifier = "deny-take-navigation-request" +description = "Denies the take_navigation_request command without any pre-configured scope." +commands.deny = ["take_navigation_request"] diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f1d06a4e..e5d85041 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -23,7 +23,8 @@ } ], "security": { - "csp": null + "csp": "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src ipc: http://ipc.localhost; object-src 'none'; base-uri 'none'; frame-src 'none'; form-action 'none'", + "devCsp": "" } }, "plugins": { diff --git a/src-tauri/tests/config.rs b/src-tauri/tests/config.rs index f2d06b91..8e561a1f 100644 --- a/src-tauri/tests/config.rs +++ b/src-tauri/tests/config.rs @@ -33,3 +33,108 @@ fn application_configuration_uses_the_promoted_identity() { ); assert_eq!(config["bundle"]["macOS"]["hardenedRuntime"], true); } + +fn capability(path: &str) -> serde_json::Value { + serde_json::from_str(path).expect("valid capability configuration") +} + +fn permission_names(capability: &serde_json::Value) -> Vec<&str> { + capability["permissions"] + .as_array() + .expect("capability permissions") + .iter() + .map(|permission| permission.as_str().expect("permission name")) + .collect() +} + +fn command_names(source: &str, start: &str, end: &str) -> std::collections::BTreeSet { + source + .split_once(start) + .expect("command list start") + .1 + .split_once(end) + .expect("command list end") + .0 + .split(',') + .map(|command| command.trim().trim_matches('"').to_owned()) + .filter(|command| !command.is_empty()) + .collect() +} + +#[test] +fn every_invoke_handler_command_is_registered_in_the_app_manifest() { + let manifest_commands = command_names(include_str!("../build.rs"), ".commands(&[", "]);"); + let handler_commands = command_names( + include_str!("../src/lib.rs"), + "tauri::generate_handler![", + "])", + ); + assert_eq!(manifest_commands, handler_commands); +} + +#[test] +fn main_window_has_application_commands_and_core_defaults() { + let main = capability(include_str!("../capabilities/main.json")); + assert_eq!(main["windows"], serde_json::json!(["main"])); + + let permissions = permission_names(&main); + assert!(permissions.contains(&"core:default")); + for permission in [ + "allow-get-app-state", + "allow-approve-pairing", + "allow-save-settings", + "allow-list-switch-profiles", + "allow-check-for-updates", + "allow-export-diagnostics", + ] { + assert!(permissions.contains(&permission), "missing {permission}"); + } +} + +#[test] +fn modifier_overlay_has_only_its_minimal_event_and_command_permissions() { + let overlay = capability(include_str!("../capabilities/modifier-overlay.json")); + assert_eq!(overlay["windows"], serde_json::json!(["modifier-overlay"])); + assert_eq!( + permission_names(&overlay), + [ + "core:event:allow-listen", + "core:event:allow-unlisten", + "allow-modifier-overlay-ready", + "allow-modifier-overlay-present", + ] + ); +} + +#[test] +fn modifier_overlay_cannot_invoke_sensitive_application_commands() { + let overlay = capability(include_str!("../capabilities/modifier-overlay.json")); + let permissions = permission_names(&overlay); + for denied in [ + "allow-save-settings", + "allow-approve-pairing", + "allow-reject-pairing", + "allow-check-for-updates", + "allow-download-update", + "allow-install-update", + "allow-export-diagnostics", + "allow-list-switch-profiles", + "allow-save-switch-profile", + "allow-delete-switch-profile", + ] { + assert!(!permissions.contains(&denied), "overlay grants {denied}"); + } +} + +#[test] +fn production_csp_allows_only_local_assets_and_tauri_ipc() { + let config: serde_json::Value = + serde_json::from_str(include_str!("../tauri.conf.json")).expect("valid Tauri config"); + let security = &config["app"]["security"]; + assert_eq!(security["devCsp"], ""); + assert_ne!(security["devCsp"], security["csp"]); + assert_eq!( + security["csp"], + "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src ipc: http://ipc.localhost; object-src 'none'; base-uri 'none'; frame-src 'none'; form-action 'none'" + ); +}