Browse Source

feat(core): enhance IPC permission error message (#10664)

* 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
Lucas Fernandes Nogueira 11 months ago
parent
commit
ed04cc3d36
2 changed files with 231 additions and 26 deletions
  1. 5 0
      .changes/enhance-permission-error-message.md
  2. 226 26
      core/tauri/src/ipc/authority.rs

+ 5 - 0
.changes/enhance-permission-error-message.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch:enhance
+---
+
+Include more information in the IPC permission error message.

+ 226 - 26
core/tauri/src/ipc/authority.rs

@@ -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| {
@@ -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,
@@ -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)
           )
         }
@@ -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 {
@@ -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"
+    );
+  }
 }