mirror of
https://github.com/tauri-apps/tauri.git
synced 2025-01-07 03:44:07 +03:00
refactor(acl): allow extending scope on the capability file (#8674)
This commit is contained in:
parent
734d78f736
commit
fd4bf94d4c
@ -132,8 +132,9 @@ pub fn validate_capabilities(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for permission in &capability.permissions {
|
for permission_entry in &capability.permissions {
|
||||||
if let Some((plugin_name, permission_name)) = permission.get().split_once(':') {
|
let permission_id = permission_entry.identifier();
|
||||||
|
if let Some((plugin_name, permission_name)) = permission_id.get().split_once(':') {
|
||||||
let permission_exists = plugin_manifests
|
let permission_exists = plugin_manifests
|
||||||
.get(plugin_name)
|
.get(plugin_name)
|
||||||
.map(|manifest| {
|
.map(|manifest| {
|
||||||
@ -162,7 +163,7 @@ pub fn validate_capabilities(
|
|||||||
|
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Permission {} not found, expected one of {}",
|
"Permission {} not found, expected one of {}",
|
||||||
permission.get(),
|
permission_id.get(),
|
||||||
available_permissions.join(", ")
|
available_permissions.join(", ")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,37 @@
|
|||||||
use crate::{acl::Identifier, platform::Target};
|
use crate::{acl::Identifier, platform::Target};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
use super::Scopes;
|
||||||
|
|
||||||
/// A set of direct capabilities grouped together under a new name.
|
/// An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`]
|
||||||
pub struct CapabilitySet {
|
/// or an object that references a permission and extends its scope.
|
||||||
inner: Vec<Capability>,
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
pub enum PermissionEntry {
|
||||||
|
/// Reference a permission or permission set by identifier.
|
||||||
|
PermissionRef(Identifier),
|
||||||
|
/// Reference a permission or permission set by identifier and extends its scope.
|
||||||
|
ExtendedPermission {
|
||||||
|
/// Identifier of the permission or permission set.
|
||||||
|
identifier: Identifier,
|
||||||
|
/// Scope to append to the existing permission scope.
|
||||||
|
#[serde(default, flatten)]
|
||||||
|
scope: Scopes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PermissionEntry {
|
||||||
|
/// The identifier of the permission referenced in this entry.
|
||||||
|
pub fn identifier(&self) -> &Identifier {
|
||||||
|
match self {
|
||||||
|
Self::PermissionRef(identifier) => identifier,
|
||||||
|
Self::ExtendedPermission {
|
||||||
|
identifier,
|
||||||
|
scope: _,
|
||||||
|
} => identifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime.
|
/// a grouping and boundary mechanism developers can use to separate windows or plugins functionality from each other at runtime.
|
||||||
@ -36,7 +62,7 @@ pub struct Capability {
|
|||||||
/// List of windows that uses this capability. Can be a glob pattern.
|
/// List of windows that uses this capability. Can be a glob pattern.
|
||||||
pub windows: Vec<String>,
|
pub windows: 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<Identifier>,
|
pub permissions: Vec<PermissionEntry>,
|
||||||
/// Target platforms this capability applies. By default all platforms applies.
|
/// Target platforms this capability applies. By default all platforms applies.
|
||||||
#[serde(default = "default_platforms")]
|
#[serde(default = "default_platforms")]
|
||||||
pub platforms: Vec<Target>,
|
pub platforms: Vec<Target>,
|
||||||
|
@ -14,7 +14,7 @@ use glob::Pattern;
|
|||||||
use crate::platform::Target;
|
use crate::platform::Target;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
capability::{Capability, CapabilityContext},
|
capability::{Capability, CapabilityContext, PermissionEntry},
|
||||||
plugin::Manifest,
|
plugin::Manifest,
|
||||||
Error, ExecutionContext, Permission, PermissionSet, Scopes, Value,
|
Error, ExecutionContext, Permission, PermissionSet, Scopes, Value,
|
||||||
};
|
};
|
||||||
@ -83,24 +83,48 @@ impl Resolved {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for permission_id in &capability.permissions {
|
for permission_entry in &capability.permissions {
|
||||||
|
let permission_id = permission_entry.identifier();
|
||||||
let permission_name = permission_id.get_base();
|
let permission_name = permission_id.get_base();
|
||||||
|
|
||||||
if let Some(plugin_name) = permission_id.get_prefix() {
|
if let Some(plugin_name) = permission_id.get_prefix() {
|
||||||
let permissions = get_permissions(plugin_name, permission_name, &acl)?;
|
let permissions = get_permissions(plugin_name, permission_name, &acl)?;
|
||||||
|
|
||||||
for permission in permissions {
|
for permission in permissions {
|
||||||
|
let scope = match permission_entry {
|
||||||
|
PermissionEntry::PermissionRef(_) => permission.scope.clone(),
|
||||||
|
PermissionEntry::ExtendedPermission {
|
||||||
|
identifier: _,
|
||||||
|
scope,
|
||||||
|
} => {
|
||||||
|
let mut merged = permission.scope.clone();
|
||||||
|
if let Some(allow) = scope.allow.clone() {
|
||||||
|
merged
|
||||||
|
.allow
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.extend(allow);
|
||||||
|
}
|
||||||
|
if let Some(deny) = scope.deny.clone() {
|
||||||
|
merged
|
||||||
|
.deny
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.extend(deny);
|
||||||
|
}
|
||||||
|
merged
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if permission.commands.allow.is_empty() && permission.commands.deny.is_empty() {
|
if permission.commands.allow.is_empty() && permission.commands.deny.is_empty() {
|
||||||
// global scope
|
// global scope
|
||||||
global_scope
|
global_scope
|
||||||
.entry(plugin_name.to_string())
|
.entry(plugin_name.to_string())
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(permission.scope.clone());
|
.push(scope.clone());
|
||||||
} else {
|
} else {
|
||||||
let has_scope = permission.scope.allow.is_some() || permission.scope.deny.is_some();
|
let has_scope = scope.allow.is_some() || scope.deny.is_some();
|
||||||
if has_scope {
|
if has_scope {
|
||||||
current_scope_id += 1;
|
current_scope_id += 1;
|
||||||
command_scopes.insert(current_scope_id, permission.scope.clone());
|
command_scopes.insert(current_scope_id, scope.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope_id = if has_scope {
|
let scope_id = if has_scope {
|
||||||
|
40
core/tests/acl/fixtures/capabilities/scope-extended/cap.json
Normal file
40
core/tests/acl/fixtures/capabilities/scope-extended/cap.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"identifier": "run-app",
|
||||||
|
"description": "app capability",
|
||||||
|
"windows": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"identifier": "fs:read",
|
||||||
|
"allow": [
|
||||||
|
{
|
||||||
|
"path": "$HOME/.config/**"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fs:deny-home",
|
||||||
|
{
|
||||||
|
"identifier": "fs:allow-read-resources",
|
||||||
|
"deny": [
|
||||||
|
{
|
||||||
|
"path": "$RESOURCE/**/*.key"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fs:allow-move-temp",
|
||||||
|
{
|
||||||
|
"identifier": "fs:allow-app",
|
||||||
|
"allow": [
|
||||||
|
{
|
||||||
|
"path": "$APP/**"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deny": [
|
||||||
|
{
|
||||||
|
"path": "$APP/*.db"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
["fs"]
|
@ -0,0 +1,212 @@
|
|||||||
|
---
|
||||||
|
source: core/tests/acl/src/lib.rs
|
||||||
|
assertion_line: 59
|
||||||
|
expression: resolved
|
||||||
|
---
|
||||||
|
Resolved {
|
||||||
|
allowed_commands: {
|
||||||
|
CommandKey {
|
||||||
|
name: "plugin:fs|move",
|
||||||
|
context: Local,
|
||||||
|
}: ResolvedCommand {
|
||||||
|
windows: [
|
||||||
|
Pattern {
|
||||||
|
original: "main",
|
||||||
|
tokens: [
|
||||||
|
Char(
|
||||||
|
'm',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'a',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'i',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'n',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
is_recursive: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scope: Some(
|
||||||
|
792017965103506125,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
CommandKey {
|
||||||
|
name: "plugin:fs|read_dir",
|
||||||
|
context: Local,
|
||||||
|
}: ResolvedCommand {
|
||||||
|
windows: [
|
||||||
|
Pattern {
|
||||||
|
original: "main",
|
||||||
|
tokens: [
|
||||||
|
Char(
|
||||||
|
'm',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'a',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'i',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'n',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
is_recursive: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scope: Some(
|
||||||
|
5856262838373339618,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
CommandKey {
|
||||||
|
name: "plugin:fs|read_file",
|
||||||
|
context: Local,
|
||||||
|
}: ResolvedCommand {
|
||||||
|
windows: [
|
||||||
|
Pattern {
|
||||||
|
original: "main",
|
||||||
|
tokens: [
|
||||||
|
Char(
|
||||||
|
'm',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'a',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'i',
|
||||||
|
),
|
||||||
|
Char(
|
||||||
|
'n',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
is_recursive: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scope: Some(
|
||||||
|
10252531491715478446,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
denied_commands: {},
|
||||||
|
command_scope: {
|
||||||
|
792017965103506125: ResolvedScope {
|
||||||
|
allow: [
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$TEMP/*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
deny: [],
|
||||||
|
},
|
||||||
|
5856262838373339618: ResolvedScope {
|
||||||
|
allow: [
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$HOME/.config/**",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$RESOURCE/**",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$RESOURCE",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
deny: [
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$RESOURCE/**/*.key",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
10252531491715478446: ResolvedScope {
|
||||||
|
allow: [
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$HOME/.config/**",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$RESOURCE/**",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$RESOURCE",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
deny: [
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$RESOURCE/**/*.key",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
global_scope: {
|
||||||
|
"fs": ResolvedScope {
|
||||||
|
allow: [
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$APP",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$APP/**",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
deny: [
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$HOME",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Map(
|
||||||
|
{
|
||||||
|
"path": String(
|
||||||
|
"$APP/*.db",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -50,7 +50,7 @@ mod tests {
|
|||||||
.expect("required-plugins.json is not a valid JSON");
|
.expect("required-plugins.json is not a valid JSON");
|
||||||
|
|
||||||
let manifests = load_plugins(&fixture_plugins);
|
let manifests = load_plugins(&fixture_plugins);
|
||||||
let capabilities = parse_capabilities(&format!("{}/*.toml", fixture_entry.path().display()))
|
let capabilities = parse_capabilities(&format!("{}/cap*", fixture_entry.path().display()))
|
||||||
.expect("failed to parse capabilities");
|
.expect("failed to parse capabilities");
|
||||||
|
|
||||||
let resolved = Resolved::resolve(manifests, capabilities, Target::current())
|
let resolved = Resolved::resolve(manifests, capabilities, Target::current())
|
||||||
|
Loading…
Reference in New Issue
Block a user