mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-01 10:01:07 +02:00
feat(core): allow denying paths on the fs and asset scopes (#3607)
This commit is contained in:
committed by
GitHub
parent
b744cd2758
commit
983ccb815b
5
.changes/fs-scope-forbidden-paths.md
Normal file
5
.changes/fs-scope-forbidden-paths.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Allow configuring forbidden paths on the asset and filesystem scopes.
|
||||
@@ -638,9 +638,47 @@ macro_rules! check_feature {
|
||||
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
|
||||
/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
|
||||
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`.
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||
pub struct FsAllowlistScope(pub Vec<PathBuf>);
|
||||
#[serde(untagged)]
|
||||
pub enum FsAllowlistScope {
|
||||
/// A list of paths that are allowed by this scope.
|
||||
AllowedPaths(Vec<PathBuf>),
|
||||
/// A complete scope configuration.
|
||||
Scope {
|
||||
/// A list of paths that are allowed by this scope.
|
||||
#[serde(default)]
|
||||
allow: Vec<PathBuf>,
|
||||
/// A list of paths that are not allowed by this scope.
|
||||
/// This gets precedence over the [`Self::allow`] list.
|
||||
#[serde(default)]
|
||||
deny: Vec<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for FsAllowlistScope {
|
||||
fn default() -> Self {
|
||||
Self::AllowedPaths(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl FsAllowlistScope {
|
||||
/// The list of allowed paths.
|
||||
pub fn allowed_paths(&self) -> &Vec<PathBuf> {
|
||||
match self {
|
||||
Self::AllowedPaths(p) => p,
|
||||
Self::Scope { allow, .. } => allow,
|
||||
}
|
||||
}
|
||||
|
||||
/// The list of forbidden paths.
|
||||
pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
|
||||
match self {
|
||||
Self::AllowedPaths(_) => None,
|
||||
Self::Scope { deny, .. } => Some(deny),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allowlist for the file system APIs.
|
||||
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
|
||||
@@ -2381,8 +2419,19 @@ mod build {
|
||||
|
||||
impl ToTokens for FsAllowlistScope {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let allowed_paths = vec_lit(&self.0, path_buf_lit);
|
||||
tokens.append_all(quote! { ::tauri::utils::config::FsAllowlistScope(#allowed_paths) })
|
||||
let prefix = quote! { ::tauri::utils::config::FsAllowlistScope };
|
||||
|
||||
tokens.append_all(match self {
|
||||
Self::AllowedPaths(allow) => {
|
||||
let allowed_paths = vec_lit(allow, path_buf_lit);
|
||||
quote! { #prefix::AllowedPaths(#allowed_paths) }
|
||||
}
|
||||
Self::Scope { allow, deny } => {
|
||||
let allow = vec_lit(allow, path_buf_lit);
|
||||
let deny = vec_lit(deny, path_buf_lit);
|
||||
quote! { #prefix::Scope { allow: #allow, deny: #deny } }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::api::path::parse as parse_path;
|
||||
#[derive(Clone)]
|
||||
pub struct Scope {
|
||||
allow_patterns: Arc<Mutex<Vec<Pattern>>>,
|
||||
forbidden_patterns: Arc<Mutex<Vec<Pattern>>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Scope {
|
||||
@@ -35,6 +36,16 @@ impl fmt::Debug for Scope {
|
||||
.map(|p| p.as_str())
|
||||
.collect::<Vec<&str>>(),
|
||||
)
|
||||
.field(
|
||||
"forbidden_patterns",
|
||||
&self
|
||||
.forbidden_patterns
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|p| p.as_str())
|
||||
.collect::<Vec<&str>>(),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -58,13 +69,24 @@ impl Scope {
|
||||
scope: &FsAllowlistScope,
|
||||
) -> Self {
|
||||
let mut allow_patterns = Vec::new();
|
||||
for path in &scope.0 {
|
||||
for path in scope.allowed_paths() {
|
||||
if let Ok(path) = parse_path(config, package_info, env, path) {
|
||||
push_pattern(&mut allow_patterns, path);
|
||||
}
|
||||
}
|
||||
|
||||
let mut forbidden_patterns = Vec::new();
|
||||
if let Some(forbidden_paths) = scope.forbidden_paths() {
|
||||
for path in forbidden_paths {
|
||||
if let Ok(path) = parse_path(config, package_info, env, path) {
|
||||
push_pattern(&mut forbidden_patterns, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
allow_patterns: Arc::new(Mutex::new(allow_patterns)),
|
||||
forbidden_patterns: Arc::new(Mutex::new(forbidden_patterns)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +111,26 @@ impl Scope {
|
||||
push_pattern(&mut self.allow_patterns.lock().unwrap(), path);
|
||||
}
|
||||
|
||||
/// Set the given directory path to be forbidden by this scope.
|
||||
///
|
||||
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
|
||||
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let mut list = self.forbidden_patterns.lock().unwrap();
|
||||
|
||||
// allow the directory to be read
|
||||
push_pattern(&mut list, &path);
|
||||
// allow its files and subdirectories to be read
|
||||
push_pattern(&mut list, path.join(if recursive { "**" } else { "*" }));
|
||||
}
|
||||
|
||||
/// Set the given file path to be forbidden by this scope.
|
||||
///
|
||||
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
|
||||
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) {
|
||||
push_pattern(&mut self.forbidden_patterns.lock().unwrap(), path);
|
||||
}
|
||||
|
||||
/// Determines if the given path is allowed on this scope.
|
||||
pub fn is_allowed<P: AsRef<Path>>(&self, path: P) -> bool {
|
||||
let path = path.as_ref();
|
||||
@@ -100,13 +142,25 @@ impl Scope {
|
||||
|
||||
if let Ok(path) = path {
|
||||
let path: PathBuf = path.components().collect();
|
||||
let allowed = self
|
||||
.allow_patterns
|
||||
|
||||
let forbidden = self
|
||||
.forbidden_patterns
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|p| p.matches_path(&path));
|
||||
allowed
|
||||
|
||||
if forbidden {
|
||||
false
|
||||
} else {
|
||||
let allowed = self
|
||||
.allow_patterns
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|p| p.matches_path(&path));
|
||||
allowed
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -85,7 +85,10 @@
|
||||
"allowlist": {
|
||||
"all": true,
|
||||
"fs": {
|
||||
"scope": ["$APP/db", "$DOWNLOAD/**", "$RESOURCE/**"]
|
||||
"scope": {
|
||||
"allow": ["$APP/db/**", "$DOWNLOAD/**", "$RESOURCE/**"],
|
||||
"deny": ["$APP/db/*.stronghold"]
|
||||
}
|
||||
},
|
||||
"shell": {
|
||||
"scope": [
|
||||
@@ -103,7 +106,10 @@
|
||||
},
|
||||
"protocol": {
|
||||
"asset": true,
|
||||
"assetScope": ["$RESOURCE/**", "$APP/**"]
|
||||
"assetScope": {
|
||||
"allow": ["$APP/db/**", "$RESOURCE/**"],
|
||||
"deny": ["$APP/db/*.stronghold"]
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"scope": ["https://jsonplaceholder.typicode.com/todos/*"]
|
||||
@@ -116,7 +122,7 @@
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src 'self' customprotocol: img-src: 'self'; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com; img-src 'self' asset: https://asset.localhost blob: data:; font-src https://fonts.gstatic.com",
|
||||
"csp": "default-src 'self' customprotocol: asset: img-src: 'self'; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com; img-src 'self' asset: https://asset.localhost blob: data:; font-src https://fonts.gstatic.com",
|
||||
"freezePrototype": true
|
||||
},
|
||||
"systemTray": {
|
||||
|
||||
4
tooling/cli/Cargo.lock
generated
4
tooling/cli/Cargo.lock
generated
@@ -2756,9 +2756,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
@@ -997,10 +997,37 @@
|
||||
},
|
||||
"FsAllowlistScope": {
|
||||
"description": "Filesystem scope definition. It is a list of glob patterns that restrict the API access from the webview.\n\nEach pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A list of paths that are allowed by this scope.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "A complete scope configuration.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "A list of paths that are allowed by this scope.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "A list of paths that are not allowed by this scope. This gets precedence over the [`Self::allow`] list.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"GlobalShortcutAllowlistConfig": {
|
||||
"description": "Allowlist for the global shortcut APIs.",
|
||||
|
||||
Reference in New Issue
Block a user