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 <amr.bashir2015@gmail.com>
This commit is contained in:
Lucas Fernandes Nogueira
2024-01-29 13:31:03 -03:00
committed by GitHub
parent 11de49788a
commit f998fa0ec2
4 changed files with 57 additions and 28 deletions

View File

@@ -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<T: Debug + DeserializeOwned + Send + Sync + 'static> {
pub struct ScopeValue<T: ScopeObject> {
allow: Vec<T>,
deny: Vec<T>,
}
impl<T: Debug + DeserializeOwned + Send + Sync + 'static> ScopeValue<T> {
impl<T: ScopeObject> ScopeValue<T> {
/// What this access scope allows.
pub fn allows(&self) -> &Vec<T> {
&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<T>>,
);
pub struct CommandScope<'a, T: ScopeObject>(OwnedOrRef<'a, ScopeValue<T>>);
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<T> {
&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<Self, InvokeError> {
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<T>);
pub struct GlobalScope<'a, T: ScopeObject>(&'a ScopeValue<T>);
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<T> {
&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<Self, InvokeError> {
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<R: Runtime>(app: &AppHandle<R>, raw: Value) -> Result<Self, Self::Error>;
}
impl<T: Send + Sync + Debug + DeserializeOwned + 'static> ScopeObject for T {
type Error = serde_json::Error;
fn deserialize<R: Runtime>(_app: &AppHandle<R>, raw: Value) -> Result<Self, Self::Error> {
serde_json::from_value(raw.into())
}
}
impl ScopeManager {
pub(crate) fn get_global_scope_typed<T: Send + Sync + DeserializeOwned + Debug + 'static>(
pub(crate) fn get_global_scope_typed<R: Runtime, T: ScopeObject>(
&self,
app: &AppHandle<R>,
plugin: &str,
) -> crate::Result<&ScopeValue<T>> {
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<T: Send + Sync + DeserializeOwned + Debug + 'static>(
fn get_command_scope_typed<R: Runtime, T: ScopeObject>(
&self,
app: &AppHandle<R>,
key: &ScopeKey,
) -> crate::Result<&ScopeValue<T>> {
let cache = self.command_cache.get(key).unwrap();
@@ -264,10 +286,16 @@ impl ScopeManager {
let mut deny: Vec<T> = 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 };

View File

@@ -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.

View File

@@ -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<dyn std::error::Error>),
}
/// `Result<T, ::tauri::Error>`

View File

@@ -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<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: Debug + DeserializeOwned + Send + Sync + 'static>(
&self,
) -> crate::Result<&ScopeValue<T>> {
pub fn scope<T: ScopeObject>(&self) -> crate::Result<&ScopeValue<T>> {
self
.handle
.manager
.runtime_authority
.scope_manager
.get_global_scope_typed(self.name)
.get_global_scope_typed(&self.handle, self.name)
}
}