diff --git a/.changes/capabilities-multiwebview.md b/.changes/capabilities-multiwebview.md new file mode 100644 index 000000000..1fc9aa9e6 --- /dev/null +++ b/.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. diff --git a/core/tauri-utils/src/acl/capability.rs b/core/tauri-utils/src/acl/capability.rs index 4f35ae782..7afadf654 100644 --- a/core/tauri-utils/src/acl/capability.rs +++ b/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, + /// 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, /// 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, /// Target platforms this capability applies. By default all platforms applies. diff --git a/core/tauri-utils/src/acl/resolved.rs b/core/tauri-utils/src/acl/resolved.rs index 9a138fce8..d60448d25 100644 --- a/core/tauri-utils/src/acl/resolved.rs +++ b/core/tauri-utils/src/acl/resolved.rs @@ -41,6 +41,8 @@ pub struct ResolvedCommand { pub referenced_by: Vec, /// The list of window label patterns that was resolved for this command. pub windows: Vec, + /// The list of webview label patterns that was resolved for this command. + pub webviews: Vec, /// The reference of the scope that is associated with this command. See [`Resolved#structfield.scopes`]. pub scope: Option, } @@ -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) -> Result, Error> { +fn parse_glob_patterns(raw: HashSet) -> Result, Error> { + let mut raw = raw.into_iter().collect::>(); + 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, pub windows: HashSet, + pub webviews: HashSet, pub scope: Vec, pub resolved_scope_key: Option, } @@ -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 ) } diff --git a/core/tauri/src/ipc/authority.rs b/core/tauri/src/ipc/authority.rs index 1ff3f4a91..34126ecce 100644 --- a/core/tauri/src/ipc/authority.rs +++ b/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::>().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::>().join(", "), + resolved.webviews.iter().map(|w| w.as_str()).collect::>().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()); } } diff --git a/core/tauri/src/webview/mod.rs b/core/tauri/src/webview/mod.rs index b64840420..0b86035cb 100644 --- a/core/tauri/src/webview/mod.rs +++ b/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, )); } diff --git a/core/tests/acl/fixtures/capabilities/multiwebview/cap.toml b/core/tests/acl/fixtures/capabilities/multiwebview/cap.toml new file mode 100644 index 000000000..eada13bee --- /dev/null +++ b/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"] diff --git a/core/tests/acl/fixtures/capabilities/multiwebview/required-plugins.json b/core/tests/acl/fixtures/capabilities/multiwebview/required-plugins.json new file mode 100644 index 000000000..4239d5ee3 --- /dev/null +++ b/core/tests/acl/fixtures/capabilities/multiwebview/required-plugins.json @@ -0,0 +1 @@ +["ping"] diff --git a/core/tests/acl/fixtures/snapshots/acl_tests__tests__basic-ping.snap b/core/tests/acl/fixtures/snapshots/acl_tests__tests__basic-ping.snap index afb562c98..b32a332a6 100644 --- a/core/tests/acl/fixtures/snapshots/acl_tests__tests__basic-ping.snap +++ b/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, }, }, diff --git a/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap b/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap index 2e1664d70..f28578025 100644 --- a/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer-remote.snap +++ b/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, }, }, diff --git a/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer.snap b/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer.snap index 0571cf4ca..260780ada 100644 --- a/core/tests/acl/fixtures/snapshots/acl_tests__tests__file-explorer.snap +++ b/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, }, }, diff --git a/core/tests/acl/fixtures/snapshots/acl_tests__tests__multiwebview.snap b/core/tests/acl/fixtures/snapshots/acl_tests__tests__multiwebview.snap new file mode 100644 index 000000000..54ef63c38 --- /dev/null +++ b/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: {}, +} diff --git a/core/tests/acl/fixtures/snapshots/acl_tests__tests__scope-extended.snap b/core/tests/acl/fixtures/snapshots/acl_tests__tests__scope-extended.snap index fee8df2a6..595f0f03c 100644 --- a/core/tests/acl/fixtures/snapshots/acl_tests__tests__scope-extended.snap +++ b/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, ), diff --git a/core/tests/acl/fixtures/snapshots/acl_tests__tests__scope.snap b/core/tests/acl/fixtures/snapshots/acl_tests__tests__scope.snap index 5805c5833..b44e5bed6 100644 --- a/core/tests/acl/fixtures/snapshots/acl_tests__tests__scope.snap +++ b/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, ), diff --git a/tooling/cli/src/migrate/config.rs b/tooling/cli/src/migrate/config.rs index e37286c48..2f0201cb9 100644 --- a/tooling/cli/src/migrate/config.rs +++ b/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,