refactor(acl): use URLPattern instead of glob for remote URLs (#9116)

This commit is contained in:
Lucas Fernandes Nogueira 2024-03-07 13:08:57 -03:00 committed by GitHub
parent 9dc9ca6e38
commit 4ef17d0833
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 490 additions and 169 deletions

View File

@ -0,0 +1,6 @@
---
"tauri": patch:breaking
"tauri-utils": patch:breaking
---
The ACL configuration for remote URLs now uses the URLPattern standard instead of glob patterns.

57
Cargo.lock generated
View File

@ -3642,6 +3642,7 @@ dependencies = [
"tracing",
"tray-icon",
"url",
"urlpattern",
"uuid",
"webkit2gtk",
"webview2-com",
@ -3796,6 +3797,7 @@ dependencies = [
"phf 0.11.2",
"proc-macro2",
"quote",
"regex",
"schemars",
"semver",
"serde",
@ -3806,6 +3808,7 @@ dependencies = [
"thiserror",
"toml 0.8.2",
"url",
"urlpattern",
"walkdir",
]
@ -4179,6 +4182,47 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-ucd-ident"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]]
name = "unicode-bidi"
version = "0.3.15"
@ -4234,6 +4278,19 @@ dependencies = [
"serde",
]
[[package]]
name = "urlpattern"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609"
dependencies = [
"derive_more",
"regex",
"serde",
"unic-ucd-ident",
"url",
]
[[package]]
name = "utf-8"
version = "0.7.6"

View File

@ -1146,7 +1146,7 @@
],
"properties": {
"urls": {
"description": "Remote domains this capability refers to. Can use glob patterns.",
"description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).",
"type": "array",
"items": {
"type": "string"

View File

@ -33,6 +33,8 @@ json5 = { version = "0.4", optional = true }
toml = { version = "0.8", features = [ "parse" ] }
json-patch = "1.2"
glob = "0.3"
urlpattern = "0.2"
regex = "1"
walkdir = { version = "2", optional = true }
memchr = "2"
semver = "1"

View File

@ -98,7 +98,7 @@ fn default_platforms() -> Vec<Target> {
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub struct CapabilityRemote {
/// Remote domains this capability refers to. Can use glob patterns.
/// Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).
pub urls: Vec<String>,
}

View File

@ -4,10 +4,10 @@
//! Access Control List types.
use glob::Pattern;
use serde::{Deserialize, Serialize};
use std::num::NonZeroU64;
use std::{num::NonZeroU64, str::FromStr, sync::Arc};
use thiserror::Error;
use url::Url;
use crate::platform::Target;
@ -204,16 +204,60 @@ pub struct PermissionSet {
pub permissions: Vec<String>,
}
/// UrlPattern for [`ExecutionContext::Remote`].
#[derive(Debug, Clone)]
pub struct RemoteUrlPattern(Arc<urlpattern::UrlPattern>, String);
impl FromStr for RemoteUrlPattern {
type Err = urlpattern::quirks::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let init = urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(s, None)?;
let pattern = urlpattern::UrlPattern::parse(init)?;
Ok(Self(Arc::new(pattern), s.to_string()))
}
}
impl RemoteUrlPattern {
#[doc(hidden)]
pub fn as_str(&self) -> &str {
&self.1
}
/// Test if a given URL matches the pattern.
pub fn test(&self, url: &Url) -> bool {
self
.0
.test(urlpattern::UrlPatternMatchInput::Url(url.clone()))
.unwrap_or_default()
}
}
impl PartialEq for RemoteUrlPattern {
fn eq(&self, other: &Self) -> bool {
self.0.protocol() == other.0.protocol()
&& self.0.username() == other.0.username()
&& self.0.password() == other.0.password()
&& self.0.hostname() == other.0.hostname()
&& self.0.port() == other.0.port()
&& self.0.pathname() == other.0.pathname()
&& self.0.search() == other.0.search()
&& self.0.hash() == other.0.hash()
}
}
impl Eq for RemoteUrlPattern {}
/// Execution context of an IPC call.
#[derive(Debug, Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub enum ExecutionContext {
/// A local URL is used (the Tauri app URL).
#[default]
Local,
/// Remote URL is tring to use the IPC.
Remote {
/// The URL trying to access the IPC (glob pattern).
url: Pattern,
/// The URL trying to access the IPC (URL pattern).
url: RemoteUrlPattern,
},
}

View File

@ -6,8 +6,6 @@
use std::{collections::BTreeMap, fmt};
use glob::Pattern;
use crate::platform::Target;
use super::{
@ -292,8 +290,9 @@ fn resolve_command(
if let Some(remote) = &capability.remote {
contexts.extend(remote.urls.iter().map(|url| {
ExecutionContext::Remote {
url: Pattern::new(url)
.unwrap_or_else(|e| panic!("invalid glob pattern for remote URL {url}: {e}")),
url: url
.parse()
.unwrap_or_else(|e| panic!("invalid URL pattern for remote URL {url}: {e}")),
}
}));
}

View File

@ -64,6 +64,7 @@ reqwest = { version = "0.11", default-features = false, features = [ "json", "st
bytes = { version = "1", features = [ "serde" ] }
raw-window-handle = "0.6"
glob = "0.3"
urlpattern = "0.2"
mime = "0.3"
data-url = { version = "0.3", optional = true }
serialize-to-javascript = "=0.1.1"

View File

@ -20,6 +20,8 @@ use tauri_utils::acl::{
ExecutionContext, Scopes,
};
use url::Url;
use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
use crate::{AppHandle, Manager};
@ -40,7 +42,7 @@ pub enum Origin {
/// Remote origin.
Remote {
/// Remote URL.
url: String,
url: Url,
},
}
@ -58,7 +60,7 @@ impl Origin {
match (self, context) {
(Self::Local, ExecutionContext::Local) => true,
(Self::Remote { url }, ExecutionContext::Remote { url: url_pattern }) => {
url_pattern.matches(url)
url_pattern.test(url)
}
_ => false,
}
@ -816,44 +818,7 @@ mod tests {
let resolved_cmd = vec![ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
context: ExecutionContext::Remote {
url: Pattern::new(url).unwrap(),
},
..Default::default()
}];
let allowed_commands = [(command.to_string(), resolved_cmd.clone())]
.into_iter()
.collect();
let authority = RuntimeAuthority::new(
Default::default(),
Resolved {
allowed_commands,
..Default::default()
},
);
assert_eq!(
authority.resolve_access(
command,
window,
webview,
&Origin::Remote { url: url.into() }
),
Some(resolved_cmd)
);
}
#[test]
fn remote_domain_glob_pattern_matches() {
let url = "http://tauri.*";
let command = "my-command";
let window = "main";
let webview = "main";
let resolved_cmd = vec![ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
context: ExecutionContext::Remote {
url: Pattern::new(url).unwrap(),
url: url.parse().unwrap(),
},
..Default::default()
}];
@ -875,7 +840,46 @@ mod tests {
window,
webview,
&Origin::Remote {
url: url.replace('*', "studio")
url: url.parse().unwrap()
}
),
Some(resolved_cmd)
);
}
#[test]
fn remote_domain_glob_pattern_matches() {
let url = "http://tauri.*";
let command = "my-command";
let window = "main";
let webview = "main";
let resolved_cmd = vec![ResolvedCommand {
windows: vec![Pattern::new(window).unwrap()],
context: ExecutionContext::Remote {
url: url.parse().unwrap(),
},
..Default::default()
}];
let allowed_commands = [(command.to_string(), resolved_cmd.clone())]
.into_iter()
.collect();
let authority = RuntimeAuthority::new(
Default::default(),
Resolved {
allowed_commands,
..Default::default()
},
);
assert_eq!(
authority.resolve_access(
command,
window,
webview,
&Origin::Remote {
url: url.replace('*', "studio").parse().unwrap()
}
),
Some(resolved_cmd)
@ -908,7 +912,7 @@ mod tests {
window,
webview,
&Origin::Remote {
url: "https://tauri.app".into()
url: "https://tauri.app".parse().unwrap()
}
)
.is_none());

View File

@ -1137,7 +1137,7 @@ fn main() {
Origin::Local
} else {
Origin::Remote {
url: current_url.to_string(),
url: current_url.clone(),
}
};
let (resolved_acl, has_app_acl_manifest) = {

View File

@ -7,63 +7,139 @@ Resolved {
"plugin:fs|read_dir": [
ResolvedCommand {
context: Remote {
url: Pattern {
original: "https://tauri.app",
tokens: [
Char(
'h',
),
Char(
't',
),
Char(
't',
),
Char(
'p',
),
Char(
's',
),
Char(
':',
),
Char(
'/',
),
Char(
'/',
),
Char(
't',
),
Char(
'a',
),
Char(
'u',
),
Char(
'r',
),
Char(
'i',
),
Char(
'.',
),
Char(
'a',
),
Char(
'p',
),
Char(
'p',
),
],
is_recursive: false,
},
url: RemoteUrlPattern(
UrlPattern {
protocol: Component {
pattern_string: "https",
regexp: Ok(
Regex(
"^https$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "https",
},
},
},
username: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
password: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
hostname: Component {
pattern_string: "tauri.app",
regexp: Ok(
Regex(
"^tauri\\.app$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "tauri.app",
},
},
},
port: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
pathname: Component {
pattern_string: "/",
regexp: Ok(
Regex(
"^/$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "/",
},
},
},
search: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
hash: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
},
"https://tauri.app",
),
},
windows: [
Pattern {
@ -92,63 +168,139 @@ Resolved {
"plugin:fs|read_file": [
ResolvedCommand {
context: Remote {
url: Pattern {
original: "https://tauri.app",
tokens: [
Char(
'h',
),
Char(
't',
),
Char(
't',
),
Char(
'p',
),
Char(
's',
),
Char(
':',
),
Char(
'/',
),
Char(
'/',
),
Char(
't',
),
Char(
'a',
),
Char(
'u',
),
Char(
'r',
),
Char(
'i',
),
Char(
'.',
),
Char(
'a',
),
Char(
'p',
),
Char(
'p',
),
],
is_recursive: false,
},
url: RemoteUrlPattern(
UrlPattern {
protocol: Component {
pattern_string: "https",
regexp: Ok(
Regex(
"^https$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "https",
},
},
},
username: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
password: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
hostname: Component {
pattern_string: "tauri.app",
regexp: Ok(
Regex(
"^tauri\\.app$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "tauri.app",
},
},
},
port: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
pathname: Component {
pattern_string: "/",
regexp: Ok(
Regex(
"^/$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "/",
},
},
},
search: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
hash: Component {
pattern_string: "",
regexp: Ok(
Regex(
"^$",
),
),
group_name_list: [],
matcher: Matcher {
prefix: "",
suffix: "",
inner: Literal {
literal: "",
},
},
},
},
"https://tauri.app",
),
},
windows: [
Pattern {

56
tooling/cli/Cargo.lock generated
View File

@ -4936,6 +4936,7 @@ dependencies = [
"log",
"memchr",
"phf 0.11.2",
"regex",
"schemars",
"semver",
"serde",
@ -4945,6 +4946,7 @@ dependencies = [
"thiserror",
"toml 0.8.10",
"url",
"urlpattern",
"walkdir",
]
@ -5359,6 +5361,47 @@ dependencies = [
"libc",
]
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-ucd-ident"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]]
name = "unicode-bidi"
version = "0.3.15"
@ -5475,6 +5518,19 @@ dependencies = [
"serde",
]
[[package]]
name = "urlpattern"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609"
dependencies = [
"derive_more",
"regex",
"serde",
"unic-ucd-ident",
"url",
]
[[package]]
name = "usvg"
version = "0.40.0"

View File

@ -1146,7 +1146,7 @@
],
"properties": {
"urls": {
"description": "Remote domains this capability refers to. Can use glob patterns.",
"description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).",
"type": "array",
"items": {
"type": "string"