Browse Source

feat(core): capabilities on multiwebview contexts (#8789)

* feat(core): capabilities on multiwebview contexts

* fix cli

* lint

* sort
Lucas Fernandes Nogueira 1 year ago
parent
commit
0cb0a15ce2

+ 6 - 0
.changes/capabilities-multiwebview.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch:enhance
+"tauri-utils": patch:enhance
+---
+
+Add `webviews` array on the capability for usage on multiwebview contexts.

+ 8 - 0
core/tauri-utils/src/acl/capability.rs

@@ -60,7 +60,15 @@ pub struct Capability {
   #[serde(default)]
   pub context: CapabilityContext,
   /// List of windows that uses this capability. Can be a glob pattern.
+  ///
+  /// On multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.
   pub windows: Vec<String>,
+  /// List of webviews that uses this capability. Can be a glob pattern.
+  ///
+  /// This is only required when using on multiwebview contexts, by default
+  /// all child webviews of a window that matches [`Self::windows`] are linked.
+  #[serde(default, skip_serializing_if = "Vec::is_empty")]
+  pub webviews: Vec<String>,
   /// List of permissions attached to this capability. Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.
   pub permissions: Vec<PermissionEntry>,
   /// Target platforms this capability applies. By default all platforms applies.

+ 23 - 5
core/tauri-utils/src/acl/resolved.rs

@@ -41,6 +41,8 @@ pub struct ResolvedCommand {
   pub referenced_by: Vec<ResolvedCommandReference>,
   /// The list of window label patterns that was resolved for this command.
   pub windows: Vec<glob::Pattern>,
+  /// The list of webview label patterns that was resolved for this command.
+  pub webviews: Vec<glob::Pattern>,
   /// The reference of the scope that is associated with this command. See [`Resolved#structfield.scopes`].
   pub scope: Option<ScopeKey>,
 }
@@ -49,6 +51,7 @@ impl fmt::Debug for ResolvedCommand {
   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     f.debug_struct("ResolvedCommand")
       .field("windows", &self.windows)
+      .field("webviews", &self.webviews)
       .field("scope", &self.scope)
       .finish()
   }
@@ -271,7 +274,8 @@ impl Resolved {
             ResolvedCommand {
               #[cfg(debug_assertions)]
               referenced_by: cmd.referenced_by,
-              windows: parse_window_patterns(cmd.windows)?,
+              windows: parse_glob_patterns(cmd.windows)?,
+              webviews: parse_glob_patterns(cmd.webviews)?,
               scope: cmd.resolved_scope_key,
             },
           ))
@@ -285,7 +289,8 @@ impl Resolved {
             ResolvedCommand {
               #[cfg(debug_assertions)]
               referenced_by: cmd.referenced_by,
-              windows: parse_window_patterns(cmd.windows)?,
+              windows: parse_glob_patterns(cmd.windows)?,
+              webviews: parse_glob_patterns(cmd.webviews)?,
               scope: cmd.resolved_scope_key,
             },
           ))
@@ -299,11 +304,15 @@ impl Resolved {
   }
 }
 
-fn parse_window_patterns(windows: HashSet<String>) -> Result<Vec<glob::Pattern>, Error> {
+fn parse_glob_patterns(raw: HashSet<String>) -> Result<Vec<glob::Pattern>, Error> {
+  let mut raw = raw.into_iter().collect::<Vec<_>>();
+  raw.sort();
+
   let mut patterns = Vec::new();
-  for window in windows {
-    patterns.push(glob::Pattern::new(&window)?);
+  for pattern in raw {
+    patterns.push(glob::Pattern::new(&pattern)?);
   }
+
   Ok(patterns)
 }
 
@@ -312,6 +321,7 @@ struct ResolvedCommandTemp {
   #[cfg(debug_assertions)]
   pub referenced_by: Vec<ResolvedCommandReference>,
   pub windows: HashSet<String>,
+  pub webviews: HashSet<String>,
   pub scope: Vec<ScopeKey>,
   pub resolved_scope_key: Option<ScopeKey>,
 }
@@ -351,6 +361,8 @@ fn resolve_command(
     });
 
     resolved.windows.extend(capability.windows.clone());
+    resolved.webviews.extend(capability.webviews.clone());
+
     if let Some(id) = scope_id {
       resolved.scope.push(id);
     }
@@ -456,6 +468,10 @@ mod build {
         let w = window.as_str();
         quote!(#w.parse().unwrap())
       });
+      let webviews = vec_lit(&self.webviews, |window| {
+        let w = window.as_str();
+        quote!(#w.parse().unwrap())
+      });
       let scope = opt_lit(self.scope.as_ref());
 
       #[cfg(debug_assertions)]
@@ -465,6 +481,7 @@ mod build {
           ::tauri::utils::acl::resolved::ResolvedCommand,
           referenced_by,
           windows,
+          webviews,
           scope
         )
       }
@@ -473,6 +490,7 @@ mod build {
         tokens,
         ::tauri::utils::acl::resolved::ResolvedCommand,
         windows,
+        webviews,
         scope
       )
     }

+ 58 - 4
core/tauri/src/ipc/authority.rs

@@ -90,6 +90,7 @@ impl RuntimeAuthority {
     plugin: &str,
     command_name: &str,
     window: &str,
+    webview: &str,
     origin: &Origin,
   ) -> String {
     fn print_references(resolved: &ResolvedCommand) -> String {
@@ -147,10 +148,16 @@ impl RuntimeAuthority {
         .iter()
         .find(|(cmd, _)| origin.matches(&cmd.context))
       {
-        if resolved.windows.iter().any(|w| w.matches(window)) {
+        if resolved.webviews.iter().any(|w| w.matches(webview))
+          || 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))
+          format!("{plugin}.{command_name} not allowed on window {window}, webview {webview}, allowed windows: {}, allowed webviews: {}, referenced by {}",
+            resolved.windows.iter().map(|w| w.as_str()).collect::<Vec<_>>().join(", "),
+            resolved.webviews.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) {
@@ -217,6 +224,7 @@ impl RuntimeAuthority {
     &self,
     command: &str,
     window: &str,
+    webview: &str,
     origin: &Origin,
   ) -> Option<&ResolvedCommand> {
     if self
@@ -231,7 +239,10 @@ impl RuntimeAuthority {
         .iter()
         .find(|(cmd, _)| cmd.name == command && origin.matches(&cmd.context))
         .map(|(_cmd, resolved)| resolved)
-        .filter(|resolved| resolved.windows.iter().any(|w| w.matches(window)))
+        .filter(|resolved| {
+          resolved.webviews.iter().any(|w| w.matches(webview))
+            || resolved.windows.iter().any(|w| w.matches(window))
+        })
     }
   }
 }
@@ -467,6 +478,7 @@ mod tests {
       context: ExecutionContext::Local,
     };
     let window = "main-*";
+    let webview = "other-*";
 
     let resolved_cmd = ResolvedCommand {
       windows: vec![Pattern::new(window).unwrap()],
@@ -485,6 +497,41 @@ mod tests {
       authority.resolve_access(
         &command.name,
         &window.replace('*', "something"),
+        webview,
+        &Origin::Local
+      ),
+      Some(&resolved_cmd)
+    );
+  }
+
+  #[test]
+  fn webview_glob_pattern_matches() {
+    let command = CommandKey {
+      name: "my-command".into(),
+      context: ExecutionContext::Local,
+    };
+    let window = "other-*";
+    let webview = "main-*";
+
+    let resolved_cmd = ResolvedCommand {
+      windows: vec![Pattern::new(window).unwrap()],
+      webviews: vec![Pattern::new(webview).unwrap()],
+      ..Default::default()
+    };
+    let allowed_commands = [(command.clone(), resolved_cmd.clone())]
+      .into_iter()
+      .collect();
+
+    let authority = RuntimeAuthority::new(Resolved {
+      allowed_commands,
+      ..Default::default()
+    });
+
+    assert_eq!(
+      authority.resolve_access(
+        &command.name,
+        window,
+        &webview.replace('*', "something"),
         &Origin::Local
       ),
       Some(&resolved_cmd)
@@ -501,6 +548,7 @@ mod tests {
       },
     };
     let window = "main";
+    let webview = "main";
 
     let resolved_cmd = ResolvedCommand {
       windows: vec![Pattern::new(window).unwrap()],
@@ -520,6 +568,7 @@ mod tests {
       authority.resolve_access(
         &command.name,
         window,
+        webview,
         &Origin::Remote {
           domain: domain.into()
         }
@@ -538,6 +587,7 @@ mod tests {
       },
     };
     let window = "main";
+    let webview = "main";
 
     let resolved_cmd = ResolvedCommand {
       windows: vec![Pattern::new(window).unwrap()],
@@ -557,6 +607,7 @@ mod tests {
       authority.resolve_access(
         &command.name,
         window,
+        webview,
         &Origin::Remote {
           domain: domain.replace('*', "studio")
         }
@@ -572,6 +623,7 @@ mod tests {
       context: ExecutionContext::Local,
     };
     let window = "main";
+    let webview = "main";
 
     let resolved_cmd = ResolvedCommand {
       windows: vec![Pattern::new(window).unwrap()],
@@ -591,6 +643,7 @@ mod tests {
       .resolve_access(
         &command.name,
         window,
+        webview,
         &Origin::Remote {
           domain: "tauri.app".into()
         }
@@ -605,6 +658,7 @@ mod tests {
       context: ExecutionContext::Local,
     };
     let window = "main";
+    let webview = "main";
     let windows = vec![Pattern::new(window).unwrap()];
     let allowed_commands = [(
       command.clone(),
@@ -632,7 +686,7 @@ mod tests {
     });
 
     assert!(authority
-      .resolve_access(&command.name, window, &Origin::Local)
+      .resolve_access(&command.name, window, webview, &Origin::Local)
       .is_none());
   }
 }

+ 8 - 2
core/tauri/src/webview/mod.rs

@@ -1121,7 +1121,12 @@ fn main() {
     };
     let resolved_acl = manager
       .runtime_authority
-      .resolve_access(&request.cmd, &message.webview.webview.label, &acl_origin)
+      .resolve_access(
+        &request.cmd,
+        message.webview.label(),
+        message.webview.window().label(),
+        &acl_origin,
+      )
       .cloned();
 
     let mut invoke = Invoke {
@@ -1145,7 +1150,8 @@ fn main() {
             .reject(manager.runtime_authority.resolve_access_message(
               plugin,
               &command_name,
-              &invoke.message.webview.webview.label,
+              invoke.message.webview.window().label(),
+              invoke.message.webview.label(),
               &acl_origin,
             ));
         }

+ 5 - 0
core/tests/acl/fixtures/capabilities/multiwebview/cap.toml

@@ -0,0 +1,5 @@
+identifier = "run-app"
+description = "app capability"
+windows = ["main"]
+webviews = ["child1", "child2"]
+permissions = ["ping:allow-ping"]

+ 1 - 0
core/tests/acl/fixtures/capabilities/multiwebview/required-plugins.json

@@ -0,0 +1 @@
+["ping"]

+ 1 - 1
core/tests/acl/fixtures/snapshots/acl_tests__tests__basic-ping.snap

@@ -1,6 +1,5 @@
 ---
 source: core/tests/acl/src/lib.rs
-assertion_line: 59
 expression: resolved
 ---
 Resolved {
@@ -29,6 +28,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: None,
         },
     },

+ 2 - 1
core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap

@@ -1,6 +1,5 @@
 ---
 source: core/tests/acl/src/lib.rs
-assertion_line: 59
 expression: resolved
 ---
 Resolved {
@@ -63,6 +62,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: None,
         },
         CommandKey {
@@ -123,6 +123,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: None,
         },
     },

+ 2 - 1
core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer.snap

@@ -1,6 +1,5 @@
 ---
 source: core/tests/acl/src/lib.rs
-assertion_line: 59
 expression: resolved
 ---
 Resolved {
@@ -29,6 +28,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: None,
         },
         CommandKey {
@@ -55,6 +55,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: None,
         },
     },

+ 87 - 0
core/tests/acl/fixtures/snapshots/acl_tests__tests__multiwebview.snap

@@ -0,0 +1,87 @@
+---
+source: core/tests/acl/src/lib.rs
+expression: resolved
+---
+Resolved {
+    allowed_commands: {
+        CommandKey {
+            name: "plugin:ping|ping",
+            context: Local,
+        }: ResolvedCommand {
+            windows: [
+                Pattern {
+                    original: "main",
+                    tokens: [
+                        Char(
+                            'm',
+                        ),
+                        Char(
+                            'a',
+                        ),
+                        Char(
+                            'i',
+                        ),
+                        Char(
+                            'n',
+                        ),
+                    ],
+                    is_recursive: false,
+                },
+            ],
+            webviews: [
+                Pattern {
+                    original: "child1",
+                    tokens: [
+                        Char(
+                            'c',
+                        ),
+                        Char(
+                            'h',
+                        ),
+                        Char(
+                            'i',
+                        ),
+                        Char(
+                            'l',
+                        ),
+                        Char(
+                            'd',
+                        ),
+                        Char(
+                            '1',
+                        ),
+                    ],
+                    is_recursive: false,
+                },
+                Pattern {
+                    original: "child2",
+                    tokens: [
+                        Char(
+                            'c',
+                        ),
+                        Char(
+                            'h',
+                        ),
+                        Char(
+                            'i',
+                        ),
+                        Char(
+                            'l',
+                        ),
+                        Char(
+                            'd',
+                        ),
+                        Char(
+                            '2',
+                        ),
+                    ],
+                    is_recursive: false,
+                },
+            ],
+            scope: None,
+        },
+    },
+    denied_commands: {},
+    command_scope: {},
+    global_scope: {},
+}

+ 3 - 0
core/tests/acl/fixtures/snapshots/acl_tests__tests__scope-extended.snap

@@ -28,6 +28,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: Some(
                 9188997750422900590,
             ),
@@ -56,6 +57,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: Some(
                 1349364295896631601,
             ),
@@ -84,6 +86,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: Some(
                 8031926490300119127,
             ),

+ 3 - 0
core/tests/acl/fixtures/snapshots/acl_tests__tests__scope.snap

@@ -28,6 +28,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: Some(
                 18088007599891946824,
             ),
@@ -56,6 +57,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: Some(
                 5856262838373339618,
             ),
@@ -84,6 +86,7 @@ Resolved {
                     is_recursive: false,
                 },
             ],
+            webviews: [],
             scope: Some(
                 7912899488978770657,
             ),

+ 1 - 0
tooling/cli/src/migrate/config.rs

@@ -61,6 +61,7 @@ pub fn migrate(tauri_dir: &Path) -> Result<()> {
         description: "permissions that were migrated from v1".into(),
         context: CapabilityContext::Local,
         windows: vec!["main".into()],
+        webviews: vec![],
         permissions,
         platforms: vec![
           Target::Linux,