feat(core): allow denying paths on the fs and asset scopes (#3607)

This commit is contained in:
Lucas Fernandes Nogueira
2022-03-03 17:32:12 -03:00
committed by GitHub
parent b744cd2758
commit 983ccb815b
6 changed files with 158 additions and 17 deletions

View File

@@ -0,0 +1,5 @@
---
"tauri": patch
---
Allow configuring forbidden paths on the asset and filesystem scopes.

View File

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

View File

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

View File

@@ -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": {

View File

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

View File

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