diff --git a/.changes/codegen-capabilities-attribute.md b/.changes/codegen-capabilities-attribute.md new file mode 100644 index 000000000..c96d47141 --- /dev/null +++ b/.changes/codegen-capabilities-attribute.md @@ -0,0 +1,6 @@ +--- +"tauri-macros": patch:enhance +"tauri-codegen": patch:enhance +--- + +The `generate_context` proc macro now accepts a `capabilities` attribute where the value is an array of file paths that can be [conditionally compiled](https://doc.rust-lang.org/reference/conditional-compilation.html). These capabilities are added to the application along the capabilities defined in the Tauri configuration file. diff --git a/.changes/context-runtime-authority.md b/.changes/context-runtime-authority.md new file mode 100644 index 000000000..4d2a65e5c --- /dev/null +++ b/.changes/context-runtime-authority.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": patch:enhance +--- + +The `Context` struct now includes the runtime authority instead of the resolved ACL. This does not impact most applications. diff --git a/.changes/tauri-build-codegen-capabilities.md b/.changes/tauri-build-codegen-capabilities.md new file mode 100644 index 000000000..cc767345a --- /dev/null +++ b/.changes/tauri-build-codegen-capabilities.md @@ -0,0 +1,5 @@ +--- +"tauri-build": patch:enhance +--- + +Added `CodegenContext::capability` to include a capability file dynamically. diff --git a/.changes/tauri-utils-capability-refactor.md b/.changes/tauri-utils-capability-refactor.md new file mode 100644 index 000000000..c1fa7873f --- /dev/null +++ b/.changes/tauri-utils-capability-refactor.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": patch:enhance +--- + +Refactored the capability types and resolution algorithm. diff --git a/Cargo.lock b/Cargo.lock index de68ec0ec..5d9f12560 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4149,6 +4149,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "syn 2.0.49", "tauri-utils", "thiserror", "time", diff --git a/core/tauri-build/src/acl.rs b/core/tauri-build/src/acl.rs index b8226c058..c897c86f3 100644 --- a/core/tauri-build/src/acl.rs +++ b/core/tauri-build/src/acl.rs @@ -17,7 +17,10 @@ use schemars::{ schema_for, }; use tauri_utils::{ - acl::{build::CapabilityFile, capability::Capability, plugin::Manifest}, + acl::{ + capability::{Capability, CapabilityFile}, + plugin::Manifest, + }, platform::Target, }; diff --git a/core/tauri-build/src/codegen/context.rs b/core/tauri-build/src/codegen/context.rs index 65afc364a..dc07c63b8 100644 --- a/core/tauri-build/src/codegen/context.rs +++ b/core/tauri-build/src/codegen/context.rs @@ -7,7 +7,7 @@ use std::{ env::var, fs::{create_dir_all, File}, io::{BufWriter, Write}, - path::PathBuf, + path::{Path, PathBuf}, }; use tauri_codegen::{context_codegen, ContextData}; use tauri_utils::config::FrontendDist; @@ -20,6 +20,7 @@ pub struct CodegenContext { dev: bool, config_path: PathBuf, out_file: PathBuf, + capabilities: Option>, } impl Default for CodegenContext { @@ -28,6 +29,7 @@ impl Default for CodegenContext { dev: false, config_path: PathBuf::from("tauri.conf.json"), out_file: PathBuf::from("tauri-build-context.rs"), + capabilities: None, } } } @@ -74,6 +76,16 @@ impl CodegenContext { self } + /// Adds a capability file to the generated context. + #[must_use] + pub fn capability>(mut self, path: P) -> Self { + self + .capabilities + .get_or_insert_with(Default::default) + .push(path.as_ref().to_path_buf()); + self + } + /// Generate the code and write it to the output file - returning the path it was saved to. /// /// Unless you are doing something special with this builder, you don't need to do anything with @@ -125,6 +137,7 @@ impl CodegenContext { // it's very hard to have a build script for unit tests, so assume this is always called from // outside the tauri crate, making the ::tauri root valid. root: quote::quote!(::tauri), + capabilities: self.capabilities, })?; // get the full output file path diff --git a/core/tauri-codegen/Cargo.toml b/core/tauri-codegen/Cargo.toml index 3d90e06c6..bd99e92b9 100644 --- a/core/tauri-codegen/Cargo.toml +++ b/core/tauri-codegen/Cargo.toml @@ -17,7 +17,8 @@ sha2 = "0.10" base64 = "0.21" proc-macro2 = "1" quote = "1" -serde = { version = "1", features = ["derive"] } +syn = "2" +serde = { version = "1", features = [ "derive" ] } serde_json = "1" tauri-utils = { version = "2.0.0-beta.1", path = "../tauri-utils", features = [ "build" ] } thiserror = "1" diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index ce3c0dafb..d371580bf 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT use std::collections::BTreeMap; +use std::convert::identity; use std::path::{Path, PathBuf}; use std::{ffi::OsStr, str::FromStr}; @@ -11,7 +12,7 @@ use proc_macro2::TokenStream; use quote::quote; use sha2::{Digest, Sha256}; -use tauri_utils::acl::capability::Capability; +use tauri_utils::acl::capability::{Capability, CapabilityFile}; use tauri_utils::acl::plugin::Manifest; use tauri_utils::acl::resolved::Resolved; use tauri_utils::assets::AssetKey; @@ -20,6 +21,7 @@ use tauri_utils::html::{ inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, }; use tauri_utils::platform::Target; +use tauri_utils::tokens::{map_lit, str_lit}; use crate::embedded_assets::{AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsError}; @@ -32,6 +34,8 @@ pub struct ContextData { pub config: Config, pub config_parent: PathBuf, pub root: TokenStream, + /// Additional capabilities to include. + pub capabilities: Option>, } fn map_core_assets( @@ -126,6 +130,7 @@ pub fn context_codegen(data: ContextData) -> Result Result Result { + capabilities.insert(c.identifier.clone(), c); + } + CapabilityFile::List { + capabilities: capabilities_list, + } => { + capabilities.extend( + capabilities_list + .into_iter() + .map(|c| (c.identifier.clone(), c)), + ); + } + } + } + } + + let resolved = Resolved::resolve(&acl, capabilities, target).expect("failed to resolve ACL"); + let runtime_authority = quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved)); Ok(quote!({ #[allow(unused_mut, clippy::let_and_return)] @@ -422,7 +456,7 @@ pub fn context_codegen(data: ContextData) -> Result>, } impl Parse for ContextItems { @@ -26,51 +27,92 @@ impl Parse for ContextItems { .map(Target::from_triple) .unwrap_or_else(|_| Target::current()); - let config_file = if input.is_empty() { - std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join("tauri.conf.json")) - } else { - let raw: LitStr = input.parse()?; + let mut root = None; + let mut capabilities = None; + let config_file = input.parse::().ok().map(|raw| { + let _ = input.parse::(); let path = PathBuf::from(raw.value()); if path.is_relative() { - std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join(path)) + std::env::var("CARGO_MANIFEST_DIR") + .map(|m| PathBuf::from(m).join(path)) + .map_err(|e| e.to_string()) } else { Ok(path) } + .and_then(|path| { + if does_supported_file_name_exist(target, &path) { + Ok(path) + } else { + Err(format!( + "no file at path {} exists, expected tauri config file", + path.display() + )) + } + }) + }); + + while let Ok(meta) = input.parse::() { + match meta { + Meta::Path(p) => { + root.replace(p); + } + Meta::NameValue(v) => { + if *v.path.require_ident()? == "capabilities" { + if let Expr::Array(array) = v.value { + capabilities.replace( + array + .elems + .into_iter() + .map(|e| { + if let Expr::Lit(ExprLit { + attrs: _, + lit: Lit::Str(s), + }) = e + { + Ok(s.value().into()) + } else { + Err(syn::Error::new( + input.span(), + "unexpected expression for capability", + )) + } + }) + .collect::, syn::Error>>()?, + ); + } else { + return Err(syn::Error::new( + input.span(), + "unexpected value for capabilities", + )); + } + } + } + Meta::List(_) => { + return Err(syn::Error::new(input.span(), "unexpected list input")); + } + } } - .map_err(|error| match error { - VarError::NotPresent => "no CARGO_MANIFEST_DIR env var, this should be set by cargo".into(), - VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into(), - }) - .and_then(|path| { - if does_supported_file_name_exist(target, &path) { - Ok(path) - } else { - Err(format!( - "no file at path {} exists, expected tauri config file", - path.display() - )) - } - }) - .map_err(|e| input.error(e))?; - - let context_path = if input.is_empty() { - let mut segments = Punctuated::new(); - segments.push(PathSegment { - ident: Ident::new("tauri", Span::call_site()), - arguments: PathArguments::None, - }); - syn::Path { - leading_colon: Some(Token![::](Span::call_site())), - segments, - } - } else { - let _: Token![,] = input.parse()?; - input.call(syn::Path::parse_mod_style)? - }; Ok(Self { - config_file, - root: context_path, + config_file: config_file + .unwrap_or_else(|| { + std::env::var("CARGO_MANIFEST_DIR") + .map(|m| PathBuf::from(m).join("tauri.conf.json")) + .map_err(|e| e.to_string()) + }) + .map_err(|e| input.error(e))?, + root: root.unwrap_or_else(|| { + let mut segments = Punctuated::new(); + segments.push(PathSegment { + ident: Ident::new("tauri", Span::call_site()), + arguments: PathArguments::None, + }); + syn::Path { + leading_colon: Some(Token![::](Span::call_site())), + segments, + } + }), + capabilities, }) } } @@ -83,6 +125,7 @@ pub(crate) fn generate_context(context: ContextItems) -> TokenStream { config, config_parent, root: context.root.to_token_stream(), + capabilities: context.capabilities, }) .and_then(|data| context_codegen(data).map_err(|e| e.to_string())); diff --git a/core/tauri-utils/src/acl/build.rs b/core/tauri-utils/src/acl/build.rs index 37297c5d9..b922ce674 100644 --- a/core/tauri-utils/src/acl/build.rs +++ b/core/tauri-utils/src/acl/build.rs @@ -16,9 +16,11 @@ use schemars::{ schema::{InstanceType, Metadata, RootSchema, Schema, SchemaObject, SubschemaValidation}, schema_for, }; -use serde::Deserialize; -use super::{capability::Capability, plugin::PermissionFile}; +use super::{ + capability::{Capability, CapabilityFile}, + plugin::PermissionFile, +}; /// Known name of the folder containing autogenerated permissions. pub const AUTOGENERATED_FOLDER_NAME: &str = "autogenerated"; @@ -49,19 +51,6 @@ const CAPABILITIES_SCHEMA_FOLDER_NAME: &str = "schemas"; const CORE_PLUGIN_PERMISSIONS_TOKEN: &str = "__CORE_PLUGIN__"; -/// Capability formats accepted in a capability file. -#[derive(Deserialize, schemars::JsonSchema)] -#[serde(untagged)] -pub enum CapabilityFile { - /// A single capability. - Capability(Capability), - /// A list of capabilities. - List { - /// The list of capabilities. - capabilities: Vec, - }, -} - /// Write the permissions to a temporary directory and pass it to the immediate consuming crate. pub fn define_permissions( pattern: &str, @@ -143,15 +132,7 @@ pub fn parse_capabilities( // TODO: remove this before stable .filter(|p| p.parent().unwrap().file_name().unwrap() != CAPABILITIES_SCHEMA_FOLDER_NAME) { - let capability_file = std::fs::read_to_string(&path).map_err(Error::ReadFile)?; - let ext = path.extension().unwrap().to_string_lossy().to_string(); - let capability: CapabilityFile = match ext.as_str() { - "toml" => toml::from_str(&capability_file)?, - "json" => serde_json::from_str(&capability_file)?, - _ => return Err(Error::UnknownCapabilityFormat(ext)), - }; - - match capability { + match CapabilityFile::load(&path)? { CapabilityFile::Capability(capability) => { capabilities_map.insert(capability.identifier.clone(), capability); } diff --git a/core/tauri-utils/src/acl/capability.rs b/core/tauri-utils/src/acl/capability.rs index 15390c813..d0f192638 100644 --- a/core/tauri-utils/src/acl/capability.rs +++ b/core/tauri-utils/src/acl/capability.rs @@ -4,6 +4,8 @@ //! End-user abstraction for selecting permissions a window has access to. +use std::{path::Path, str::FromStr}; + use crate::{acl::Identifier, platform::Target}; use serde::{Deserialize, Serialize}; @@ -101,6 +103,47 @@ pub enum CapabilityContext { }, } +/// Capability formats accepted in a capability file. +#[derive(Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(untagged)] +pub enum CapabilityFile { + /// A single capability. + Capability(Capability), + /// A list of capabilities. + List { + /// The list of capabilities. + capabilities: Vec, + }, +} + +impl CapabilityFile { + /// Load the given capability file. + pub fn load>(path: P) -> Result { + let path = path.as_ref(); + let capability_file = std::fs::read_to_string(path).map_err(super::Error::ReadFile)?; + let ext = path.extension().unwrap().to_string_lossy().to_string(); + let file: Self = match ext.as_str() { + "toml" => toml::from_str(&capability_file)?, + "json" => serde_json::from_str(&capability_file)?, + _ => return Err(super::Error::UnknownCapabilityFormat(ext)), + }; + Ok(file) + } +} + +impl FromStr for CapabilityFile { + type Err = super::Error; + + fn from_str(s: &str) -> Result { + match s.chars().next() { + Some('[') => toml::from_str(s).map_err(Into::into), + Some('{') => serde_json::from_str(s).map_err(Into::into), + _ => Err(super::Error::UnknownCapabilityFormat(s.into())), + } + } +} + #[cfg(feature = "build")] mod build { use std::convert::identity; diff --git a/core/tauri-utils/src/acl/resolved.rs b/core/tauri-utils/src/acl/resolved.rs index d60448d25..0a4bd8c2e 100644 --- a/core/tauri-utils/src/acl/resolved.rs +++ b/core/tauri-utils/src/acl/resolved.rs @@ -77,11 +77,8 @@ pub struct CommandKey { } /// Resolved access control list. -#[derive(Default)] +#[derive(Debug, Default)] pub struct Resolved { - /// ACL plugin manifests. - #[cfg(debug_assertions)] - pub acl: BTreeMap, /// The commands that are allowed. Map each command with its context to a [`ResolvedCommand`]. pub allowed_commands: BTreeMap, /// The commands that are denied. Map each command with its context to a [`ResolvedCommand`]. @@ -92,21 +89,10 @@ pub struct Resolved { pub global_scope: BTreeMap, } -impl fmt::Debug for Resolved { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Resolved") - .field("allowed_commands", &self.allowed_commands) - .field("denied_commands", &self.denied_commands) - .field("command_scope", &self.command_scope) - .field("global_scope", &self.global_scope) - .finish() - } -} - impl Resolved { /// Resolves the ACL for the given plugin permissions and app capabilities. pub fn resolve( - acl: BTreeMap, + acl: &BTreeMap, capabilities: BTreeMap, target: Target, ) -> Result { @@ -123,67 +109,25 @@ impl Resolved { continue; } - for permission_entry in &capability.permissions { - let permission_id = permission_entry.identifier(); - let permission_name = permission_id.get_base(); - - if let Some(plugin_name) = permission_id.get_prefix() { - let permissions = get_permissions(plugin_name, permission_name, &acl)?; - - let mut resolved_scope = Scopes::default(); - let mut commands = Commands::default(); - - if let PermissionEntry::ExtendedPermission { - identifier: _, - scope, - } = permission_entry - { - if let Some(allow) = scope.allow.clone() { - resolved_scope - .allow - .get_or_insert_with(Default::default) - .extend(allow); - } - if let Some(deny) = scope.deny.clone() { - resolved_scope - .deny - .get_or_insert_with(Default::default) - .extend(deny); - } - } - - for permission in permissions { - if let Some(allow) = permission.scope.allow.clone() { - resolved_scope - .allow - .get_or_insert_with(Default::default) - .extend(allow); - } - if let Some(deny) = permission.scope.deny.clone() { - resolved_scope - .deny - .get_or_insert_with(Default::default) - .extend(deny); - } - - commands.allow.extend(permission.commands.allow.clone()); - commands.deny.extend(permission.commands.deny.clone()); - } - + with_resolved_permissions( + capability, + acl, + |ResolvedPermission { + plugin_name, + permission_name, + commands, + scope, + }| { if commands.allow.is_empty() && commands.deny.is_empty() { // global scope global_scope .entry(plugin_name.to_string()) .or_default() - .push(resolved_scope); + .push(scope); } else { - let has_scope = resolved_scope.allow.is_some() || resolved_scope.deny.is_some(); - if has_scope { + let scope_id = if scope.allow.is_some() || scope.deny.is_some() { current_scope_id += 1; - command_scopes.insert(current_scope_id, resolved_scope); - } - - let scope_id = if has_scope { + command_scopes.insert(current_scope_id, scope); Some(current_scope_id) } else { None @@ -211,8 +155,8 @@ impl Resolved { ); } } - } - } + }, + )?; } // resolve scopes @@ -264,8 +208,6 @@ impl Resolved { .collect(); let resolved = Self { - #[cfg(debug_assertions)] - acl, allowed_commands: allowed_commands .into_iter() .map(|(key, cmd)| { @@ -316,6 +258,77 @@ fn parse_glob_patterns(raw: HashSet) -> Result, Error Ok(patterns) } +struct ResolvedPermission<'a> { + plugin_name: &'a str, + permission_name: &'a str, + commands: Commands, + scope: Scopes, +} + +fn with_resolved_permissions)>( + capability: &Capability, + acl: &BTreeMap, + mut f: F, +) -> Result<(), Error> { + for permission_entry in &capability.permissions { + let permission_id = permission_entry.identifier(); + let permission_name = permission_id.get_base(); + + if let Some(plugin_name) = permission_id.get_prefix() { + let permissions = get_permissions(plugin_name, permission_name, acl)?; + + let mut resolved_scope = Scopes::default(); + let mut commands = Commands::default(); + + if let PermissionEntry::ExtendedPermission { + identifier: _, + scope, + } = permission_entry + { + if let Some(allow) = scope.allow.clone() { + resolved_scope + .allow + .get_or_insert_with(Default::default) + .extend(allow); + } + if let Some(deny) = scope.deny.clone() { + resolved_scope + .deny + .get_or_insert_with(Default::default) + .extend(deny); + } + } + + for permission in permissions { + if let Some(allow) = permission.scope.allow.clone() { + resolved_scope + .allow + .get_or_insert_with(Default::default) + .extend(allow); + } + if let Some(deny) = permission.scope.deny.clone() { + resolved_scope + .deny + .get_or_insert_with(Default::default) + .extend(deny); + } + + commands.allow.extend(permission.commands.allow.clone()); + commands.deny.extend(permission.commands.deny.clone()); + } + + f(ResolvedPermission { + plugin_name, + permission_name, + commands, + scope: resolved_scope, + }); + } + } + + Ok(()) +} + #[derive(Debug, Default)] struct ResolvedCommandTemp { #[cfg(debug_assertions)] @@ -325,7 +338,6 @@ struct ResolvedCommandTemp { pub scope: Vec, pub resolved_scope_key: Option, } - fn resolve_command( commands: &mut BTreeMap, command: String, @@ -511,14 +523,6 @@ mod build { impl ToTokens for Resolved { fn to_tokens(&self, tokens: &mut TokenStream) { - #[cfg(debug_assertions)] - let acl = map_lit( - quote! { ::std::collections::BTreeMap }, - &self.acl, - str_lit, - identity, - ); - let allowed_commands = map_lit( quote! { ::std::collections::BTreeMap }, &self.allowed_commands, @@ -547,19 +551,6 @@ mod build { identity, ); - #[cfg(debug_assertions)] - { - literal_struct!( - tokens, - ::tauri::utils::acl::resolved::Resolved, - acl, - allowed_commands, - denied_commands, - command_scope, - global_scope - ) - } - #[cfg(not(debug_assertions))] literal_struct!( tokens, ::tauri::utils::acl::resolved::Resolved, diff --git a/core/tauri/src/ipc/authority.rs b/core/tauri/src/ipc/authority.rs index 34126ecce..653f5173d 100644 --- a/core/tauri/src/ipc/authority.rs +++ b/core/tauri/src/ipc/authority.rs @@ -8,6 +8,8 @@ use std::{collections::BTreeMap, ops::Deref}; use serde::de::DeserializeOwned; use state::TypeMap; +use tauri_utils::acl::capability::CapabilityFile; +use tauri_utils::acl::plugin::Manifest; use tauri_utils::acl::Value; use tauri_utils::acl::{ resolved::{CommandKey, Resolved, ResolvedCommand, ResolvedScope, ScopeKey}, @@ -21,7 +23,6 @@ use super::{CommandArg, CommandItem}; /// The runtime authority used to authorize IPC execution based on the Access Control List. pub struct RuntimeAuthority { - #[cfg(debug_assertions)] acl: BTreeMap, allowed_commands: BTreeMap, denied_commands: BTreeMap, @@ -64,15 +65,15 @@ impl Origin { } impl RuntimeAuthority { - pub(crate) fn new(resolved_acl: Resolved) -> Self { + #[doc(hidden)] + pub fn new(acl: BTreeMap, resolved_acl: Resolved) -> Self { let command_cache = resolved_acl .command_scope .keys() .map(|key| (*key, ::new())) .collect(); Self { - #[cfg(debug_assertions)] - acl: resolved_acl.acl, + acl, allowed_commands: resolved_acl.allowed_commands, denied_commands: resolved_acl.denied_commands, scope_manager: ScopeManager { @@ -84,6 +85,93 @@ impl RuntimeAuthority { } } + #[doc(hidden)] + pub fn __allow_command(&mut self, command: String, context: ExecutionContext) { + self.allowed_commands.insert( + CommandKey { + name: command, + context, + }, + ResolvedCommand { + windows: vec!["*".parse().unwrap()], + ..Default::default() + }, + ); + } + + /// Adds the given capability to the runtime authority. + pub fn add_capability(&mut self, capability: CapabilityFile) -> crate::Result<()> { + let mut capabilities = BTreeMap::new(); + match capability { + CapabilityFile::Capability(c) => { + capabilities.insert(c.identifier.clone(), c); + } + CapabilityFile::List { + capabilities: capabilities_list, + } => { + capabilities.extend( + capabilities_list + .into_iter() + .map(|c| (c.identifier.clone(), c)), + ); + } + } + + let resolved = Resolved::resolve( + &self.acl, + capabilities, + tauri_utils::platform::Target::current(), + ) + .unwrap(); + + // fill global scope + for (plugin, global_scope) in resolved.global_scope { + let global_scope_entry = self.scope_manager.global_scope.entry(plugin).or_default(); + + global_scope_entry.allow.extend(global_scope.allow); + global_scope_entry.deny.extend(global_scope.deny); + + self.scope_manager.global_scope_cache = Default::default(); + } + + // denied commands + for (cmd_key, resolved_cmd) in resolved.denied_commands { + let entry = self.denied_commands.entry(cmd_key).or_default(); + + entry.windows.extend(resolved_cmd.windows); + #[cfg(debug_assertions)] + entry.referenced_by.extend(resolved_cmd.referenced_by); + } + + // allowed commands + for (cmd_key, resolved_cmd) in resolved.allowed_commands { + let entry = self.allowed_commands.entry(cmd_key).or_default(); + + entry.windows.extend(resolved_cmd.windows); + #[cfg(debug_assertions)] + entry.referenced_by.extend(resolved_cmd.referenced_by); + + // fill command scope + if let Some(scope_id) = resolved_cmd.scope { + let command_scope = resolved.command_scope.get(&scope_id).unwrap(); + + let command_scope_entry = self + .scope_manager + .command_scope + .entry(scope_id) + .or_default(); + command_scope_entry + .allow + .extend(command_scope.allow.clone()); + command_scope_entry.deny.extend(command_scope.deny.clone()); + + self.scope_manager.command_cache.remove(&scope_id); + } + } + + Ok(()) + } + #[cfg(debug_assertions)] pub(crate) fn resolve_access_message( &self, @@ -488,10 +576,13 @@ mod tests { .into_iter() .collect(); - let authority = RuntimeAuthority::new(Resolved { - allowed_commands, - ..Default::default() - }); + let authority = RuntimeAuthority::new( + Default::default(), + Resolved { + allowed_commands, + ..Default::default() + }, + ); assert_eq!( authority.resolve_access( @@ -522,10 +613,13 @@ mod tests { .into_iter() .collect(); - let authority = RuntimeAuthority::new(Resolved { - allowed_commands, - ..Default::default() - }); + let authority = RuntimeAuthority::new( + Default::default(), + Resolved { + allowed_commands, + ..Default::default() + }, + ); assert_eq!( authority.resolve_access( @@ -559,10 +653,13 @@ mod tests { .into_iter() .collect(); - let authority = RuntimeAuthority::new(Resolved { - allowed_commands, - ..Default::default() - }); + let authority = RuntimeAuthority::new( + Default::default(), + Resolved { + allowed_commands, + ..Default::default() + }, + ); assert_eq!( authority.resolve_access( @@ -598,10 +695,13 @@ mod tests { .into_iter() .collect(); - let authority = RuntimeAuthority::new(Resolved { - allowed_commands, - ..Default::default() - }); + let authority = RuntimeAuthority::new( + Default::default(), + Resolved { + allowed_commands, + ..Default::default() + }, + ); assert_eq!( authority.resolve_access( @@ -634,10 +734,13 @@ mod tests { .into_iter() .collect(); - let authority = RuntimeAuthority::new(Resolved { - allowed_commands, - ..Default::default() - }); + let authority = RuntimeAuthority::new( + Default::default(), + Resolved { + allowed_commands, + ..Default::default() + }, + ); assert!(authority .resolve_access( @@ -679,11 +782,14 @@ mod tests { .into_iter() .collect(); - let authority = RuntimeAuthority::new(Resolved { - allowed_commands, - denied_commands, - ..Default::default() - }); + let authority = RuntimeAuthority::new( + Default::default(), + Resolved { + allowed_commands, + denied_commands, + ..Default::default() + }, + ); assert!(authority .resolve_access(&command.name, window, webview, &Origin::Local) diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 785998de0..7c68d8ce1 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -69,6 +69,7 @@ pub use cocoa; #[doc(hidden)] pub use embed_plist; pub use error::{Error, Result}; +use ipc::RuntimeAuthority; pub use resources::{Resource, ResourceId, ResourceTable}; #[cfg(target_os = "ios")] #[doc(hidden)] @@ -193,7 +194,6 @@ use std::{ fmt::{self, Debug}, sync::MutexGuard, }; -use utils::acl::resolved::Resolved; #[cfg(feature = "wry")] #[cfg_attr(docsrs, doc(cfg(feature = "wry")))] @@ -432,7 +432,7 @@ pub struct Context { pub(crate) package_info: PackageInfo, pub(crate) _info_plist: (), pub(crate) pattern: Pattern, - pub(crate) resolved_acl: Resolved, + pub(crate) runtime_authority: RuntimeAuthority, } impl fmt::Debug for Context { @@ -529,8 +529,8 @@ impl Context { /// This API is unstable. #[doc(hidden)] #[inline(always)] - pub fn resolved_acl(&mut self) -> &mut Resolved { - &mut self.resolved_acl + pub fn runtime_authority_mut(&mut self) -> &mut RuntimeAuthority { + &mut self.runtime_authority } /// Create a new [`Context`] from the minimal required items. @@ -544,7 +544,7 @@ impl Context { package_info: PackageInfo, info_plist: (), pattern: Pattern, - resolved_acl: Resolved, + runtime_authority: RuntimeAuthority, ) -> Self { Self { config, @@ -556,7 +556,7 @@ impl Context { package_info, _info_plist: info_plist, pattern, - resolved_acl, + runtime_authority, } } diff --git a/core/tauri/src/manager/mod.rs b/core/tauri/src/manager/mod.rs index 2ecd1b8f9..42daa82ad 100644 --- a/core/tauri/src/manager/mod.rs +++ b/core/tauri/src/manager/mod.rs @@ -245,7 +245,7 @@ impl AppManager { } Self { - runtime_authority: RuntimeAuthority::new(context.resolved_acl), + runtime_authority: context.runtime_authority, window: window::WindowManager { windows: Mutex::default(), default_icon: context.default_window_icon, diff --git a/core/tauri/src/test/mod.rs b/core/tauri/src/test/mod.rs index 15a33d8c4..44960ad37 100644 --- a/core/tauri/src/test/mod.rs +++ b/core/tauri/src/test/mod.rs @@ -55,7 +55,7 @@ use serde::Serialize; use std::{borrow::Cow, collections::HashMap, fmt::Debug}; use crate::{ - ipc::{InvokeBody, InvokeError, InvokeResponse}, + ipc::{InvokeBody, InvokeError, InvokeResponse, RuntimeAuthority}, webview::InvokeRequest, App, Builder, Context, Pattern, Webview, }; @@ -126,14 +126,7 @@ pub fn mock_context(assets: A) -> crate::Context { }, _info_plist: (), pattern: Pattern::Brownfield(std::marker::PhantomData), - resolved_acl: Resolved { - #[cfg(debug_assertions)] - acl: Default::default(), - allowed_commands: Default::default(), - denied_commands: Default::default(), - command_scope: Default::default(), - global_scope: Default::default(), - }, + runtime_authority: RuntimeAuthority::new(Default::default(), Resolved::default()), } } diff --git a/core/tests/acl/src/lib.rs b/core/tests/acl/src/lib.rs index f2d2aaed4..72db2e977 100644 --- a/core/tests/acl/src/lib.rs +++ b/core/tests/acl/src/lib.rs @@ -57,7 +57,7 @@ mod tests { let capabilities = parse_capabilities(&format!("{}/cap*", fixture_entry.path().display())) .expect("failed to parse capabilities"); - let resolved = Resolved::resolve(manifests, capabilities, Target::current()) + let resolved = Resolved::resolve(&manifests, capabilities, Target::current()) .expect("failed to resolve ACL"); insta::assert_debug_snapshot!( diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 4a37e400c..15a21149d 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -3767,6 +3767,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "syn 2.0.48", "tauri-utils", "thiserror", "time", diff --git a/examples/multiwindow/main.rs b/examples/multiwindow/main.rs index 648565865..3f54e97a6 100644 --- a/examples/multiwindow/main.rs +++ b/examples/multiwindow/main.rs @@ -5,10 +5,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use tauri::{webview::PageLoadEvent, WebviewWindowBuilder}; -use tauri_utils::acl::{ - resolved::{CommandKey, ResolvedCommand}, - ExecutionContext, -}; +use tauri_utils::acl::ExecutionContext; fn main() { let mut context = tauri::generate_context!("../../examples/multiwindow/tauri.conf.json"); @@ -17,16 +14,9 @@ fn main() { "plugin:event|emit", "plugin:event|emit_to", ] { - context.resolved_acl().allowed_commands.insert( - CommandKey { - name: cmd.into(), - context: ExecutionContext::Local, - }, - ResolvedCommand { - windows: vec!["*".parse().unwrap()], - ..Default::default() - }, - ); + context + .runtime_authority_mut() + .__allow_command(cmd.to_string(), ExecutionContext::Local); } tauri::Builder::default() diff --git a/examples/parent-window/main.rs b/examples/parent-window/main.rs index d2bd750e3..45628b3bb 100644 --- a/examples/parent-window/main.rs +++ b/examples/parent-window/main.rs @@ -5,10 +5,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use tauri::{webview::PageLoadEvent, WebviewUrl, WebviewWindowBuilder}; -use tauri_utils::acl::{ - resolved::{CommandKey, ResolvedCommand}, - ExecutionContext, -}; +use tauri_utils::acl::ExecutionContext; fn main() { let mut context = tauri::generate_context!("../../examples/parent-window/tauri.conf.json"); @@ -16,16 +13,9 @@ fn main() { "plugin:event|listen", "plugin:webview|create_webview_window", ] { - context.resolved_acl().allowed_commands.insert( - CommandKey { - name: cmd.into(), - context: ExecutionContext::Local, - }, - ResolvedCommand { - windows: vec!["*".parse().unwrap()], - ..Default::default() - }, - ); + context + .runtime_authority_mut() + .__allow_command(cmd.to_string(), ExecutionContext::Local); } tauri::Builder::default() diff --git a/tooling/cli/templates/tauri.conf.json b/tooling/cli/templates/tauri.conf.json index 66155a363..b004cce57 100644 --- a/tooling/cli/templates/tauri.conf.json +++ b/tooling/cli/templates/tauri.conf.json @@ -19,7 +19,8 @@ } ], "security": { - "csp": null + "csp": null, + "capabilities": ["default-plugins"] } }, "bundle": {