From f998fa0ec28b4444397c254ee5c49684aa434bb3 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Mon, 29 Jan 2024 13:31:03 -0300 Subject: [PATCH] refactor(core): allow scope deserialization to run setup code (#8699) * refactor(core): allow scope deserialization to run setup code * Update core/tauri/src/command/authority.rs --------- Co-authored-by: Amr Bashir --- core/tauri/src/command/authority.rs | 72 ++++++++++++++++++++--------- core/tauri/src/command/mod.rs | 2 +- core/tauri/src/error.rs | 3 ++ core/tauri/src/plugin.rs | 8 ++-- 4 files changed, 57 insertions(+), 28 deletions(-) diff --git a/core/tauri/src/command/authority.rs b/core/tauri/src/command/authority.rs index af68d6fff..314655ee5 100644 --- a/core/tauri/src/command/authority.rs +++ b/core/tauri/src/command/authority.rs @@ -8,12 +8,14 @@ use std::{collections::BTreeMap, ops::Deref}; use serde::de::DeserializeOwned; use state::TypeMap; +use tauri_utils::acl::Value; use tauri_utils::acl::{ resolved::{CommandKey, Resolved, ResolvedCommand, ResolvedScope, ScopeKey}, ExecutionContext, }; use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime}; +use crate::{AppHandle, Manager}; use super::{CommandArg, CommandItem}; @@ -95,12 +97,12 @@ impl RuntimeAuthority { /// List of allowed and denied objects that match either the command-specific or plugin global scope criterias. #[derive(Debug)] -pub struct ScopeValue { +pub struct ScopeValue { allow: Vec, deny: Vec, } -impl ScopeValue { +impl ScopeValue { /// What this access scope allows. pub fn allows(&self) -> &Vec { &self.allow @@ -130,11 +132,9 @@ impl<'a, T: Debug> Deref for OwnedOrRef<'a, T> { /// Access scope for a command that can be retrieved directly in the command function. #[derive(Debug)] -pub struct CommandScope<'a, T: Debug + DeserializeOwned + Send + Sync + 'static>( - OwnedOrRef<'a, ScopeValue>, -); +pub struct CommandScope<'a, T: ScopeObject>(OwnedOrRef<'a, ScopeValue>); -impl<'a, T: Debug + DeserializeOwned + Send + Sync + 'static> CommandScope<'a, T> { +impl<'a, T: ScopeObject> CommandScope<'a, T> { /// What this access scope allows. pub fn allows(&self) -> &Vec { &self.0.allow @@ -146,9 +146,7 @@ impl<'a, T: Debug + DeserializeOwned + Send + Sync + 'static> CommandScope<'a, T } } -impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> CommandArg<'a, R> - for CommandScope<'a, T> -{ +impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<'a, T> { /// 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) { @@ -159,7 +157,7 @@ impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> Comman .manager() .runtime_authority .scope_manager - .get_command_scope_typed(&scope_id)?, + .get_command_scope_typed(command.message.webview.app_handle(), &scope_id)?, ))) } else { Ok(CommandScope(OwnedOrRef::Owned(ScopeValue { @@ -172,9 +170,9 @@ impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> Comman /// Global access scope that can be retrieved directly in the command function. #[derive(Debug)] -pub struct GlobalScope<'a, T: Debug + DeserializeOwned + Send + Sync + 'static>(&'a ScopeValue); +pub struct GlobalScope<'a, T: ScopeObject>(&'a ScopeValue); -impl<'a, T: Debug + DeserializeOwned + Send + Sync + 'static> GlobalScope<'a, T> { +impl<'a, T: ScopeObject> GlobalScope<'a, T> { /// What this access scope allows. pub fn allows(&self) -> &Vec { &self.0.allow @@ -186,9 +184,7 @@ impl<'a, T: Debug + DeserializeOwned + Send + Sync + 'static> GlobalScope<'a, T> } } -impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> CommandArg<'a, R> - for GlobalScope<'a, T> -{ +impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<'a, T> { /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`]. fn from_command(command: CommandItem<'a, R>) -> Result { command @@ -205,7 +201,7 @@ impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> Comman .manager() .runtime_authority .scope_manager - .get_global_scope_typed(plugin) + .get_global_scope_typed(command.message.webview.app_handle(), plugin) .map_err(InvokeError::from_error) }) .map(GlobalScope) @@ -220,9 +216,28 @@ pub struct ScopeManager { global_scope_cache: TypeMap![Send + Sync], } +/// Marks a type as a scope object. +/// +/// Usually you will just rely on [`serde::de::DeserializeOwned`] instead of implementing it manually, +/// though this is useful if you need to do some initialization logic on the type itself. +pub trait ScopeObject: Sized + Send + Sync + Debug + 'static { + /// The error type. + type Error: std::error::Error; + /// Deserialize the raw scope value. + fn deserialize(app: &AppHandle, raw: Value) -> Result; +} + +impl ScopeObject for T { + type Error = serde_json::Error; + fn deserialize(_app: &AppHandle, raw: Value) -> Result { + serde_json::from_value(raw.into()) + } +} + impl ScopeManager { - pub(crate) fn get_global_scope_typed( + pub(crate) fn get_global_scope_typed( &self, + app: &AppHandle, plugin: &str, ) -> crate::Result<&ScopeValue> { match self.global_scope_cache.try_get() { @@ -233,10 +248,16 @@ impl ScopeManager { if let Some(global_scope) = self.global_scope.get(plugin) { for allowed in &global_scope.allow { - allow.push(serde_json::from_value(allowed.clone().into())?); + allow.push( + T::deserialize(app, allowed.clone()) + .map_err(|e| crate::Error::CannotDeserializeScope(Box::new(e)))?, + ); } for denied in &global_scope.deny { - deny.push(serde_json::from_value(denied.clone().into())?); + deny.push( + T::deserialize(app, denied.clone()) + .map_err(|e| crate::Error::CannotDeserializeScope(Box::new(e)))?, + ); } } @@ -247,8 +268,9 @@ impl ScopeManager { } } - fn get_command_scope_typed( + fn get_command_scope_typed( &self, + app: &AppHandle, key: &ScopeKey, ) -> crate::Result<&ScopeValue> { let cache = self.command_cache.get(key).unwrap(); @@ -264,10 +286,16 @@ impl ScopeManager { let mut deny: Vec = Vec::new(); for allowed in &resolved_scope.allow { - allow.push(serde_json::from_value(allowed.clone().into())?); + allow.push( + T::deserialize(app, allowed.clone()) + .map_err(|e| crate::Error::CannotDeserializeScope(Box::new(e)))?, + ); } for denied in &resolved_scope.deny { - deny.push(serde_json::from_value(denied.clone().into())?); + deny.push( + T::deserialize(app, denied.clone()) + .map_err(|e| crate::Error::CannotDeserializeScope(Box::new(e)))?, + ); } let value = ScopeValue { allow, deny }; diff --git a/core/tauri/src/command/mod.rs b/core/tauri/src/command/mod.rs index bf7ff86a4..db3786e28 100644 --- a/core/tauri/src/command/mod.rs +++ b/core/tauri/src/command/mod.rs @@ -18,7 +18,7 @@ use serde::{ mod authority; -pub use authority::{CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeValue}; +pub use authority::{CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeObject, ScopeValue}; use tauri_utils::acl::resolved::ResolvedCommand; /// Represents a custom command. diff --git a/core/tauri/src/error.rs b/core/tauri/src/error.rs index e70724394..6463b15b1 100644 --- a/core/tauri/src/error.rs +++ b/core/tauri/src/error.rs @@ -145,6 +145,9 @@ pub enum Error { /// API requires the unstable feature flag. #[error("this feature requires the `unstable` flag on Cargo.toml")] UnstableFeatureNotSupported, + /// Failed to deserialize scope object. + #[error("error deserializing scope: {0}")] + CannotDeserializeScope(Box), } /// `Result` diff --git a/core/tauri/src/plugin.rs b/core/tauri/src/plugin.rs index d2c999fd1..488fa9db2 100644 --- a/core/tauri/src/plugin.rs +++ b/core/tauri/src/plugin.rs @@ -6,7 +6,7 @@ use crate::{ app::UriSchemeResponder, - command::ScopeValue, + command::{ScopeObject, ScopeValue}, ipc::{Invoke, InvokeHandler}, manager::webview::UriSchemeProtocol, utils::config::PluginConfig, @@ -140,15 +140,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<&ScopeValue> { self .handle .manager .runtime_authority .scope_manager - .get_global_scope_typed(self.name) + .get_global_scope_typed(&self.handle, self.name) } }