diff --git a/.changes/runtime-capability-dynamic.md b/.changes/runtime-capability-dynamic.md new file mode 100644 index 000000000..404c76579 --- /dev/null +++ b/.changes/runtime-capability-dynamic.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:enhance +--- + +`Manager::add_capability` now allows adding a dynamically defined capability instead of only relying on static strings. diff --git a/core/tauri/src/ipc/authority.rs b/core/tauri/src/ipc/authority.rs index c56613447..b6d9d21cf 100644 --- a/core/tauri/src/ipc/authority.rs +++ b/core/tauri/src/ipc/authority.rs @@ -7,15 +7,18 @@ use std::fmt::{Debug, Display}; use std::sync::Arc; use serde::de::DeserializeOwned; +use serde::Serialize; use state::TypeMap; -use tauri_utils::acl::capability::CapabilityFile; -use tauri_utils::acl::manifest::Manifest; +use tauri_utils::acl::{ + capability::{Capability, CapabilityFile, PermissionEntry}, + manifest::Manifest, + Value, APP_ACL_KEY, +}; use tauri_utils::acl::{ resolved::{CommandKey, Resolved, ResolvedCommand, ResolvedScope, ScopeKey}, - ExecutionContext, + ExecutionContext, Scopes, }; -use tauri_utils::acl::{Value, APP_ACL_KEY}; use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime}; use crate::{AppHandle, Manager}; @@ -62,6 +65,140 @@ impl Origin { } } +/// 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: Vec::new(), + }) + } + + /// 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 + } +} + +impl RuntimeCapability for CapabilityBuilder { + fn build(self) -> CapabilityFile { + CapabilityFile::Capability(self.0) + } +} + impl RuntimeAuthority { #[doc(hidden)] pub fn new(acl: BTreeMap, resolved_acl: Resolved) -> Self { @@ -102,9 +239,9 @@ impl RuntimeAuthority { } /// Adds the given capability to the runtime authority. - pub fn add_capability(&mut self, capability: CapabilityFile) -> crate::Result<()> { + pub fn add_capability(&mut self, capability: impl RuntimeCapability) -> crate::Result<()> { let mut capabilities = BTreeMap::new(); - match capability { + match capability.build() { CapabilityFile::Capability(c) => { capabilities.insert(c.identifier.clone(), c); } diff --git a/core/tauri/src/ipc/mod.rs b/core/tauri/src/ipc/mod.rs index 1cf56523d..0842e2864 100644 --- a/core/tauri/src/ipc/mod.rs +++ b/core/tauri/src/ipc/mod.rs @@ -24,7 +24,10 @@ mod command; pub(crate) mod format_callback; pub(crate) mod protocol; -pub use authority::{CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeObject, ScopeValue}; +pub use authority::{ + CapabilityBuilder, CommandScope, GlobalScope, Origin, RuntimeAuthority, RuntimeCapability, + ScopeObject, ScopeValue, +}; pub use channel::{Channel, JavaScriptChannelId}; pub use command::{private, CommandArg, CommandItem}; diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index b34515f5d..f4fbc3d90 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -69,7 +69,7 @@ pub use cocoa; #[doc(hidden)] pub use embed_plist; pub use error::{Error, Result}; -use ipc::RuntimeAuthority; +use ipc::{RuntimeAuthority, RuntimeCapability}; pub use resources::{Resource, ResourceId, ResourceTable}; #[cfg(target_os = "ios")] #[doc(hidden)] @@ -888,13 +888,13 @@ pub trait Manager: sealed::ManagerBase { /// Ok(()) /// }); /// ``` - fn add_capability(&self, capability: &'static str) -> Result<()> { + fn add_capability(&self, capability: impl RuntimeCapability) -> Result<()> { self .manager() .runtime_authority .lock() .unwrap() - .add_capability(capability.parse().expect("invalid capability")) + .add_capability(capability) } } diff --git a/core/tauri/src/webview/mod.rs b/core/tauri/src/webview/mod.rs index 6a4c95cf0..d508b1deb 100644 --- a/core/tauri/src/webview/mod.rs +++ b/core/tauri/src/webview/mod.rs @@ -22,10 +22,7 @@ use tauri_runtime::{ window::dpi::{PhysicalPosition, PhysicalSize, Position, Size}, WindowDispatch, }; -use tauri_utils::{ - acl::APP_ACL_KEY, - config::{WebviewUrl, WindowConfig}, -}; +use tauri_utils::config::{WebviewUrl, WindowConfig}; pub use url::Url; use crate::{ @@ -1192,7 +1189,7 @@ fn main() { { let (key, command_name) = plugin_command .clone() - .unwrap_or_else(|| (APP_ACL_KEY, request.cmd.clone())); + .unwrap_or_else(|| (tauri_utils::acl::APP_ACL_KEY, request.cmd.clone())); invoke.resolver.reject( manager .runtime_authority