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

* feat(core): capabilities on multiwebview contexts

* fix cli

* lint

* sort
This commit is contained in:
Lucas Fernandes Nogueira 2024-02-16 08:24:51 -03:00 committed by GitHub
parent edb11c138d
commit 0cb0a15ce2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 208 additions and 14 deletions

View File

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

View File

@ -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.

View File

@ -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
)
}

View File

@ -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());
}
}

View File

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

View File

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

View File

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

View File

@ -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,
},
},

View File

@ -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,
},
},

View File

@ -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,
},
},

View File

@ -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: {},
}

View File

@ -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,
),

View File

@ -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,
),

View File

@ -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,