diff --git a/.changes/acl-scope-refactor.md b/.changes/acl-scope-refactor.md new file mode 100644 index 000000000000..7781a67fcf75 --- /dev/null +++ b/.changes/acl-scope-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:breaking +--- + +Removed the lifetime parameter from `ipc::GlobalScope` and `ipc::CommandScope`. diff --git a/.changes/runtime-add-capability.md b/.changes/runtime-add-capability.md new file mode 100644 index 000000000000..851d118013cb --- /dev/null +++ b/.changes/runtime-add-capability.md @@ -0,0 +1,5 @@ +--- +"tauri": patch:enhance +--- + +Added `Manager::add_capability` to add a capability file at runtime. diff --git a/core/tauri-utils/src/acl/resolved.rs b/core/tauri-utils/src/acl/resolved.rs index 05b8f8185f03..1cd6f107a4cd 100644 --- a/core/tauri-utils/src/acl/resolved.rs +++ b/core/tauri-utils/src/acl/resolved.rs @@ -338,6 +338,7 @@ struct ResolvedCommandTemp { pub scope: Vec, pub resolved_scope_key: Option, } + fn resolve_command( commands: &mut BTreeMap, command: String, diff --git a/core/tauri/src/ipc/authority.rs b/core/tauri/src/ipc/authority.rs index dc36e7fcb50c..ba7e51f2327c 100644 --- a/core/tauri/src/ipc/authority.rs +++ b/core/tauri/src/ipc/authority.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use std::collections::BTreeMap; use std::fmt::{Debug, Display}; -use std::{collections::BTreeMap, ops::Deref}; +use std::sync::Arc; use serde::de::DeserializeOwned; use state::TypeMap; @@ -335,11 +336,18 @@ impl RuntimeAuthority { /// List of allowed and denied objects that match either the command-specific or plugin global scope criterias. #[derive(Debug)] pub struct ScopeValue { - allow: Vec, - deny: Vec, + allow: Arc>, + deny: Arc>, } impl ScopeValue { + fn clone(&self) -> Self { + Self { + allow: self.allow.clone(), + deny: self.deny.clone(), + } + } + /// What this access scope allows. pub fn allows(&self) -> &Vec { &self.allow @@ -351,27 +359,11 @@ impl ScopeValue { } } -#[derive(Debug)] -enum OwnedOrRef<'a, T: Debug> { - Owned(T), - Ref(&'a T), -} - -impl<'a, T: Debug> Deref for OwnedOrRef<'a, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - match self { - Self::Owned(t) => t, - Self::Ref(r) => r, - } - } -} - /// Access scope for a command that can be retrieved directly in the command function. #[derive(Debug)] -pub struct CommandScope<'a, T: ScopeObject>(OwnedOrRef<'a, ScopeValue>); +pub struct CommandScope(ScopeValue); -impl<'a, T: ScopeObject> CommandScope<'a, T> { +impl CommandScope { /// What this access scope allows. pub fn allows(&self) -> &Vec { &self.0.allow @@ -383,33 +375,35 @@ impl<'a, T: ScopeObject> CommandScope<'a, T> { } } -impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<'a, T> { +impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope { /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`CommandScope`]. fn from_command(command: CommandItem<'a, R>) -> Result { if let Some(scope_id) = command.acl.as_ref().and_then(|resolved| resolved.scope) { - Ok(CommandScope(OwnedOrRef::Ref( + Ok(CommandScope( command .message .webview .manager() .runtime_authority + .lock() + .unwrap() .scope_manager .get_command_scope_typed(command.message.webview.app_handle(), &scope_id)?, - ))) + )) } else { - Ok(CommandScope(OwnedOrRef::Owned(ScopeValue { - allow: Vec::new(), - deny: Vec::new(), - }))) + Ok(CommandScope(ScopeValue { + allow: Default::default(), + deny: Default::default(), + })) } } } /// Global access scope that can be retrieved directly in the command function. #[derive(Debug)] -pub struct GlobalScope<'a, T: ScopeObject>(&'a ScopeValue); +pub struct GlobalScope(ScopeValue); -impl<'a, T: ScopeObject> GlobalScope<'a, T> { +impl GlobalScope { /// What this access scope allows. pub fn allows(&self) -> &Vec { &self.0.allow @@ -421,7 +415,7 @@ impl<'a, T: ScopeObject> GlobalScope<'a, T> { } } -impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<'a, T> { +impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope { /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`]. fn from_command(command: CommandItem<'a, R>) -> Result { command @@ -437,6 +431,8 @@ impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<'a, T> { .webview .manager() .runtime_authority + .lock() + .unwrap() .scope_manager .get_global_scope_typed(command.message.webview.app_handle(), plugin) .map_err(InvokeError::from_error) @@ -476,9 +472,9 @@ impl ScopeManager { &self, app: &AppHandle, plugin: &str, - ) -> crate::Result<&ScopeValue> { - match self.global_scope_cache.try_get() { - Some(cached) => Ok(cached), + ) -> crate::Result> { + match self.global_scope_cache.try_get::>() { + Some(cached) => Ok(cached.clone()), None => { let mut allow: Vec = Vec::new(); let mut deny: Vec = Vec::new(); @@ -498,9 +494,12 @@ impl ScopeManager { } } - let scope = ScopeValue { allow, deny }; - let _ = self.global_scope_cache.set(scope); - Ok(self.global_scope_cache.get()) + let scope = ScopeValue { + allow: Arc::new(allow), + deny: Arc::new(deny), + }; + self.global_scope_cache.set(scope.clone()); + Ok(scope) } } } @@ -509,10 +508,10 @@ impl ScopeManager { &self, app: &AppHandle, key: &ScopeKey, - ) -> crate::Result<&ScopeValue> { + ) -> crate::Result> { let cache = self.command_cache.get(key).unwrap(); - match cache.try_get() { - Some(cached) => Ok(cached), + match cache.try_get::>() { + Some(cached) => Ok(cached.clone()), None => { let resolved_scope = self .command_scope @@ -535,10 +534,13 @@ impl ScopeManager { ); } - let value = ScopeValue { allow, deny }; + let value = ScopeValue { + allow: Arc::new(allow), + deny: Arc::new(deny), + }; - let _ = cache.set(value); - Ok(cache.get()) + let _ = cache.set(value.clone()); + Ok(value) } } } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 7c68d8ce1b23..15ed37742499 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -966,6 +966,28 @@ pub trait Manager: sealed::ManagerBase { fn path(&self) -> &crate::path::PathResolver { self.state::>().inner() } + + /// Adds a capability to the app. + /// + /// # Examples + /// ``` + /// use tauri::Manager; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// #[cfg(feature = "beta")] + /// app.add_capability(include_str!("../capabilities/beta.json")); + /// Ok(()) + /// }); + /// ``` + fn add_capability(&self, capability: &'static str) -> Result<()> { + self + .manager() + .runtime_authority + .lock() + .unwrap() + .add_capability(capability.parse().expect("invalid capability")) + } } /// Prevent implementation details from leaking out of the [`Manager`] trait. diff --git a/core/tauri/src/manager/mod.rs b/core/tauri/src/manager/mod.rs index 42daa82ad7e2..23009ae46868 100644 --- a/core/tauri/src/manager/mod.rs +++ b/core/tauri/src/manager/mod.rs @@ -175,7 +175,7 @@ pub struct Asset { #[default_runtime(crate::Wry, wry)] pub struct AppManager { - pub runtime_authority: RuntimeAuthority, + pub runtime_authority: Mutex, pub window: window::WindowManager, pub webview: webview::WebviewManager, #[cfg(all(desktop, feature = "tray-icon"))] @@ -245,7 +245,7 @@ impl AppManager { } Self { - runtime_authority: context.runtime_authority, + runtime_authority: Mutex::new(context.runtime_authority), window: window::WindowManager { windows: Mutex::default(), default_icon: context.default_window_icon, diff --git a/core/tauri/src/plugin.rs b/core/tauri/src/plugin.rs index 149d7f6064a7..d8c4826796f8 100644 --- a/core/tauri/src/plugin.rs +++ b/core/tauri/src/plugin.rs @@ -139,11 +139,13 @@ impl PluginApi { } /// Gets the global scope defined on the permissions that are part of the app ACL. - pub fn scope(&self) -> crate::Result<&ScopeValue> { + pub fn scope(&self) -> crate::Result> { self .handle .manager .runtime_authority + .lock() + .unwrap() .scope_manager .get_global_scope_typed(&self.handle, self.name) } diff --git a/core/tauri/src/webview/mod.rs b/core/tauri/src/webview/mod.rs index 412fd983089b..545e66fc7427 100644 --- a/core/tauri/src/webview/mod.rs +++ b/core/tauri/src/webview/mod.rs @@ -1118,6 +1118,8 @@ fn main() { }; let resolved_acl = manager .runtime_authority + .lock() + .unwrap() .resolve_access( &request.cmd, message.webview.label(), @@ -1142,15 +1144,19 @@ fn main() { if request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND && invoke.acl.is_none() { #[cfg(debug_assertions)] { - invoke - .resolver - .reject(manager.runtime_authority.resolve_access_message( - plugin, - &command_name, - invoke.message.webview.window().label(), - invoke.message.webview.label(), - &acl_origin, - )); + invoke.resolver.reject( + manager + .runtime_authority + .lock() + .unwrap() + .resolve_access_message( + plugin, + &command_name, + invoke.message.webview.window().label(), + invoke.message.webview.label(), + &acl_origin, + ), + ); } #[cfg(not(debug_assertions))] invoke