feat(ipc): improve error message when plugin command is not allowed (#8681)

* feat(ipc): improve error message when plugin command is not allowed

* reuse literal_struct macro
This commit is contained in:
Lucas Fernandes Nogueira 2024-01-30 11:54:10 -03:00 committed by GitHub
parent 38b8e67237
commit 9af90ca7f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 523 additions and 109 deletions

View File

@ -200,6 +200,10 @@ pub enum ExecutionContext {
#[cfg(feature = "build")]
mod build_ {
use std::convert::identity;
use crate::{literal_struct, tokens::*};
use super::*;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
@ -219,4 +223,57 @@ mod build_ {
});
}
}
impl ToTokens for Commands {
fn to_tokens(&self, tokens: &mut TokenStream) {
let allow = vec_lit(&self.allow, str_lit);
let deny = vec_lit(&self.deny, str_lit);
literal_struct!(tokens, ::tauri::utils::acl::Commands, allow, deny)
}
}
impl ToTokens for Scopes {
fn to_tokens(&self, tokens: &mut TokenStream) {
let allow = opt_vec_lit(self.allow.as_ref(), identity);
let deny = opt_vec_lit(self.deny.as_ref(), identity);
literal_struct!(tokens, ::tauri::utils::acl::Scopes, allow, deny)
}
}
impl ToTokens for Permission {
fn to_tokens(&self, tokens: &mut TokenStream) {
let version = opt_lit_owned(self.version.as_ref().map(|v| {
let v = v.get();
quote!(::core::num::NonZeroU64::new(#v).unwrap())
}));
let identifier = str_lit(&self.identifier);
let description = opt_str_lit(self.description.as_ref());
let commands = &self.commands;
let scope = &self.scope;
literal_struct!(
tokens,
::tauri::utils::acl::Permission,
version,
identifier,
description,
commands,
scope
)
}
}
impl ToTokens for PermissionSet {
fn to_tokens(&self, tokens: &mut TokenStream) {
let identifier = str_lit(&self.identifier);
let description = str_lit(&self.description);
let permissions = vec_lit(&self.permissions, str_lit);
literal_struct!(
tokens,
::tauri::utils::acl::PermissionSet,
identifier,
description,
permissions
)
}
}
}

View File

@ -36,9 +36,6 @@ pub struct PermissionFile {
#[serde(default)]
pub set: Vec<PermissionSet>,
/// Test something!!
pub test: Option<PermissionSet>,
/// A list of inlined permissions
#[serde(default)]
pub permission: Vec<Permission>,
@ -110,3 +107,63 @@ impl Manifest {
manifest
}
}
#[cfg(feature = "build")]
mod build {
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use std::convert::identity;
use super::*;
use crate::{literal_struct, tokens::*};
impl ToTokens for DefaultPermission {
fn to_tokens(&self, tokens: &mut TokenStream) {
let version = opt_lit_owned(self.version.as_ref().map(|v| {
let v = v.get();
quote!(::core::num::NonZeroU64::new(#v).unwrap())
}));
let description = opt_str_lit(self.description.as_ref());
let permissions = vec_lit(&self.permissions, str_lit);
literal_struct!(
tokens,
::tauri::utils::acl::plugin::DefaultPermission,
version,
description,
permissions
)
}
}
impl ToTokens for Manifest {
fn to_tokens(&self, tokens: &mut TokenStream) {
let default_permission = opt_lit(self.default_permission.as_ref());
let permissions = map_lit(
quote! { ::std::collections::BTreeMap },
&self.permissions,
str_lit,
identity,
);
let permission_sets = map_lit(
quote! { ::std::collections::BTreeMap },
&self.permission_sets,
str_lit,
identity,
);
let global_scope_schema =
opt_lit_owned(self.global_scope_schema.as_ref().map(json_value_lit));
literal_struct!(
tokens,
::tauri::utils::acl::plugin::Manifest,
default_permission,
permissions,
permission_sets,
global_scope_schema
)
}
}
}

View File

@ -6,6 +6,7 @@
use std::{
collections::{hash_map::DefaultHasher, BTreeMap, HashSet},
fmt,
hash::{Hash, Hasher},
};
@ -22,15 +23,37 @@ use super::{
/// A key for a scope, used to link a [`ResolvedCommand#structfield.scope`] to the store [`Resolved#structfield.scopes`].
pub type ScopeKey = usize;
/// Metadata for what referenced a [`ResolvedCommand`].
#[cfg(debug_assertions)]
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ResolvedCommandReference {
/// Identifier of the capability.
pub capability: String,
/// Identifier of the permission.
pub permission: String,
}
/// A resolved command permission.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Default, Clone, PartialEq, Eq)]
pub struct ResolvedCommand {
/// The list of window label patterns that is allowed to run this command.
/// The list of capability/permission that referenced this command.
#[cfg(debug_assertions)]
pub referenced_by: Vec<ResolvedCommandReference>,
/// The list of window label patterns that was resolved for this command.
pub windows: Vec<glob::Pattern>,
/// The reference of the scope that is associated with this command. See [`Resolved#structfield.scopes`].
pub scope: Option<ScopeKey>,
}
impl fmt::Debug for ResolvedCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResolvedCommand")
.field("windows", &self.windows)
.field("scope", &self.scope)
.finish()
}
}
/// A resolved scope. Merges all scopes defined for a single command.
#[derive(Debug, Default)]
pub struct ResolvedScope {
@ -51,8 +74,11 @@ pub struct CommandKey {
}
/// Resolved access control list.
#[derive(Debug)]
#[derive(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`].
@ -63,6 +89,17 @@ 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(
@ -139,6 +176,8 @@ impl Resolved {
format!("plugin:{plugin_name}|{allowed_command}"),
capability,
scope_id,
#[cfg(debug_assertions)]
permission,
);
}
@ -148,6 +187,8 @@ impl Resolved {
format!("plugin:{plugin_name}|{denied_command}"),
capability,
scope_id,
#[cfg(debug_assertions)]
permission,
);
}
}
@ -205,12 +246,16 @@ impl Resolved {
.collect();
let resolved = Self {
#[cfg(debug_assertions)]
acl,
allowed_commands: allowed_commands
.into_iter()
.map(|(key, cmd)| {
Ok((
key,
ResolvedCommand {
#[cfg(debug_assertions)]
referenced_by: cmd.referenced_by,
windows: parse_window_patterns(cmd.windows)?,
scope: cmd.resolved_scope_key,
},
@ -223,6 +268,8 @@ impl Resolved {
Ok((
key,
ResolvedCommand {
#[cfg(debug_assertions)]
referenced_by: cmd.referenced_by,
windows: parse_window_patterns(cmd.windows)?,
scope: cmd.resolved_scope_key,
},
@ -247,6 +294,8 @@ fn parse_window_patterns(windows: HashSet<String>) -> Result<Vec<glob::Pattern>,
#[derive(Debug, Default)]
struct ResolvedCommandTemp {
#[cfg(debug_assertions)]
pub referenced_by: Vec<ResolvedCommandReference>,
pub windows: HashSet<String>,
pub scope: Vec<usize>,
pub resolved_scope_key: Option<usize>,
@ -257,6 +306,7 @@ fn resolve_command(
command: String,
capability: &Capability,
scope_id: Option<usize>,
#[cfg(debug_assertions)] permission: &Permission,
) {
let contexts = match &capability.context {
CapabilityContext::Local => {
@ -279,6 +329,12 @@ fn resolve_command(
})
.or_default();
#[cfg(debug_assertions)]
resolved.referenced_by.push(ResolvedCommandReference {
capability: capability.identifier.clone(),
permission: permission.identifier.clone(),
});
resolved.windows.extend(capability.windows.clone());
if let Some(id) = scope_id {
resolved.scope.push(id);
@ -347,37 +403,63 @@ mod build {
use std::convert::identity;
use super::*;
use crate::tokens::*;
/// Write a `TokenStream` of the `$struct`'s fields to the `$tokens`.
///
/// All fields must represent a binding of the same name that implements `ToTokens`.
macro_rules! literal_struct {
($tokens:ident, $struct:ident, $($field:ident),+) => {
$tokens.append_all(quote! {
::tauri::utils::acl::resolved::$struct {
$($field: #$field),+
}
})
};
}
use crate::{literal_struct, tokens::*};
impl ToTokens for CommandKey {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = str_lit(&self.name);
let context = &self.context;
literal_struct!(tokens, CommandKey, name, context)
literal_struct!(
tokens,
::tauri::utils::acl::resolved::CommandKey,
name,
context
)
}
}
#[cfg(debug_assertions)]
impl ToTokens for ResolvedCommandReference {
fn to_tokens(&self, tokens: &mut TokenStream) {
let capability = str_lit(&self.capability);
let permission = str_lit(&self.permission);
literal_struct!(
tokens,
::tauri::utils::acl::resolved::ResolvedCommandReference,
capability,
permission
)
}
}
impl ToTokens for ResolvedCommand {
fn to_tokens(&self, tokens: &mut TokenStream) {
#[cfg(debug_assertions)]
let referenced_by = vec_lit(&self.referenced_by, identity);
let windows = vec_lit(&self.windows, |window| {
let w = window.as_str();
quote!(#w.parse().unwrap())
});
let scope = opt_lit(self.scope.as_ref());
literal_struct!(tokens, ResolvedCommand, windows, scope)
#[cfg(debug_assertions)]
{
literal_struct!(
tokens,
::tauri::utils::acl::resolved::ResolvedCommand,
referenced_by,
windows,
scope
)
}
#[cfg(not(debug_assertions))]
literal_struct!(
tokens,
::tauri::utils::acl::resolved::ResolvedCommand,
windows,
scope
)
}
}
@ -385,12 +467,25 @@ mod build {
fn to_tokens(&self, tokens: &mut TokenStream) {
let allow = vec_lit(&self.allow, identity);
let deny = vec_lit(&self.deny, identity);
literal_struct!(tokens, ResolvedScope, allow, deny)
literal_struct!(
tokens,
::tauri::utils::acl::resolved::ResolvedScope,
allow,
deny
)
}
}
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,
@ -419,9 +514,22 @@ mod build {
identity,
);
#[cfg(debug_assertions)]
{
literal_struct!(
tokens,
Resolved,
::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,
allowed_commands,
denied_commands,
command_scope,

View File

@ -2141,24 +2141,11 @@ fn default_build() -> BuildConfig {
#[cfg(feature = "build")]
mod build {
use super::*;
use crate::tokens::*;
use crate::{literal_struct, tokens::*};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use std::convert::identity;
/// Write a `TokenStream` of the `$struct`'s fields to the `$tokens`.
///
/// All fields must represent a binding of the same name that implements `ToTokens`.
macro_rules! literal_struct {
($tokens:ident, $struct:ident, $($field:ident),+) => {
$tokens.append_all(quote! {
::tauri::utils::config::$struct {
$($field: #$field),+
}
})
};
}
impl ToTokens for WebviewUrl {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::utils::config::WebviewUrl };
@ -2200,7 +2187,14 @@ mod build {
let radius = opt_lit(self.radius.as_ref());
let color = opt_lit(self.color.as_ref());
literal_struct!(tokens, WindowEffectsConfig, effects, state, radius, color)
literal_struct!(
tokens,
::tauri::utils::config::WindowEffectsConfig,
effects,
state,
radius,
color
)
}
}
@ -2309,7 +2303,7 @@ mod build {
literal_struct!(
tokens,
WindowConfig,
::tauri::utils::config::WindowConfig,
label,
url,
user_agent,
@ -2415,7 +2409,13 @@ mod build {
let pubkey = str_lit(&self.pubkey);
let windows = &self.windows;
literal_struct!(tokens, UpdaterConfig, active, pubkey, windows);
literal_struct!(
tokens,
::tauri::utils::config::UpdaterConfig,
active,
pubkey,
windows
);
}
}
@ -2437,7 +2437,7 @@ mod build {
let rpm = quote!(Default::default());
let dmg = quote!(Default::default());
let macos = quote!(Default::default());
let external_bin = opt_vec_str_lit(self.external_bin.as_ref());
let external_bin = opt_vec_lit(self.external_bin.as_ref(), str_lit);
let windows = &self.windows;
let ios = quote!(Default::default());
let android = quote!(Default::default());
@ -2445,7 +2445,7 @@ mod build {
literal_struct!(
tokens,
BundleConfig,
::tauri::utils::config::BundleConfig,
active,
identifier,
publisher,
@ -2500,7 +2500,7 @@ mod build {
literal_struct!(
tokens,
BuildConfig,
::tauri::utils::config::BuildConfig,
runner,
dev_path,
dist_dir,
@ -2528,7 +2528,11 @@ mod build {
impl ToTokens for UpdaterWindowsConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let install_mode = &self.install_mode;
literal_struct!(tokens, UpdaterWindowsConfig, install_mode);
literal_struct!(
tokens,
::tauri::utils::config::UpdaterWindowsConfig,
install_mode
);
}
}
@ -2596,7 +2600,7 @@ mod build {
literal_struct!(
tokens,
RemoteDomainAccessScope,
::tauri::utils::config::RemoteDomainAccessScope,
scheme,
domain,
windows,
@ -2615,7 +2619,7 @@ mod build {
literal_struct!(
tokens,
SecurityConfig,
::tauri::utils::config::SecurityConfig,
csp,
dev_csp,
freeze_prototype,
@ -2635,7 +2639,7 @@ mod build {
let tooltip = opt_str_lit(self.tooltip.as_ref());
literal_struct!(
tokens,
TrayIconConfig,
::tauri::utils::config::TrayIconConfig,
id,
icon_path,
icon_as_template,
@ -2683,7 +2687,7 @@ mod build {
literal_struct!(
tokens,
TauriConfig,
::tauri::utils::config::TauriConfig,
pattern,
windows,
bundle,
@ -2711,7 +2715,12 @@ mod build {
let product_name = opt_str_lit(self.product_name.as_ref());
let version = opt_str_lit(self.version.as_ref());
literal_struct!(tokens, PackageConfig, product_name, version);
literal_struct!(
tokens,
::tauri::utils::config::PackageConfig,
product_name,
version
);
}
}
@ -2723,7 +2732,15 @@ mod build {
let build = &self.build;
let plugins = &self.plugins;
literal_struct!(tokens, Config, schema, package, tauri, build, plugins);
literal_struct!(
tokens,
::tauri::utils::config::Config,
schema,
package,
tauri,
build,
plugins
);
}
}
}

View File

@ -11,6 +11,20 @@ use quote::{quote, ToTokens};
use serde_json::Value as JsonValue;
use url::Url;
/// Write a `TokenStream` of the `$struct`'s fields to the `$tokens`.
///
/// All fields must represent a binding of the same name that implements `ToTokens`.
#[macro_export]
macro_rules! literal_struct {
($tokens:ident, $struct:path, $($field:ident),+) => {
$tokens.append_all(quote! {
$struct {
$($field: #$field),+
}
})
};
}
/// Create a `String` constructor `TokenStream`.
///
/// e.g. `"Hello World" -> String::from("Hello World").
@ -28,14 +42,28 @@ pub fn opt_lit(item: Option<&impl ToTokens>) -> TokenStream {
}
}
/// Create an `Option` constructor `TokenStream` over an owned [`ToTokens`] impl type.
pub fn opt_lit_owned(item: Option<impl ToTokens>) -> TokenStream {
match item {
None => quote! { ::core::option::Option::None },
Some(item) => quote! { ::core::option::Option::Some(#item) },
}
}
/// Helper function to combine an `opt_lit` with `str_lit`.
pub fn opt_str_lit(item: Option<impl AsRef<str>>) -> TokenStream {
opt_lit(item.map(str_lit).as_ref())
}
/// Helper function to combine an `opt_lit` with a list of `str_lit`
pub fn opt_vec_str_lit(item: Option<impl IntoIterator<Item = impl AsRef<str>>>) -> TokenStream {
opt_lit(item.map(|list| vec_lit(list, str_lit)).as_ref())
pub fn opt_vec_lit<Raw, Tokens>(
item: Option<impl IntoIterator<Item = Raw>>,
map: impl Fn(Raw) -> Tokens,
) -> TokenStream
where
Tokens: ToTokens,
{
opt_lit(item.map(|list| vec_lit(list, map)).as_ref())
}
/// Create a `Vec` constructor, mapping items with a function that spits out `TokenStream`s.

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::fmt::Debug;
use std::fmt::{Debug, Display};
use std::{collections::BTreeMap, ops::Deref};
use serde::de::DeserializeOwned;
@ -21,6 +21,8 @@ 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>,
pub(crate) scope_manager: ScopeManager,
@ -37,6 +39,15 @@ pub enum Origin {
},
}
impl Display for Origin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Local => write!(f, "local"),
Self::Remote { domain } => write!(f, "remote: {domain}"),
}
}
}
impl Origin {
fn matches(&self, context: &ExecutionContext) -> bool {
match (self, context) {
@ -53,30 +64,160 @@ impl Origin {
}
impl RuntimeAuthority {
pub(crate) fn new(acl: Resolved) -> Self {
let command_cache = acl
pub(crate) fn new(resolved_acl: Resolved) -> Self {
let command_cache = resolved_acl
.command_scope
.keys()
.map(|key| (*key, <TypeMap![Send + Sync]>::new()))
.collect();
Self {
allowed_commands: acl.allowed_commands,
denied_commands: acl.denied_commands,
#[cfg(debug_assertions)]
acl: resolved_acl.acl,
allowed_commands: resolved_acl.allowed_commands,
denied_commands: resolved_acl.denied_commands,
scope_manager: ScopeManager {
command_scope: acl.command_scope,
global_scope: acl.global_scope,
command_scope: resolved_acl.command_scope,
global_scope: resolved_acl.global_scope,
command_cache,
global_scope_cache: Default::default(),
},
}
}
#[cfg(debug_assertions)]
pub(crate) fn resolve_access_message(
&self,
plugin: &str,
command_name: &str,
window: &str,
origin: &Origin,
) -> String {
fn print_references(resolved: &ResolvedCommand) -> String {
resolved
.referenced_by
.iter()
.map(|r| format!("capability: {}, permission: {}", r.capability, r.permission))
.collect::<Vec<_>>()
.join(" || ")
}
fn has_permissions_allowing_command(
manifest: &crate::utils::acl::plugin::Manifest,
set: &crate::utils::acl::PermissionSet,
command: &str,
) -> bool {
for permission_id in &set.permissions {
if permission_id == "default" {
if let Some(default) = &manifest.default_permission {
if has_permissions_allowing_command(manifest, default, command) {
return true;
}
}
} else if let Some(ref_set) = manifest.permission_sets.get(permission_id) {
if has_permissions_allowing_command(manifest, ref_set, command) {
return true;
}
} else if let Some(permission) = manifest.permissions.get(permission_id) {
if permission.commands.allow.contains(&command.into()) {
return true;
}
}
}
false
}
let command = format!("plugin:{plugin}|{command_name}");
if let Some((_cmd, resolved)) = self
.denied_commands
.iter()
.find(|(cmd, _)| cmd.name == command && origin.matches(&cmd.context))
{
format!(
"{plugin}.{command_name} denied on origin {origin}, referenced by: {}",
print_references(resolved)
)
} else {
let command_matches = self
.allowed_commands
.iter()
.filter(|(cmd, _)| cmd.name == command)
.collect::<BTreeMap<_, _>>();
if let Some((_cmd, resolved)) = command_matches
.iter()
.find(|(cmd, _)| origin.matches(&cmd.context))
{
if resolved.windows.iter().any(|w| w.matches(window)) {
"allowed".to_string()
} else {
format!("{plugin}.{command_name} not allowed on window {window}, expected one of {}, referenced by {}", resolved.windows.iter().map(|w| w.as_str()).collect::<Vec<_>>().join(", "), print_references(resolved))
}
} else {
let permission_error_detail = if let Some(manifest) = self.acl.get(plugin) {
let mut permissions_referencing_command = Vec::new();
if let Some(default) = &manifest.default_permission {
if has_permissions_allowing_command(manifest, default, command_name) {
permissions_referencing_command.push("default".into());
}
}
for set in manifest.permission_sets.values() {
if has_permissions_allowing_command(manifest, set, command_name) {
permissions_referencing_command.push(set.identifier.clone());
}
}
for permission in manifest.permissions.values() {
if permission.commands.allow.contains(&command_name.into()) {
permissions_referencing_command.push(permission.identifier.clone());
}
}
permissions_referencing_command.sort();
format!(
"Permissions associated with this command: {}",
permissions_referencing_command
.iter()
.map(|p| format!("{plugin}:{p}"))
.collect::<Vec<_>>()
.join(", ")
)
} else {
"Plugin did not define its manifest".to_string()
};
if command_matches.is_empty() {
format!("{plugin}.{command_name} not allowed. {permission_error_detail}")
} else {
format!(
"{plugin}.{command_name} not allowed on origin [{}]. Please create a capability that has this origin on the context field.\n\nFound matches for: {}\n\n{permission_error_detail}",
origin,
command_matches
.iter()
.map(|(cmd, resolved)| {
let context = match &cmd.context {
ExecutionContext::Local => "[local]".to_string(),
ExecutionContext::Remote { domain } => format!("[remote: {}]", domain.as_str()),
};
format!(
"- context: {context}, referenced by: {}",
print_references(resolved)
)
})
.collect::<Vec<_>>()
.join("\n")
)
}
}
}
}
/// Checks if the given IPC execution is allowed and returns the [`ResolvedCommand`] if it is.
pub fn resolve_access(
&self,
command: &str,
window: &str,
origin: Origin,
origin: &Origin,
) -> Option<&ResolvedCommand> {
if self
.denied_commands
@ -89,8 +230,8 @@ impl RuntimeAuthority {
.allowed_commands
.iter()
.find(|(cmd, _)| cmd.name == command && origin.matches(&cmd.context))
.map(|(_cmd, allowed)| allowed)
.filter(|allowed| allowed.windows.iter().any(|w| w.matches(window)))
.map(|(_cmd, resolved)| resolved)
.filter(|resolved| resolved.windows.iter().any(|w| w.matches(window)))
}
}
}
@ -329,7 +470,7 @@ mod tests {
let resolved_cmd = ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
scope: None,
..Default::default()
};
let allowed_commands = [(command.clone(), resolved_cmd.clone())]
.into_iter()
@ -337,16 +478,14 @@ mod tests {
let authority = RuntimeAuthority::new(Resolved {
allowed_commands,
denied_commands: Default::default(),
command_scope: Default::default(),
global_scope: Default::default(),
..Default::default()
});
assert_eq!(
authority.resolve_access(
&command.name,
&window.replace('*', "something"),
Origin::Local
&Origin::Local
),
Some(&resolved_cmd)
);
@ -366,6 +505,7 @@ mod tests {
let resolved_cmd = ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
scope: None,
..Default::default()
};
let allowed_commands = [(command.clone(), resolved_cmd.clone())]
.into_iter()
@ -373,16 +513,14 @@ mod tests {
let authority = RuntimeAuthority::new(Resolved {
allowed_commands,
denied_commands: Default::default(),
command_scope: Default::default(),
global_scope: Default::default(),
..Default::default()
});
assert_eq!(
authority.resolve_access(
&command.name,
window,
Origin::Remote {
&Origin::Remote {
domain: domain.into()
}
),
@ -404,6 +542,7 @@ mod tests {
let resolved_cmd = ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
scope: None,
..Default::default()
};
let allowed_commands = [(command.clone(), resolved_cmd.clone())]
.into_iter()
@ -411,16 +550,14 @@ mod tests {
let authority = RuntimeAuthority::new(Resolved {
allowed_commands,
denied_commands: Default::default(),
command_scope: Default::default(),
global_scope: Default::default(),
..Default::default()
});
assert_eq!(
authority.resolve_access(
&command.name,
window,
Origin::Remote {
&Origin::Remote {
domain: domain.replace('*', "studio")
}
),
@ -439,6 +576,7 @@ mod tests {
let resolved_cmd = ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
scope: None,
..Default::default()
};
let allowed_commands = [(command.clone(), resolved_cmd.clone())]
.into_iter()
@ -446,16 +584,14 @@ mod tests {
let authority = RuntimeAuthority::new(Resolved {
allowed_commands,
denied_commands: Default::default(),
command_scope: Default::default(),
global_scope: Default::default(),
..Default::default()
});
assert!(authority
.resolve_access(
&command.name,
window,
Origin::Remote {
&Origin::Remote {
domain: "tauri.app".into()
}
)
@ -474,7 +610,7 @@ mod tests {
command.clone(),
ResolvedCommand {
windows: windows.clone(),
scope: None,
..Default::default()
},
)]
.into_iter()
@ -483,7 +619,7 @@ mod tests {
command.clone(),
ResolvedCommand {
windows: windows.clone(),
scope: None,
..Default::default()
},
)]
.into_iter()
@ -492,12 +628,11 @@ mod tests {
let authority = RuntimeAuthority::new(Resolved {
allowed_commands,
denied_commands,
command_scope: Default::default(),
global_scope: Default::default(),
..Default::default()
});
assert!(authority
.resolve_access(&command.name, window, Origin::Local)
.resolve_access(&command.name, window, &Origin::Local)
.is_none());
}
}

View File

@ -125,6 +125,8 @@ 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(),

View File

@ -1093,12 +1093,7 @@ fn main() {
request.headers,
);
let resolved_acl = manager
.runtime_authority
.resolve_access(
&request.cmd,
&message.webview.webview.label,
if is_local {
let acl_origin = if is_local {
Origin::Local
} else {
Origin::Remote {
@ -1107,8 +1102,10 @@ fn main() {
.map(|d| d.to_string())
.unwrap_or_default(),
}
},
)
};
let resolved_acl = manager
.runtime_authority
.resolve_access(&request.cmd, &message.webview.webview.label, &acl_origin)
.cloned();
let mut invoke = Invoke {
@ -1117,20 +1114,33 @@ fn main() {
acl: resolved_acl,
};
if request.cmd.starts_with("plugin:") {
if let Some((plugin, command_name)) = request.cmd.strip_prefix("plugin:").map(|raw_command| {
let mut tokens = raw_command.split('|');
// safe to unwrap: split always has a least one item
let plugin = tokens.next().unwrap();
let command = tokens.next().map(|c| c.to_string()).unwrap_or_default();
(plugin, command)
}) {
if request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND && invoke.acl.is_none() {
invoke.resolver.reject("NOT ALLOWED");
#[cfg(debug_assertions)]
{
invoke
.resolver
.reject(manager.runtime_authority.resolve_access_message(
plugin,
&command_name,
&invoke.message.webview.webview.label,
&acl_origin,
));
}
#[cfg(not(debug_assertions))]
invoke
.resolver
.reject(format!("Command {} not allowed by ACL", request.cmd));
return;
}
let command = invoke.message.command.replace("plugin:", "");
let mut tokens = command.split('|');
// safe to unwrap: split always has a least one item
let plugin = tokens.next().unwrap();
invoke.message.command = tokens
.next()
.map(|c| c.to_string())
.unwrap_or_else(String::new);
invoke.message.command = command_name;
let command = invoke.message.command.clone();