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

* 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

* add change files

* go through code review

* fix tests

* remove tokens option
This commit is contained in:
Lucas Fernandes Nogueira 2024-02-19 11:13:36 -03:00 committed by GitHub
parent 770051ae63
commit 8d16a80d2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 453 additions and 241 deletions

View File

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

View File

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

View File

@ -0,0 +1,5 @@
---
"tauri-build": patch:enhance
---
Added `CodegenContext::capability` to include a capability file dynamically.

View File

@ -0,0 +1,5 @@
---
"tauri-utils": patch:enhance
---
Refactored the capability types and resolution algorithm.

1
Cargo.lock generated
View File

@ -4149,6 +4149,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"syn 2.0.49",
"tauri-utils",
"thiserror",
"time",

View File

@ -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,
};

View File

@ -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<Vec<PathBuf>>,
}
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<P: AsRef<Path>>(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

View File

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

View File

@ -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<Vec<PathBuf>>,
}
fn map_core_assets(
@ -126,6 +130,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
config,
config_parent,
root,
capabilities: additional_capabilities,
} = data;
let target = std::env::var("TARGET")
@ -390,7 +395,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
Default::default()
};
let capabilities = if config.app.security.capabilities.is_empty() {
let mut capabilities = if config.app.security.capabilities.is_empty() {
capabilities_from_files
} else {
let mut capabilities = BTreeMap::new();
@ -410,7 +415,36 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
capabilities
};
let resolved_acl = Resolved::resolve(acl, capabilities, target).expect("failed to resolve ACL");
let acl_tokens = map_lit(
quote! { ::std::collections::BTreeMap },
&acl,
str_lit,
identity,
);
if let Some(paths) = additional_capabilities {
for path in paths {
let capability = CapabilityFile::load(&path)
.unwrap_or_else(|e| panic!("failed to read capability {}: {e}", path.display()));
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(&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<TokenStream, EmbeddedAssetsE
#package_info,
#info_plist,
#pattern,
#resolved_acl
#runtime_authority
);
#with_tray_icon_code
context

View File

@ -4,11 +4,11 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use std::{env::VarError, path::PathBuf};
use std::path::PathBuf;
use syn::{
parse::{Parse, ParseBuffer},
punctuated::Punctuated,
LitStr, PathArguments, PathSegment, Token,
Expr, ExprLit, Lit, LitStr, Meta, PathArguments, PathSegment, Token,
};
use tauri_codegen::{context_codegen, get_config, ContextData};
use tauri_utils::{config::parse::does_supported_file_name_exist, platform::Target};
@ -16,6 +16,7 @@ use tauri_utils::{config::parse::does_supported_file_name_exist, platform::Targe
pub(crate) struct ContextItems {
config_file: PathBuf,
root: syn::Path,
capabilities: Option<Vec<PathBuf>>,
}
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::<LitStr>().ok().map(|raw| {
let _ = input.parse::<Token![,]>();
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::<Meta>() {
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::<Result<Vec<_>, 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()));

View File

@ -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<Capability>,
},
}
/// 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);
}

View File

@ -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<Capability>,
},
}
impl CapabilityFile {
/// Load the given capability file.
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, super::Error> {
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<Self, Self::Err> {
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;

View File

@ -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<String, Manifest>,
/// The commands that are allowed. Map each command with its context to a [`ResolvedCommand`].
pub allowed_commands: BTreeMap<CommandKey, ResolvedCommand>,
/// 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<String, ResolvedScope>,
}
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<String, Manifest>,
acl: &BTreeMap<String, Manifest>,
capabilities: BTreeMap<String, Capability>,
target: Target,
) -> Result<Self, Error> {
@ -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<String>) -> Result<Vec<glob::Pattern>, Error
Ok(patterns)
}
struct ResolvedPermission<'a> {
plugin_name: &'a str,
permission_name: &'a str,
commands: Commands,
scope: Scopes,
}
fn with_resolved_permissions<F: FnMut(ResolvedPermission<'_>)>(
capability: &Capability,
acl: &BTreeMap<String, Manifest>,
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<ScopeKey>,
pub resolved_scope_key: Option<ScopeKey>,
}
fn resolve_command(
commands: &mut BTreeMap<CommandKey, ResolvedCommandTemp>,
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,

View File

@ -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<String, crate::utils::acl::plugin::Manifest>,
allowed_commands: BTreeMap<CommandKey, ResolvedCommand>,
denied_commands: BTreeMap<CommandKey, ResolvedCommand>,
@ -64,15 +65,15 @@ impl Origin {
}
impl RuntimeAuthority {
pub(crate) fn new(resolved_acl: Resolved) -> Self {
#[doc(hidden)]
pub fn new(acl: BTreeMap<String, Manifest>, resolved_acl: Resolved) -> Self {
let command_cache = resolved_acl
.command_scope
.keys()
.map(|key| (*key, <TypeMap![Send + Sync]>::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)

View File

@ -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<A: Assets> {
pub(crate) package_info: PackageInfo,
pub(crate) _info_plist: (),
pub(crate) pattern: Pattern,
pub(crate) resolved_acl: Resolved,
pub(crate) runtime_authority: RuntimeAuthority,
}
impl<A: Assets> fmt::Debug for Context<A> {
@ -529,8 +529,8 @@ impl<A: Assets> Context<A> {
/// 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<A: Assets> Context<A> {
package_info: PackageInfo,
info_plist: (),
pattern: Pattern,
resolved_acl: Resolved,
runtime_authority: RuntimeAuthority,
) -> Self {
Self {
config,
@ -556,7 +556,7 @@ impl<A: Assets> Context<A> {
package_info,
_info_plist: info_plist,
pattern,
resolved_acl,
runtime_authority,
}
}

View File

@ -245,7 +245,7 @@ impl<R: Runtime> AppManager<R> {
}
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,

View File

@ -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<A: Assets>(assets: A) -> crate::Context<A> {
},
_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()),
}
}

View File

@ -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!(

View File

@ -3767,6 +3767,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"syn 2.0.48",
"tauri-utils",
"thiserror",
"time",

View File

@ -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()

View File

@ -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()

View File

@ -19,7 +19,8 @@
}
],
"security": {
"csp": null
"csp": null,
"capabilities": ["default-plugins"]
}
},
"bundle": {