Explorar o código

feat(core): add Manager::add_capability, closes #8799 (#8806)

* refactor(core): capabilities must be referenced on the Tauri config file

* add all capabilities by default

* feat(codegen): allow defining additional capabilities, closes #8798

* undo example

* lint

* move add_capability to runtime authority

* feat(core): add Manager::add_capability, closes #8799

* add change file
Lucas Fernandes Nogueira hai 1 ano
pai
achega
258494bd24

+ 5 - 0
.changes/acl-scope-refactor.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch:breaking
+---
+
+Removed the lifetime parameter from `ipc::GlobalScope` and `ipc::CommandScope`.

+ 5 - 0
.changes/runtime-add-capability.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch:enhance
+---
+
+Added `Manager::add_capability` to add a capability file at runtime.

+ 1 - 0
core/tauri-utils/src/acl/resolved.rs

@@ -338,6 +338,7 @@ struct ResolvedCommandTemp {
   pub scope: Vec<ScopeKey>,
   pub resolved_scope_key: Option<ScopeKey>,
 }
+
 fn resolve_command(
   commands: &mut BTreeMap<CommandKey, ResolvedCommandTemp>,
   command: String,

+ 45 - 43
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<T: ScopeObject> {
-  allow: Vec<T>,
-  deny: Vec<T>,
+  allow: Arc<Vec<T>>,
+  deny: Arc<Vec<T>>,
 }
 
 impl<T: ScopeObject> ScopeValue<T> {
+  fn clone(&self) -> Self {
+    Self {
+      allow: self.allow.clone(),
+      deny: self.deny.clone(),
+    }
+  }
+
   /// What this access scope allows.
   pub fn allows(&self) -> &Vec<T> {
     &self.allow
@@ -351,27 +359,11 @@ impl<T: ScopeObject> ScopeValue<T> {
   }
 }
 
-#[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<T>>);
+pub struct CommandScope<T: ScopeObject>(ScopeValue<T>);
 
-impl<'a, T: ScopeObject> CommandScope<'a, T> {
+impl<T: ScopeObject> CommandScope<T> {
   /// What this access scope allows.
   pub fn allows(&self) -> &Vec<T> {
     &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<T> {
   /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`CommandScope`].
   fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
     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<T>);
+pub struct GlobalScope<T: ScopeObject>(ScopeValue<T>);
 
-impl<'a, T: ScopeObject> GlobalScope<'a, T> {
+impl<T: ScopeObject> GlobalScope<T> {
   /// What this access scope allows.
   pub fn allows(&self) -> &Vec<T> {
     &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<T> {
   /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`].
   fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
     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<R>,
     plugin: &str,
-  ) -> crate::Result<&ScopeValue<T>> {
-    match self.global_scope_cache.try_get() {
-      Some(cached) => Ok(cached),
+  ) -> crate::Result<ScopeValue<T>> {
+    match self.global_scope_cache.try_get::<ScopeValue<T>>() {
+      Some(cached) => Ok(cached.clone()),
       None => {
         let mut allow: Vec<T> = Vec::new();
         let mut deny: Vec<T> = 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<R>,
     key: &ScopeKey,
-  ) -> crate::Result<&ScopeValue<T>> {
+  ) -> crate::Result<ScopeValue<T>> {
     let cache = self.command_cache.get(key).unwrap();
-    match cache.try_get() {
-      Some(cached) => Ok(cached),
+    match cache.try_get::<ScopeValue<T>>() {
+      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)
       }
     }
   }

+ 22 - 0
core/tauri/src/lib.rs

@@ -966,6 +966,28 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
   fn path(&self) -> &crate::path::PathResolver<R> {
     self.state::<crate::path::PathResolver<R>>().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.

+ 2 - 2
core/tauri/src/manager/mod.rs

@@ -175,7 +175,7 @@ pub struct Asset {
 
 #[default_runtime(crate::Wry, wry)]
 pub struct AppManager<R: Runtime> {
-  pub runtime_authority: RuntimeAuthority,
+  pub runtime_authority: Mutex<RuntimeAuthority>,
   pub window: window::WindowManager<R>,
   pub webview: webview::WebviewManager<R>,
   #[cfg(all(desktop, feature = "tray-icon"))]
@@ -245,7 +245,7 @@ impl<R: Runtime> AppManager<R> {
     }
 
     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,

+ 3 - 1
core/tauri/src/plugin.rs

@@ -139,11 +139,13 @@ impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
   }
 
   /// Gets the global scope defined on the permissions that are part of the app ACL.
-  pub fn scope<T: ScopeObject>(&self) -> crate::Result<&ScopeValue<T>> {
+  pub fn scope<T: ScopeObject>(&self) -> crate::Result<ScopeValue<T>> {
     self
       .handle
       .manager
       .runtime_authority
+      .lock()
+      .unwrap()
       .scope_manager
       .get_global_scope_typed(&self.handle, self.name)
   }

+ 15 - 9
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