From 168629646335f24cc7f1c4a61df22688b2198f98 Mon Sep 17 00:00:00 2001 From: Tony <68118705+Legend-Master@users.noreply.github.com> Date: Fri, 16 May 2025 22:54:23 +0800 Subject: [PATCH] refactor: put dynamic acl to a feature (#13418) * refactor: put dynamic acl to a feature * Add change file * Tweak remove_unused_commands's docs * License header * Document the feature * Merge remote-tracking branch 'upstream/dev' into dynamic-acl-feature * Use a inner non generic fn for add_capability * Clippy and macro stability notice * Merge remote-tracking branch 'upstream/dev' into dynamic-acl-feature * Format --- .changes/dynamic-acl-feature.md | 7 + Cargo.lock | 82 ++----- crates/tauri-cli/config.schema.json | 2 +- crates/tauri-cli/src/icon.rs | 1 + crates/tauri-cli/src/interface/rust.rs | 4 +- crates/tauri-codegen/src/context.rs | 3 +- crates/tauri-macos-sign/src/lib.rs | 5 +- crates/tauri-macros/src/command/wrapper.rs | 1 + crates/tauri-runtime-wry/src/lib.rs | 2 - .../schemas/config.schema.json | 2 +- crates/tauri-utils/src/acl/resolved.rs | 7 + crates/tauri-utils/src/config.rs | 2 +- crates/tauri/Cargo.toml | 3 +- crates/tauri/src/ipc/authority.rs | 223 +++++------------- crates/tauri/src/ipc/capability_builder.rs | 170 +++++++++++++ crates/tauri/src/ipc/mod.rs | 7 +- crates/tauri/src/lib.rs | 6 +- 17 files changed, 274 insertions(+), 253 deletions(-) create mode 100644 .changes/dynamic-acl-feature.md create mode 100644 crates/tauri/src/ipc/capability_builder.rs diff --git a/.changes/dynamic-acl-feature.md b/.changes/dynamic-acl-feature.md new file mode 100644 index 000000000..413e1468d --- /dev/null +++ b/.changes/dynamic-acl-feature.md @@ -0,0 +1,7 @@ +--- +"tauri": 'minor:changes' +"tauri-codegen": 'minor:changes' +"tauri-utils": 'minor:changes' +--- + +Put dynamic ACL into a feature `dynamic-acl`, this is currently enabled by default to align with the previous behaviors, you can disable it through `default-features = false` to reduce the final binary size by not including the ACL references diff --git a/Cargo.lock b/Cargo.lock index 17f507735..d01225101 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,15 +145,15 @@ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_log-sys" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" [[package]] name = "android_logger" -version = "0.14.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" +checksum = "f6f39be698127218cca460cb624878c9aa4e2b47dba3b277963d2bf00bad263b" dependencies = [ "android_log-sys", "env_filter", @@ -787,12 +787,6 @@ dependencies = [ "digest", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -847,9 +841,9 @@ checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" [[package]] name = "borsh" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", "cfg_aliases", @@ -857,9 +851,9 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate 3.2.0", @@ -1307,36 +1301,6 @@ dependencies = [ "digest", ] -[[package]] -name = "cocoa" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" -dependencies = [ - "bitflags 2.7.0", - "block", - "cocoa-foundation", - "core-foundation 0.10.0", - "core-graphics", - "foreign-types 0.5.0", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" -dependencies = [ - "bitflags 2.7.0", - "block", - "core-foundation 0.10.0", - "core-graphics-types", - "libc", - "objc", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -4457,15 +4421,6 @@ dependencies = [ "vlq", ] -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "markup5ever" version = "0.11.0" @@ -5002,15 +4957,6 @@ dependencies = [ "libc", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -7001,9 +6947,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.36.0" +version = "1.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" dependencies = [ "arrayvec", "borsh", @@ -8728,16 +8674,16 @@ dependencies = [ [[package]] name = "tauri-plugin-log" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd784c138c08a43954bc3e735402e6b2b2ee8d8c254a7391f4e77c01273dd5" +checksum = "8d2b582d860eb214f28323f4ce4f2797ae3b78f197e27b11677f976f9f52aedb" dependencies = [ "android_logger", "byte-unit", - "cocoa", "fern", "log", - "objc", + "objc2 0.6.0", + "objc2-foundation 0.3.0", "serde", "serde_json", "serde_repr", diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index 75e1b997e..497a37822 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -1873,7 +1873,7 @@ } }, "removeUnusedCommands": { - "description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4", + "description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4", "default": false, "type": "boolean" } diff --git a/crates/tauri-cli/src/icon.rs b/crates/tauri-cli/src/icon.rs index f0277f17f..da3522df3 100644 --- a/crates/tauri-cli/src/icon.rs +++ b/crates/tauri-cli/src/icon.rs @@ -60,6 +60,7 @@ pub struct Options { ios_color: String, } +#[allow(clippy::large_enum_variant)] enum Source { Svg(resvg::usvg::Tree), DynamicImage(DynamicImage), diff --git a/crates/tauri-cli/src/interface/rust.rs b/crates/tauri-cli/src/interface/rust.rs index c0bcf0efb..ee8fffd30 100644 --- a/crates/tauri-cli/src/interface/rust.rs +++ b/crates/tauri-cli/src/interface/rust.rs @@ -1103,7 +1103,7 @@ impl RustAppSettings { .to_string() }) }); - let target = TargetPlatform::from_triple(&target_triple); + let target_platform = TargetPlatform::from_triple(&target_triple); Ok(Self { manifest: Mutex::new(manifest), @@ -1113,7 +1113,7 @@ impl RustAppSettings { package_settings, cargo_config, target_triple, - target_platform: target, + target_platform, }) } diff --git a/crates/tauri-codegen/src/context.rs b/crates/tauri-codegen/src/context.rs index e2c759f79..be519a2d5 100644 --- a/crates/tauri-codegen/src/context.rs +++ b/crates/tauri-codegen/src/context.rs @@ -409,7 +409,8 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult { identity, ); - let runtime_authority = quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved)); + let runtime_authority = + quote!(#root::runtime_acl!(#root::ipc::RuntimeAuthority::new, #acl_tokens, #resolved)); let plugin_global_api_scripts = if config.app.with_global_tauri { if let Some(scripts) = tauri_utils::plugin::read_global_api_scripts(&out_dir) { diff --git a/crates/tauri-macos-sign/src/lib.rs b/crates/tauri-macos-sign/src/lib.rs index f268827c2..ea47c0f46 100644 --- a/crates/tauri-macos-sign/src/lib.rs +++ b/crates/tauri-macos-sign/src/lib.rs @@ -263,10 +263,7 @@ fn assert_command( let status = response.map_err(|e| std::io::Error::new(e.kind(), format!("{error_message}: {e}")))?; if !status.success() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - error_message, - )) + Err(std::io::Error::other(error_message)) } else { Ok(()) } diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs index 683e9f6f5..54b3857b0 100644 --- a/crates/tauri-macros/src/command/wrapper.rs +++ b/crates/tauri-macros/src/command/wrapper.rs @@ -18,6 +18,7 @@ use syn::{ }; use tauri_utils::acl::REMOVE_UNUSED_COMMANDS_ENV_VAR; +#[allow(clippy::large_enum_variant)] enum WrapperAttributeKind { Meta(Meta), Async, diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index 564ae2da4..b1222de9a 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -972,7 +972,6 @@ impl WindowBuilder for WindowBuilderWrapper { /// ## Platform-specific /// /// - **iOS / Android:** Unsupported. - #[must_use] fn prevent_overflow(mut self) -> Self { self .prevent_overflow @@ -986,7 +985,6 @@ impl WindowBuilder for WindowBuilderWrapper { /// ## Platform-specific /// /// - **iOS / Android:** Unsupported. - #[must_use] fn prevent_overflow_with_margin(mut self, margin: Size) -> Self { self.prevent_overflow.replace(margin); self diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index 75e1b997e..497a37822 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -1873,7 +1873,7 @@ } }, "removeUnusedCommands": { - "description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4", + "description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4", "default": false, "type": "boolean" } diff --git a/crates/tauri-utils/src/acl/resolved.rs b/crates/tauri-utils/src/acl/resolved.rs index fb55df6ce..941019822 100644 --- a/crates/tauri-utils/src/acl/resolved.rs +++ b/crates/tauri-utils/src/acl/resolved.rs @@ -10,6 +10,7 @@ use crate::platform::Target; use super::{ capability::{Capability, PermissionEntry}, + has_app_manifest, manifest::Manifest, Commands, Error, ExecutionContext, Identifier, Permission, PermissionSet, Scopes, Value, APP_ACL_KEY, @@ -67,6 +68,8 @@ pub struct ResolvedScope { /// Resolved access control list. #[derive(Debug, Default)] pub struct Resolved { + /// If we should check the ACL for the app commands + pub has_app_acl: bool, /// The commands that are allowed. Map each command with its context to a [`ResolvedCommand`]. pub allowed_commands: BTreeMap>, /// The commands that are denied. Map each command with its context to a [`ResolvedCommand`]. @@ -182,6 +185,7 @@ impl Resolved { .collect(); let resolved = Self { + has_app_acl: has_app_manifest(acl), allowed_commands, denied_commands, command_scope, @@ -513,6 +517,8 @@ mod build { impl ToTokens for Resolved { fn to_tokens(&self, tokens: &mut TokenStream) { + let has_app_acl = self.has_app_acl; + let allowed_commands = map_lit( quote! { ::std::collections::BTreeMap }, &self.allowed_commands, @@ -544,6 +550,7 @@ mod build { literal_struct!( tokens, ::tauri::utils::acl::resolved::Resolved, + has_app_acl, allowed_commands, denied_commands, command_scope, diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index a80b51aea..ef42230e7 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -2800,7 +2800,7 @@ pub struct BuildConfig { /// and they'll try to get all the allowed commands and remove the rest /// /// Note: - /// - This won't be accounting for dynamically added ACLs so make sure to check it when using this + /// - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this /// - This feature requires tauri-plugin 2.1 and tauri 2.4 #[serde(alias = "remove-unused-commands", default)] pub remove_unused_commands: bool, diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml index a40a5c8ac..be5f2fe6a 100644 --- a/crates/tauri/Cargo.toml +++ b/crates/tauri/Cargo.toml @@ -180,7 +180,7 @@ objc2-web-kit = { version = "0.3", features = [ ] } [features] -default = ["wry", "compression", "common-controls-v6"] +default = ["wry", "compression", "common-controls-v6", "dynamic-acl"] unstable = ["tauri-runtime-wry?/unstable"] common-controls-v6 = [ "tray-icon?/common-controls-v6", @@ -217,6 +217,7 @@ config-toml = ["tauri-macros/config-toml"] image-ico = ["image/ico"] image-png = ["image/png"] macos-proxy = ["tauri-runtime-wry?/macos-proxy"] +dynamic-acl = [] specta = ["dep:specta"] [[example]] diff --git a/crates/tauri/src/ipc/authority.rs b/crates/tauri/src/ipc/authority.rs index e304a6d5d..656857dc3 100644 --- a/crates/tauri/src/ipc/authority.rs +++ b/crates/tauri/src/ipc/authority.rs @@ -7,19 +7,15 @@ use std::fmt::{Debug, Display}; use std::sync::Arc; use serde::de::DeserializeOwned; -use serde::Serialize; -use tauri_utils::acl::has_app_manifest; -use tauri_utils::acl::{ - capability::{Capability, CapabilityFile, PermissionEntry}, - manifest::Manifest, - Value, APP_ACL_KEY, -}; +#[cfg(feature = "dynamic-acl")] +use tauri_utils::acl::capability::CapabilityFile; +#[cfg(any(feature = "dynamic-acl", debug_assertions))] +use tauri_utils::acl::manifest::Manifest; use tauri_utils::acl::{ resolved::{Resolved, ResolvedCommand, ResolvedScope, ScopeKey}, - ExecutionContext, Scopes, + ExecutionContext, Value, APP_ACL_KEY, }; -use tauri_utils::platform::Target; use url::Url; @@ -30,7 +26,9 @@ use super::{CommandArg, CommandItem}; /// The runtime authority used to authorize IPC execution based on the Access Control List. pub struct RuntimeAuthority { - acl: BTreeMap, + #[cfg(any(feature = "dynamic-acl", debug_assertions))] + acl: BTreeMap, + has_app_acl: bool, allowed_commands: BTreeMap>, denied_commands: BTreeMap>, pub(crate) scope_manager: ScopeManager, @@ -68,174 +66,55 @@ impl Origin { } } -/// A capability that can be added at runtime. -pub trait RuntimeCapability { - /// Creates the capability file. - fn build(self) -> CapabilityFile; +/// This is used internally by [`crate::generate_handler!`] +/// to only include the raw ACL when it's needed +/// +/// ## Stability +/// +/// The output of this macro is managed internally by Tauri, +/// and should not be accessed directly on normal applications. +/// It may have breaking changes in the future. +#[cfg(any(feature = "dynamic-acl", debug_assertions))] +#[doc(hidden)] +#[macro_export] +macro_rules! runtime_acl { + ($func:path, $acl:expr, $resolved_acl:expr) => { + $func($acl, $resolved_acl) + }; } -impl> RuntimeCapability for T { - fn build(self) -> CapabilityFile { - self.as_ref().parse().expect("invalid capability") - } -} - -/// A builder for a [`Capability`]. -pub struct CapabilityBuilder(Capability); - -impl CapabilityBuilder { - /// Creates a new capability builder with a unique identifier. - pub fn new(identifier: impl Into) -> Self { - Self(Capability { - identifier: identifier.into(), - description: "".into(), - remote: None, - local: true, - windows: Vec::new(), - webviews: Vec::new(), - permissions: Vec::new(), - platforms: None, - }) - } - - /// Allows this capability to be used by a remote URL. - pub fn remote(mut self, url: String) -> Self { - self - .0 - .remote - .get_or_insert_with(Default::default) - .urls - .push(url); - self - } - - /// Whether this capability is applied on local app URLs or not. Defaults to `true`. - pub fn local(mut self, local: bool) -> Self { - self.0.local = local; - self - } - - /// Link this capability to the given window label. - pub fn window(mut self, window: impl Into) -> Self { - self.0.windows.push(window.into()); - self - } - - /// Link this capability to the a list of window labels. - pub fn windows(mut self, windows: impl IntoIterator>) -> Self { - self.0.windows.extend(windows.into_iter().map(|w| w.into())); - self - } - - /// Link this capability to the given webview label. - pub fn webview(mut self, webview: impl Into) -> Self { - self.0.webviews.push(webview.into()); - self - } - - /// Link this capability to the a list of window labels. - pub fn webviews(mut self, webviews: impl IntoIterator>) -> Self { - self - .0 - .webviews - .extend(webviews.into_iter().map(|w| w.into())); - self - } - - /// Add a new permission to this capability. - pub fn permission(mut self, permission: impl Into) -> Self { - let permission = permission.into(); - self.0.permissions.push(PermissionEntry::PermissionRef( - permission - .clone() - .try_into() - .unwrap_or_else(|_| panic!("invalid permission identifier '{permission}'")), - )); - self - } - - /// Add a new scoped permission to this capability. - pub fn permission_scoped( - mut self, - permission: impl Into, - allowed: Vec, - denied: Vec, - ) -> Self { - let permission = permission.into(); - let identifier = permission - .clone() - .try_into() - .unwrap_or_else(|_| panic!("invalid permission identifier '{permission}'")); - - let allowed_scope = allowed - .into_iter() - .map(|a| { - serde_json::to_value(a) - .expect("failed to serialize scope") - .into() - }) - .collect(); - let denied_scope = denied - .into_iter() - .map(|a| { - serde_json::to_value(a) - .expect("failed to serialize scope") - .into() - }) - .collect(); - let scope = Scopes { - allow: Some(allowed_scope), - deny: Some(denied_scope), - }; - - self - .0 - .permissions - .push(PermissionEntry::ExtendedPermission { identifier, scope }); - self - } - - /// Adds a target platform for this capability. - /// - /// By default all platforms are applied. - pub fn platform(mut self, platform: Target) -> Self { - self - .0 - .platforms - .get_or_insert_with(Default::default) - .push(platform); - self - } - - /// Adds target platforms for this capability. - /// - /// By default all platforms are applied. - pub fn platforms(mut self, platforms: impl IntoIterator) -> Self { - self - .0 - .platforms - .get_or_insert_with(Default::default) - .extend(platforms); - self - } -} - -impl RuntimeCapability for CapabilityBuilder { - fn build(self) -> CapabilityFile { - CapabilityFile::Capability(self.0) - } +/// This is used internally by [`crate::generate_handler!`] +/// to only include the raw ACL when it's needed +/// +/// ## Stability +/// +/// The output of this macro is managed internally by Tauri, +/// and should not be accessed directly on normal applications. +/// It may have breaking changes in the future. +#[cfg(not(any(feature = "dynamic-acl", debug_assertions)))] +#[doc(hidden)] +#[macro_export] +macro_rules! runtime_acl { + ($func:path, $_acl:expr, $resolved_acl:expr) => { + $func($resolved_acl) + }; } impl RuntimeAuthority { #[doc(hidden)] - pub fn new(acl: BTreeMap, resolved_acl: Resolved) -> Self { + pub fn new( + #[cfg(any(feature = "dynamic-acl", debug_assertions))] acl: BTreeMap, + resolved_acl: Resolved, + ) -> Self { let command_cache = resolved_acl .command_scope .keys() .map(|key| (*key, StateManager::new())) .collect(); Self { + #[cfg(any(feature = "dynamic-acl", debug_assertions))] acl, + has_app_acl: resolved_acl.has_app_acl, allowed_commands: resolved_acl.allowed_commands, denied_commands: resolved_acl.denied_commands, scope_manager: ScopeManager { @@ -248,7 +127,7 @@ impl RuntimeAuthority { } pub(crate) fn has_app_manifest(&self) -> bool { - has_app_manifest(&self.acl) + self.has_app_acl } #[doc(hidden)] @@ -264,9 +143,15 @@ impl RuntimeAuthority { } /// Adds the given capability to the runtime authority. - pub fn add_capability(&mut self, capability: impl RuntimeCapability) -> crate::Result<()> { + #[cfg(feature = "dynamic-acl")] + pub fn add_capability(&mut self, capability: impl super::RuntimeCapability) -> crate::Result<()> { + self.add_capability_inner(capability.build()) + } + + #[cfg(feature = "dynamic-acl")] + fn add_capability_inner(&mut self, capability: CapabilityFile) -> crate::Result<()> { let mut capabilities = BTreeMap::new(); - match capability.build() { + match capability { CapabilityFile::Capability(c) => { capabilities.insert(c.identifier.clone(), c); } @@ -407,7 +292,7 @@ impl RuntimeAuthority { } fn has_permissions_allowing_command( - manifest: &crate::utils::acl::manifest::Manifest, + manifest: &Manifest, set: &crate::utils::acl::PermissionSet, command: &str, ) -> bool { diff --git a/crates/tauri/src/ipc/capability_builder.rs b/crates/tauri/src/ipc/capability_builder.rs new file mode 100644 index 000000000..62b5113bd --- /dev/null +++ b/crates/tauri/src/ipc/capability_builder.rs @@ -0,0 +1,170 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::Serialize; +use tauri_utils::{ + acl::{ + capability::{Capability, CapabilityFile, PermissionEntry}, + Scopes, + }, + platform::Target, +}; + +/// A capability that can be added at runtime. +pub trait RuntimeCapability { + /// Creates the capability file. + fn build(self) -> CapabilityFile; +} + +impl> RuntimeCapability for T { + fn build(self) -> CapabilityFile { + self.as_ref().parse().expect("invalid capability") + } +} + +/// A builder for a [`Capability`]. +pub struct CapabilityBuilder(Capability); + +impl CapabilityBuilder { + /// Creates a new capability builder with a unique identifier. + pub fn new(identifier: impl Into) -> Self { + Self(Capability { + identifier: identifier.into(), + description: "".into(), + remote: None, + local: true, + windows: Vec::new(), + webviews: Vec::new(), + permissions: Vec::new(), + platforms: None, + }) + } + + /// Allows this capability to be used by a remote URL. + pub fn remote(mut self, url: String) -> Self { + self + .0 + .remote + .get_or_insert_with(Default::default) + .urls + .push(url); + self + } + + /// Whether this capability is applied on local app URLs or not. Defaults to `true`. + pub fn local(mut self, local: bool) -> Self { + self.0.local = local; + self + } + + /// Link this capability to the given window label. + pub fn window(mut self, window: impl Into) -> Self { + self.0.windows.push(window.into()); + self + } + + /// Link this capability to the a list of window labels. + pub fn windows(mut self, windows: impl IntoIterator>) -> Self { + self.0.windows.extend(windows.into_iter().map(|w| w.into())); + self + } + + /// Link this capability to the given webview label. + pub fn webview(mut self, webview: impl Into) -> Self { + self.0.webviews.push(webview.into()); + self + } + + /// Link this capability to the a list of window labels. + pub fn webviews(mut self, webviews: impl IntoIterator>) -> Self { + self + .0 + .webviews + .extend(webviews.into_iter().map(|w| w.into())); + self + } + + /// Add a new permission to this capability. + pub fn permission(mut self, permission: impl Into) -> Self { + let permission = permission.into(); + self.0.permissions.push(PermissionEntry::PermissionRef( + permission + .clone() + .try_into() + .unwrap_or_else(|_| panic!("invalid permission identifier '{permission}'")), + )); + self + } + + /// Add a new scoped permission to this capability. + pub fn permission_scoped( + mut self, + permission: impl Into, + allowed: Vec, + denied: Vec, + ) -> Self { + let permission = permission.into(); + let identifier = permission + .clone() + .try_into() + .unwrap_or_else(|_| panic!("invalid permission identifier '{permission}'")); + + let allowed_scope = allowed + .into_iter() + .map(|a| { + serde_json::to_value(a) + .expect("failed to serialize scope") + .into() + }) + .collect(); + let denied_scope = denied + .into_iter() + .map(|a| { + serde_json::to_value(a) + .expect("failed to serialize scope") + .into() + }) + .collect(); + let scope = Scopes { + allow: Some(allowed_scope), + deny: Some(denied_scope), + }; + + self + .0 + .permissions + .push(PermissionEntry::ExtendedPermission { identifier, scope }); + self + } + + /// Adds a target platform for this capability. + /// + /// By default all platforms are applied. + pub fn platform(mut self, platform: Target) -> Self { + self + .0 + .platforms + .get_or_insert_with(Default::default) + .push(platform); + self + } + + /// Adds target platforms for this capability. + /// + /// By default all platforms are applied. + pub fn platforms(mut self, platforms: impl IntoIterator) -> Self { + self + .0 + .platforms + .get_or_insert_with(Default::default) + .extend(platforms); + self + } +} + +impl RuntimeCapability for CapabilityBuilder { + fn build(self) -> CapabilityFile { + CapabilityFile::Capability(self.0) + } +} diff --git a/crates/tauri/src/ipc/mod.rs b/crates/tauri/src/ipc/mod.rs index a554dad0a..ce8d579e2 100644 --- a/crates/tauri/src/ipc/mod.rs +++ b/crates/tauri/src/ipc/mod.rs @@ -22,15 +22,18 @@ use tauri_utils::acl::resolved::ResolvedCommand; use crate::{webview::Webview, Runtime, StateManager}; mod authority; +#[cfg(feature = "dynamic-acl")] +mod capability_builder; pub(crate) mod channel; mod command; pub(crate) mod format_callback; pub(crate) mod protocol; pub use authority::{ - CapabilityBuilder, CommandScope, GlobalScope, Origin, RuntimeAuthority, RuntimeCapability, - ScopeObject, ScopeObjectMatch, ScopeValue, + CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeObject, ScopeObjectMatch, ScopeValue, }; +#[cfg(feature = "dynamic-acl")] +pub use capability_builder::{CapabilityBuilder, RuntimeCapability}; pub use channel::{Channel, JavaScriptChannelId}; pub use command::{private, CommandArg, CommandItem}; diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index 7809dfbfc..2a8a0fea6 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -35,6 +35,7 @@ //! - **image-png**: Adds support to parse `.png` image, see [`Image`]. //! - **macos-proxy**: Adds support for [`WebviewBuilder::proxy_url`] on macOS. Requires macOS 14+. //! - **specta**: Add support for [`specta::specta`](https://docs.rs/specta/%5E2.0.0-rc.9/specta/attr.specta.html) with Tauri arguments such as [`State`](crate::State), [`Window`](crate::Window) and [`AppHandle`](crate::AppHandle) +//! - **dynamic-acl** *(enabled by default)*: Enables you to add ACLs at runtime, notably it enables the [`Manager::add_capability`] function. //! //! ## Cargo allowlist features //! @@ -64,7 +65,9 @@ macro_rules! ios_plugin_binding { #[doc(hidden)] pub use embed_plist; pub use error::{Error, Result}; -use ipc::{RuntimeAuthority, RuntimeCapability}; +use ipc::RuntimeAuthority; +#[cfg(feature = "dynamic-acl")] +use ipc::RuntimeCapability; pub use resources::{Resource, ResourceId, ResourceTable}; #[cfg(target_os = "ios")] #[doc(hidden)] @@ -820,6 +823,7 @@ pub trait Manager: sealed::ManagerBase { /// /// [`tauri.conf.json > app > security > capabilities`]: https://tauri.app/reference/config/#capabilities /// [tauri_build::Attributes::capabilities_path_pattern]: https://docs.rs/tauri-build/2/tauri_build/struct.Attributes.html#method.capabilities_path_pattern + #[cfg(feature = "dynamic-acl")] fn add_capability(&self, capability: impl RuntimeCapability) -> Result<()> { self .manager()