Skip to content

Commit

Permalink
feat(core): enhance IPC permission error message (#10664)
Browse files Browse the repository at this point in the history
* feat(core): enhance IPC permission error message

- include more information about current URL and allowed origins
- enhance formatting of the error message

* plugin not found & command not found

* lint
  • Loading branch information
lucasfernog authored Aug 19, 2024
1 parent 7796a8f commit ed04cc3
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changes/enhance-permission-error-message.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:enhance
---

Include more information in the IPC permission error message.
252 changes: 226 additions & 26 deletions core/tauri/src/ipc/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ impl RuntimeAuthority {
webview: &str,
origin: &Origin,
) -> String {
fn print_references(resolved: Vec<&ResolvedCommand>) -> String {
fn print_references(resolved: &[ResolvedCommand]) -> String {
resolved
.iter()
.map(|r| {
Expand All @@ -356,6 +356,53 @@ impl RuntimeAuthority {
.join(" || ")
}

fn print_allowed_on(resolved: &[ResolvedCommand]) -> String {
if resolved.is_empty() {
"command not allowed on any window/webview/URL context".to_string()
} else {
let mut s = "allowed on: ".to_string();

let last_index = resolved.len() - 1;
for (index, cmd) in resolved.iter().enumerate() {
let windows = cmd
.windows
.iter()
.map(|w| format!("\"{}\"", w.as_str()))
.collect::<Vec<_>>()
.join(", ");
let webviews = cmd
.webviews
.iter()
.map(|w| format!("\"{}\"", w.as_str()))
.collect::<Vec<_>>()
.join(", ");

s.push('[');

if !windows.is_empty() {
s.push_str(&format!("windows: {windows}, "));
}

if !webviews.is_empty() {
s.push_str(&format!("webviews: {webviews}, "));
}

match &cmd.context {
ExecutionContext::Local => s.push_str("URL: local"),
ExecutionContext::Remote { url } => s.push_str(&format!("URL: {}", url.as_str())),
}

s.push(']');

if index != last_index {
s.push_str(", ");
}
}

s
}
}

fn has_permissions_allowing_command(
manifest: &crate::utils::acl::manifest::Manifest,
set: &crate::utils::acl::PermissionSet,
Expand Down Expand Up @@ -393,35 +440,34 @@ impl RuntimeAuthority {
format!("{key}.{command_name}")
};

if let Some(resolved) = self.denied_commands.get(&command).map(|r| {
r.iter()
.filter(|cmd| origin.matches(&cmd.context))
.collect()
}) {
if let Some(resolved) = self.denied_commands.get(&command) {
format!(
"{command_pretty_name} denied on origin {origin}, referenced by: {}",
"{command_pretty_name} explicitly denied on origin {origin}\n\nreferenced by: {}",
print_references(resolved)
)
} else {
let command_matches = self.allowed_commands.get(&command);

if let Some(resolved) = self.allowed_commands.get(&command).map(|r| {
r.iter()
if let Some(resolved) = self.allowed_commands.get(&command) {
let resolved_matching_origin = resolved
.iter()
.filter(|cmd| origin.matches(&cmd.context))
.collect::<Vec<&ResolvedCommand>>()
}) {
if resolved
.collect::<Vec<&ResolvedCommand>>();
if resolved_matching_origin
.iter()
.any(|cmd| cmd.webviews.iter().any(|w| w.matches(webview)))
|| resolved
|| resolved_matching_origin
.iter()
.any(|cmd| cmd.windows.iter().any(|w| w.matches(window)))
{
"allowed".to_string()
} else {
format!("{command_pretty_name} not allowed on window {window}, webview {webview}, allowed windows: {}, allowed webviews: {}, referenced by {}",
resolved.iter().flat_map(|cmd| cmd.windows.iter().map(|w| w.as_str())).collect::<Vec<_>>().join(", "),
resolved.iter().flat_map(|cmd| cmd.webviews.iter().map(|w| w.as_str())).collect::<Vec<_>>().join(", "),
format!("{command_pretty_name} not allowed on window \"{window}\", webview \"{webview}\", URL: {}\n\n{}\n\nreferenced by: {}",
match origin {
Origin::Local => "local",
Origin::Remote { url } => url.as_str()
},
print_allowed_on(resolved),
print_references(resolved)
)
}
Expand Down Expand Up @@ -451,20 +497,25 @@ impl RuntimeAuthority {

permissions_referencing_command.sort();

format!(
"Permissions associated with this command: {}",
permissions_referencing_command
.iter()
.map(|p| if key == APP_ACL_KEY {
let associated_permissions = permissions_referencing_command
.iter()
.map(|p| {
if key == APP_ACL_KEY {
p.to_string()
} else {
format!("{key}:{p}")
})
.collect::<Vec<_>>()
.join(", ")
)
}
})
.collect::<Vec<_>>()
.join(", ");

if associated_permissions.is_empty() {
"Command not found".to_string()
} else {
format!("Permissions associated with this command: {associated_permissions}")
}
} else {
"Plugin did not define its manifest".to_string()
"Plugin not found".to_string()
};

if let Some(resolved_cmds) = command_matches {
Expand Down Expand Up @@ -985,4 +1036,153 @@ mod tests {
.resolve_access(command, window, webview, &Origin::Local)
.is_none());
}

#[cfg(debug_assertions)]
#[test]
fn resolve_access_message() {
use tauri_utils::acl::manifest::Manifest;

let plugin_name = "myplugin";
let command_allowed_on_window = "my-command-window";
let command_allowed_on_webview_window = "my-command-webview-window";
let window = "main-*";
let webview = "webview-*";
let remote_url = "http://localhost:8080";

let referenced_by = tauri_utils::acl::resolved::ResolvedCommandReference {
capability: "maincap".to_string(),
permission: "allow-command".to_string(),
};

let resolved_window_cmd = ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
referenced_by: referenced_by.clone(),
..Default::default()
};
let resolved_webview_window_cmd = ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
webviews: vec![Pattern::new(webview).unwrap()],
referenced_by: referenced_by.clone(),
..Default::default()
};
let resolved_webview_window_remote_cmd = ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
webviews: vec![Pattern::new(webview).unwrap()],
referenced_by: referenced_by.clone(),
context: ExecutionContext::Remote {
url: remote_url.parse().unwrap(),
},
..Default::default()
};

let allowed_commands = [
(
format!("plugin:{plugin_name}|{command_allowed_on_window}"),
vec![resolved_window_cmd],
),
(
format!("plugin:{plugin_name}|{command_allowed_on_webview_window}"),
vec![
resolved_webview_window_cmd,
resolved_webview_window_remote_cmd,
],
),
]
.into_iter()
.collect();

let authority = RuntimeAuthority::new(
[(
plugin_name.to_string(),
Manifest {
default_permission: None,
permissions: Default::default(),
permission_sets: Default::default(),
global_scope_schema: None,
},
)]
.into_iter()
.collect(),
Resolved {
allowed_commands,
..Default::default()
},
);

// unknown plugin
assert_eq!(
authority.resolve_access_message(
"unknown-plugin",
command_allowed_on_window,
window,
webview,
&Origin::Local
),
"unknown-plugin.my-command-window not allowed. Plugin not found"
);

// unknown command
assert_eq!(
authority.resolve_access_message(
plugin_name,
"unknown-command",
window,
webview,
&Origin::Local
),
"myplugin.unknown-command not allowed. Command not found"
);

// window/webview do not match
assert_eq!(
authority.resolve_access_message(
plugin_name,
command_allowed_on_window,
"other-window",
"any-webview",
&Origin::Local
),
"myplugin.my-command-window not allowed on window \"other-window\", webview \"any-webview\", URL: local\n\nallowed on: [windows: \"main-*\", URL: local]\n\nreferenced by: capability: maincap, permission: allow-command"
);

// window matches, but not origin
assert_eq!(
authority.resolve_access_message(
plugin_name,
command_allowed_on_window,
window,
"any-webview",
&Origin::Remote {
url: "http://localhst".parse().unwrap()
}
),
"myplugin.my-command-window not allowed on window \"main-*\", webview \"any-webview\", URL: http://localhst/\n\nallowed on: [windows: \"main-*\", URL: local]\n\nreferenced by: capability: maincap, permission: allow-command"
);

// window/webview do not match
assert_eq!(
authority.resolve_access_message(
plugin_name,
command_allowed_on_webview_window,
"other-window",
"other-webview",
&Origin::Local
),
"myplugin.my-command-webview-window not allowed on window \"other-window\", webview \"other-webview\", URL: local\n\nallowed on: [windows: \"main-*\", webviews: \"webview-*\", URL: local], [windows: \"main-*\", webviews: \"webview-*\", URL: http://localhost:8080]\n\nreferenced by: capability: maincap, permission: allow-command || capability: maincap, permission: allow-command"
);

// window/webview matches, but not origin
assert_eq!(
authority.resolve_access_message(
plugin_name,
command_allowed_on_webview_window,
window,
webview,
&Origin::Remote {
url: "http://localhost:123".parse().unwrap()
}
),
"myplugin.my-command-webview-window not allowed on window \"main-*\", webview \"webview-*\", URL: http://localhost:123/\n\nallowed on: [windows: \"main-*\", webviews: \"webview-*\", URL: local], [windows: \"main-*\", webviews: \"webview-*\", URL: http://localhost:8080]\n\nreferenced by: capability: maincap, permission: allow-command || capability: maincap, permission: allow-command"
);
}
}

0 comments on commit ed04cc3

Please sign in to comment.