feat(acl): allow a permission to apply to a subset of target platforms (#9014)

* feat(acl): allow a permission to apply to a subset of target platforms

* fix cli
This commit is contained in:
Lucas Fernandes Nogueira 2024-02-28 17:23:52 -03:00 committed by GitHub
parent d7d03c7197
commit d7f56fef85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 358 additions and 11 deletions

View File

@ -0,0 +1,6 @@
---
"tauri": patch:feat
"tauri-utils": patch:feat
---
Allow defining a permission that only applies to a set of target platforms via the `platforms` configuration option.

View File

@ -1123,7 +1123,7 @@
} }
}, },
"platforms": { "platforms": {
"description": "Target platforms this capability applies. By default all platforms applies.", "description": "Target platforms this capability applies. By default all platforms are affected by this capability.",
"default": [ "default": [
"linux", "linux",
"macOS", "macOS",

View File

@ -74,7 +74,7 @@ pub struct Capability {
pub webviews: Vec<String>, 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}`. /// 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>, pub permissions: Vec<PermissionEntry>,
/// Target platforms this capability applies. By default all platforms applies. /// Target platforms this capability applies. By default all platforms are affected by this capability.
#[serde(default = "default_platforms", skip_serializing_if = "Vec::is_empty")] #[serde(default = "default_platforms", skip_serializing_if = "Vec::is_empty")]
pub platforms: Vec<Target>, pub platforms: Vec<Target>,
} }

View File

@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize};
use std::num::NonZeroU64; use std::num::NonZeroU64;
use thiserror::Error; use thiserror::Error;
use crate::platform::Target;
pub use self::{identifier::*, value::*}; pub use self::{identifier::*, value::*};
/// Known filename of the permission schema JSON file /// Known filename of the permission schema JSON file
@ -172,6 +174,20 @@ pub struct Permission {
/// Allowed or denied scoped when using this permission. /// Allowed or denied scoped when using this permission.
#[serde(default, skip_serializing_if = "Scopes::is_empty")] #[serde(default, skip_serializing_if = "Scopes::is_empty")]
pub scope: Scopes, pub scope: Scopes,
/// Target platforms this permission applies. By default all platforms are affected by this permission.
#[serde(default = "default_platforms", skip_serializing_if = "Vec::is_empty")]
pub platforms: Vec<Target>,
}
fn default_platforms() -> Vec<Target> {
vec![
Target::Linux,
Target::MacOS,
Target::Windows,
Target::Android,
Target::Ios,
]
} }
/// A set of direct permissions grouped together under a new name. /// A set of direct permissions grouped together under a new name.
@ -252,6 +268,8 @@ mod build_ {
let description = opt_str_lit(self.description.as_ref()); let description = opt_str_lit(self.description.as_ref());
let commands = &self.commands; let commands = &self.commands;
let scope = &self.scope; let scope = &self.scope;
let platforms = vec_lit(&self.platforms, identity);
literal_struct!( literal_struct!(
tokens, tokens,
::tauri::utils::acl::Permission, ::tauri::utils::acl::Permission,
@ -259,7 +277,8 @@ mod build_ {
identifier, identifier,
description, description,
commands, commands,
scope scope,
platforms
) )
} }
} }

View File

@ -112,6 +112,7 @@ impl Resolved {
with_resolved_permissions( with_resolved_permissions(
capability, capability,
acl, acl,
target,
|ResolvedPermission { |ResolvedPermission {
key, key,
permission_name, permission_name,
@ -273,6 +274,7 @@ struct ResolvedPermission<'a> {
fn with_resolved_permissions<F: FnMut(ResolvedPermission<'_>)>( fn with_resolved_permissions<F: FnMut(ResolvedPermission<'_>)>(
capability: &Capability, capability: &Capability,
acl: &BTreeMap<String, Manifest>, acl: &BTreeMap<String, Manifest>,
target: Target,
mut f: F, mut f: F,
) -> Result<(), Error> { ) -> Result<(), Error> {
for permission_entry in &capability.permissions { for permission_entry in &capability.permissions {
@ -281,7 +283,10 @@ fn with_resolved_permissions<F: FnMut(ResolvedPermission<'_>)>(
let key = permission_id.get_prefix().unwrap_or(APP_ACL_KEY); let key = permission_id.get_prefix().unwrap_or(APP_ACL_KEY);
let permissions = get_permissions(key, permission_name, acl)?; let permissions = get_permissions(key, permission_name, acl)?
.into_iter()
.filter(|p| p.platforms.contains(&target))
.collect::<Vec<_>>();
let mut resolved_scope = Scopes::default(); let mut resolved_scope = Scopes::default();
let mut commands = Commands::default(); let mut commands = Commands::default();

View File

@ -0,0 +1,9 @@
identifier = "run-app"
description = "app capability"
windows = ["main"]
permissions = [
"os:allow-apt-linux",
"os:allow-library-folder-macos",
"os:deny-webview-folder-windows",
"os:open-browser",
]

View File

@ -0,0 +1,7 @@
[[permission]]
identifier = "allow-apt-linux"
platforms = ["linux"]
description = "Allows spawning the apt command on Linux"
commands.allow = ["spawn"]
[[permission.scope.allow]]
command = "apt"

View File

@ -0,0 +1,7 @@
[[permission]]
identifier = "allow-library-folder-macos"
platforms = ["macOS"]
description = "Allows access to the $HOME/Library folder on maOS"
[[permission.scope.allow]]
path = "$HOME/Library/**"

View File

@ -0,0 +1,28 @@
[[permission]]
identifier = "allow-servo-linux"
platforms = ["linux"]
description = "Allows starting servo on Linux"
commands.allow = ["spawn"]
[[permission.scope.allow]]
command = "servo"
[[permission]]
identifier = "allow-edge-windows"
platforms = ["windows"]
description = "Allows starting edge on Windows"
commands.allow = ["spawn"]
[[permission.scope.allow]]
command = "edge"
[[permission]]
identifier = "allow-safari-macos"
platforms = ["macOS"]
description = "Allows starting safari on macOS"
commands.allow = ["spawn"]
[[permission.scope.allow]]
command = "safari"
[[set]]
identifier = "open-browser"
description = "allows opening a URL on the platform browser"
permissions = ["allow-servo-linux", "allow-edge-windows", "allow-safari-macos"]

View File

@ -0,0 +1,6 @@
[[permission]]
identifier = "deny-webview-folder-windows"
platforms = ["windows"]
description = "Denies access to the webview folder on Windows"
[[permission.scope.deny]]
path = "$APP/EBWebView/**"

View File

@ -0,0 +1,65 @@
---
source: core/tests/acl/src/lib.rs
expression: resolved
---
Resolved {
allowed_commands: {
CommandKey {
name: "plugin:os|spawn",
context: Local,
}: ResolvedCommand {
windows: [
Pattern {
original: "main",
tokens: [
Char(
'm',
),
Char(
'a',
),
Char(
'i',
),
Char(
'n',
),
],
is_recursive: false,
},
],
webviews: [],
scope: Some(
8031926490300119127,
),
},
},
denied_commands: {},
command_scope: {
8031926490300119127: ResolvedScope {
allow: [
Map(
{
"command": String(
"apt",
),
},
),
Map(
{
"command": String(
"servo",
),
},
),
],
deny: [],
},
},
global_scope: {
"os": ResolvedScope {
allow: [],
deny: [],
},
},
}

View File

@ -0,0 +1,66 @@
---
source: core/tests/acl/src/lib.rs
expression: resolved
---
Resolved {
allowed_commands: {
CommandKey {
name: "plugin:os|spawn",
context: Local,
}: ResolvedCommand {
windows: [
Pattern {
original: "main",
tokens: [
Char(
'm',
),
Char(
'a',
),
Char(
'i',
),
Char(
'n',
),
],
is_recursive: false,
},
],
webviews: [],
scope: Some(
7912899488978770657,
),
},
},
denied_commands: {},
command_scope: {
7912899488978770657: ResolvedScope {
allow: [
Map(
{
"command": String(
"safari",
),
},
),
],
deny: [],
},
},
global_scope: {
"os": ResolvedScope {
allow: [
Map(
{
"path": String(
"$HOME/Library/**",
),
},
),
],
deny: [],
},
},
}

View File

@ -0,0 +1,66 @@
---
source: core/tests/acl/src/lib.rs
expression: resolved
---
Resolved {
allowed_commands: {
CommandKey {
name: "plugin:os|spawn",
context: Local,
}: ResolvedCommand {
windows: [
Pattern {
original: "main",
tokens: [
Char(
'm',
),
Char(
'a',
),
Char(
'i',
),
Char(
'n',
),
],
is_recursive: false,
},
],
webviews: [],
scope: Some(
7912899488978770657,
),
},
},
denied_commands: {},
command_scope: {
7912899488978770657: ResolvedScope {
allow: [
Map(
{
"command": String(
"edge",
),
},
),
],
deny: [],
},
},
global_scope: {
"os": ResolvedScope {
allow: [],
deny: [
Map(
{
"path": String(
"$APP/EBWebView/**",
),
},
),
],
},
},
}

View File

@ -41,14 +41,21 @@ mod tests {
#[test] #[test]
fn resolve_acl() { fn resolve_acl() {
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../fixtures/snapshots");
let _guard = settings.bind_to_scope();
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let fixtures_path = manifest_dir.join("fixtures").join("capabilities"); let fixtures_path = manifest_dir.join("fixtures").join("capabilities");
for fixture_path in read_dir(fixtures_path).expect("failed to read fixtures") { for fixture_path in read_dir(fixtures_path).expect("failed to read fixtures") {
let fixture_entry = fixture_path.expect("failed to read fixture entry"); let fixture_entry = fixture_path.expect("failed to read fixture entry");
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path(
if fixture_entry.path().file_name().unwrap() == "platform-specific-permissions" {
Path::new("../fixtures/snapshots").join(Target::current().to_string())
} else {
Path::new("../fixtures/snapshots").to_path_buf()
},
);
let _guard = settings.bind_to_scope();
let fixture_plugins_str = read_to_string(fixture_entry.path().join("required-plugins.json")) let fixture_plugins_str = read_to_string(fixture_entry.path().join("required-plugins.json"))
.expect("failed to read fixture required-plugins.json file"); .expect("failed to read fixture required-plugins.json file");
let fixture_plugins: Vec<String> = serde_json::from_str(&fixture_plugins_str) let fixture_plugins: Vec<String> = serde_json::from_str(&fixture_plugins_str)

View File

@ -136,6 +136,20 @@
"$ref": "#/definitions/Scopes" "$ref": "#/definitions/Scopes"
} }
] ]
},
"platforms": {
"description": "Target platforms this permission applies. By default all platforms are affected by this permission.",
"default": [
"linux",
"macOS",
"windows",
"android",
"iOS"
],
"type": "array",
"items": {
"$ref": "#/definitions/Target"
}
} }
} }
}, },
@ -241,6 +255,46 @@
} }
] ]
}, },
"Target": {
"description": "Platform target.",
"oneOf": [
{
"description": "MacOS.",
"type": "string",
"enum": [
"macOS"
]
},
{
"description": "Windows.",
"type": "string",
"enum": [
"windows"
]
},
{
"description": "Linux.",
"type": "string",
"enum": [
"linux"
]
},
{
"description": "Android.",
"type": "string",
"enum": [
"android"
]
},
{
"description": "iOS.",
"type": "string",
"enum": [
"iOS"
]
}
]
},
"PermissionKind": { "PermissionKind": {
"type": "string", "type": "string",
"oneOf": [ "oneOf": [

View File

@ -5,5 +5,5 @@ version = 1
identifier = "deny-home-dir-config" identifier = "deny-home-dir-config"
description = "Denies read access to the complete $HOME folder." description = "Denies read access to the complete $HOME folder."
[[scope.deny]] [[permission.scope.deny]]
path = "$HOME/.config" path = "$HOME/.config"

View File

@ -6,5 +6,5 @@ identifier = "allow-home-dir"
description = "Allows read access to the complete $HOME folder." description = "Allows read access to the complete $HOME folder."
commands.allow = ["readDirectory", "readFile"] commands.allow = ["readDirectory", "readFile"]
[[scope.allow]] [[permission.scope.allow]]
path = "$HOME/**" path = "$HOME/**"

View File

@ -1123,7 +1123,7 @@
} }
}, },
"platforms": { "platforms": {
"description": "Target platforms this capability applies. By default all platforms applies.", "description": "Target platforms this capability applies. By default all platforms are affected by this capability.",
"default": [ "default": [
"linux", "linux",
"macOS", "macOS",

View File

@ -63,6 +63,7 @@ pub fn command(options: Options) -> Result<()> {
description, description,
commands: Commands { allow, deny }, commands: Commands { allow, deny },
scope: Default::default(), scope: Default::default(),
platforms: Default::default(),
}; };
let path = match options.out { let path = match options.out {