refactor: put dynamic acl to a feature (#13418)

* refactor: put dynamic acl to a feature

* Add change file

* Tweak remove_unused_commands's docs

* License header

* Document the feature

* Merge remote-tracking branch 'upstream/dev' into dynamic-acl-feature

* Use a inner non generic fn for add_capability

* Clippy and macro stability notice

* Merge remote-tracking branch 'upstream/dev' into dynamic-acl-feature

* Format
This commit is contained in:
Tony 2025-05-16 22:54:23 +08:00 committed by GitHub
parent 85baacd18b
commit 1686296463
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 274 additions and 253 deletions

View File

@ -0,0 +1,7 @@
---
"tauri": 'minor:changes'
"tauri-codegen": 'minor:changes'
"tauri-utils": 'minor:changes'
---
Put dynamic ACL into a feature `dynamic-acl`, this is currently enabled by default to align with the previous behaviors, you can disable it through `default-features = false` to reduce the final binary size by not including the ACL references

82
Cargo.lock generated
View File

@ -145,15 +145,15 @@ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_log-sys"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
[[package]]
name = "android_logger"
version = "0.14.1"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826"
checksum = "f6f39be698127218cca460cb624878c9aa4e2b47dba3b277963d2bf00bad263b"
dependencies = [
"android_log-sys",
"env_filter",
@ -787,12 +787,6 @@ dependencies = [
"digest",
]
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -847,9 +841,9 @@ checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32"
[[package]]
name = "borsh"
version = "1.5.3"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03"
checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce"
dependencies = [
"borsh-derive",
"cfg_aliases",
@ -857,9 +851,9 @@ dependencies = [
[[package]]
name = "borsh-derive"
version = "1.5.3"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244"
checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3"
dependencies = [
"once_cell",
"proc-macro-crate 3.2.0",
@ -1307,36 +1301,6 @@ dependencies = [
"digest",
]
[[package]]
name = "cocoa"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2"
dependencies = [
"bitflags 2.7.0",
"block",
"cocoa-foundation",
"core-foundation 0.10.0",
"core-graphics",
"foreign-types 0.5.0",
"libc",
"objc",
]
[[package]]
name = "cocoa-foundation"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d"
dependencies = [
"bitflags 2.7.0",
"block",
"core-foundation 0.10.0",
"core-graphics-types",
"libc",
"objc",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@ -4457,15 +4421,6 @@ dependencies = [
"vlq",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "markup5ever"
version = "0.11.0"
@ -5002,15 +4957,6 @@ dependencies = [
"libc",
]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-sys"
version = "0.3.5"
@ -7001,9 +6947,9 @@ dependencies = [
[[package]]
name = "rust_decimal"
version = "1.36.0"
version = "1.37.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555"
checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50"
dependencies = [
"arrayvec",
"borsh",
@ -8728,16 +8674,16 @@ dependencies = [
[[package]]
name = "tauri-plugin-log"
version = "2.2.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd784c138c08a43954bc3e735402e6b2b2ee8d8c254a7391f4e77c01273dd5"
checksum = "8d2b582d860eb214f28323f4ce4f2797ae3b78f197e27b11677f976f9f52aedb"
dependencies = [
"android_logger",
"byte-unit",
"cocoa",
"fern",
"log",
"objc",
"objc2 0.6.0",
"objc2-foundation 0.3.0",
"serde",
"serde_json",
"serde_repr",

View File

@ -1873,7 +1873,7 @@
}
},
"removeUnusedCommands": {
"description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4",
"description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4",
"default": false,
"type": "boolean"
}

View File

@ -60,6 +60,7 @@ pub struct Options {
ios_color: String,
}
#[allow(clippy::large_enum_variant)]
enum Source {
Svg(resvg::usvg::Tree),
DynamicImage(DynamicImage),

View File

@ -1103,7 +1103,7 @@ impl RustAppSettings {
.to_string()
})
});
let target = TargetPlatform::from_triple(&target_triple);
let target_platform = TargetPlatform::from_triple(&target_triple);
Ok(Self {
manifest: Mutex::new(manifest),
@ -1113,7 +1113,7 @@ impl RustAppSettings {
package_settings,
cargo_config,
target_triple,
target_platform: target,
target_platform,
})
}

View File

@ -409,7 +409,8 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
identity,
);
let runtime_authority = quote!(#root::ipc::RuntimeAuthority::new(#acl_tokens, #resolved));
let runtime_authority =
quote!(#root::runtime_acl!(#root::ipc::RuntimeAuthority::new, #acl_tokens, #resolved));
let plugin_global_api_scripts = if config.app.with_global_tauri {
if let Some(scripts) = tauri_utils::plugin::read_global_api_scripts(&out_dir) {

View File

@ -263,10 +263,7 @@ fn assert_command(
let status =
response.map_err(|e| std::io::Error::new(e.kind(), format!("{error_message}: {e}")))?;
if !status.success() {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
error_message,
))
Err(std::io::Error::other(error_message))
} else {
Ok(())
}

View File

@ -18,6 +18,7 @@ use syn::{
};
use tauri_utils::acl::REMOVE_UNUSED_COMMANDS_ENV_VAR;
#[allow(clippy::large_enum_variant)]
enum WrapperAttributeKind {
Meta(Meta),
Async,

View File

@ -972,7 +972,6 @@ impl WindowBuilder for WindowBuilderWrapper {
/// ## Platform-specific
///
/// - **iOS / Android:** Unsupported.
#[must_use]
fn prevent_overflow(mut self) -> Self {
self
.prevent_overflow
@ -986,7 +985,6 @@ impl WindowBuilder for WindowBuilderWrapper {
/// ## Platform-specific
///
/// - **iOS / Android:** Unsupported.
#[must_use]
fn prevent_overflow_with_margin(mut self, margin: Size) -> Self {
self.prevent_overflow.replace(margin);
self

View File

@ -1873,7 +1873,7 @@
}
},
"removeUnusedCommands": {
"description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4",
"description": "Try to remove unused commands registered from plugins base on the ACL list during `tauri build`,\n the way it works is that tauri-cli will read this and set the environment variables for the build script and macros,\n and they'll try to get all the allowed commands and remove the rest\n\n Note:\n - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this\n - This feature requires tauri-plugin 2.1 and tauri 2.4",
"default": false,
"type": "boolean"
}

View File

@ -10,6 +10,7 @@ use crate::platform::Target;
use super::{
capability::{Capability, PermissionEntry},
has_app_manifest,
manifest::Manifest,
Commands, Error, ExecutionContext, Identifier, Permission, PermissionSet, Scopes, Value,
APP_ACL_KEY,
@ -67,6 +68,8 @@ pub struct ResolvedScope {
/// Resolved access control list.
#[derive(Debug, Default)]
pub struct Resolved {
/// If we should check the ACL for the app commands
pub has_app_acl: bool,
/// The commands that are allowed. Map each command with its context to a [`ResolvedCommand`].
pub allowed_commands: BTreeMap<String, Vec<ResolvedCommand>>,
/// The commands that are denied. Map each command with its context to a [`ResolvedCommand`].
@ -182,6 +185,7 @@ impl Resolved {
.collect();
let resolved = Self {
has_app_acl: has_app_manifest(acl),
allowed_commands,
denied_commands,
command_scope,
@ -513,6 +517,8 @@ mod build {
impl ToTokens for Resolved {
fn to_tokens(&self, tokens: &mut TokenStream) {
let has_app_acl = self.has_app_acl;
let allowed_commands = map_lit(
quote! { ::std::collections::BTreeMap },
&self.allowed_commands,
@ -544,6 +550,7 @@ mod build {
literal_struct!(
tokens,
::tauri::utils::acl::resolved::Resolved,
has_app_acl,
allowed_commands,
denied_commands,
command_scope,

View File

@ -2800,7 +2800,7 @@ pub struct BuildConfig {
/// and they'll try to get all the allowed commands and remove the rest
///
/// Note:
/// - This won't be accounting for dynamically added ACLs so make sure to check it when using this
/// - This won't be accounting for dynamically added ACLs when you use features from the `dynamic-acl` (currently enabled by default) feature flag, so make sure to check it when using this
/// - This feature requires tauri-plugin 2.1 and tauri 2.4
#[serde(alias = "remove-unused-commands", default)]
pub remove_unused_commands: bool,

View File

@ -180,7 +180,7 @@ objc2-web-kit = { version = "0.3", features = [
] }
[features]
default = ["wry", "compression", "common-controls-v6"]
default = ["wry", "compression", "common-controls-v6", "dynamic-acl"]
unstable = ["tauri-runtime-wry?/unstable"]
common-controls-v6 = [
"tray-icon?/common-controls-v6",
@ -217,6 +217,7 @@ config-toml = ["tauri-macros/config-toml"]
image-ico = ["image/ico"]
image-png = ["image/png"]
macos-proxy = ["tauri-runtime-wry?/macos-proxy"]
dynamic-acl = []
specta = ["dep:specta"]
[[example]]

View File

@ -7,19 +7,15 @@ use std::fmt::{Debug, Display};
use std::sync::Arc;
use serde::de::DeserializeOwned;
use serde::Serialize;
use tauri_utils::acl::has_app_manifest;
use tauri_utils::acl::{
capability::{Capability, CapabilityFile, PermissionEntry},
manifest::Manifest,
Value, APP_ACL_KEY,
};
#[cfg(feature = "dynamic-acl")]
use tauri_utils::acl::capability::CapabilityFile;
#[cfg(any(feature = "dynamic-acl", debug_assertions))]
use tauri_utils::acl::manifest::Manifest;
use tauri_utils::acl::{
resolved::{Resolved, ResolvedCommand, ResolvedScope, ScopeKey},
ExecutionContext, Scopes,
ExecutionContext, Value, APP_ACL_KEY,
};
use tauri_utils::platform::Target;
use url::Url;
@ -30,7 +26,9 @@ use super::{CommandArg, CommandItem};
/// The runtime authority used to authorize IPC execution based on the Access Control List.
pub struct RuntimeAuthority {
acl: BTreeMap<String, crate::utils::acl::manifest::Manifest>,
#[cfg(any(feature = "dynamic-acl", debug_assertions))]
acl: BTreeMap<String, Manifest>,
has_app_acl: bool,
allowed_commands: BTreeMap<String, Vec<ResolvedCommand>>,
denied_commands: BTreeMap<String, Vec<ResolvedCommand>>,
pub(crate) scope_manager: ScopeManager,
@ -68,174 +66,55 @@ impl Origin {
}
}
/// A capability that can be added at runtime.
pub trait RuntimeCapability {
/// Creates the capability file.
fn build(self) -> CapabilityFile;
/// This is used internally by [`crate::generate_handler!`]
/// to only include the raw ACL when it's needed
///
/// ## Stability
///
/// The output of this macro is managed internally by Tauri,
/// and should not be accessed directly on normal applications.
/// It may have breaking changes in the future.
#[cfg(any(feature = "dynamic-acl", debug_assertions))]
#[doc(hidden)]
#[macro_export]
macro_rules! runtime_acl {
($func:path, $acl:expr, $resolved_acl:expr) => {
$func($acl, $resolved_acl)
};
}
impl<T: AsRef<str>> RuntimeCapability for T {
fn build(self) -> CapabilityFile {
self.as_ref().parse().expect("invalid capability")
}
}
/// A builder for a [`Capability`].
pub struct CapabilityBuilder(Capability);
impl CapabilityBuilder {
/// Creates a new capability builder with a unique identifier.
pub fn new(identifier: impl Into<String>) -> Self {
Self(Capability {
identifier: identifier.into(),
description: "".into(),
remote: None,
local: true,
windows: Vec::new(),
webviews: Vec::new(),
permissions: Vec::new(),
platforms: None,
})
}
/// Allows this capability to be used by a remote URL.
pub fn remote(mut self, url: String) -> Self {
self
.0
.remote
.get_or_insert_with(Default::default)
.urls
.push(url);
self
}
/// Whether this capability is applied on local app URLs or not. Defaults to `true`.
pub fn local(mut self, local: bool) -> Self {
self.0.local = local;
self
}
/// Link this capability to the given window label.
pub fn window(mut self, window: impl Into<String>) -> Self {
self.0.windows.push(window.into());
self
}
/// Link this capability to the a list of window labels.
pub fn windows(mut self, windows: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.0.windows.extend(windows.into_iter().map(|w| w.into()));
self
}
/// Link this capability to the given webview label.
pub fn webview(mut self, webview: impl Into<String>) -> Self {
self.0.webviews.push(webview.into());
self
}
/// Link this capability to the a list of window labels.
pub fn webviews(mut self, webviews: impl IntoIterator<Item = impl Into<String>>) -> Self {
self
.0
.webviews
.extend(webviews.into_iter().map(|w| w.into()));
self
}
/// Add a new permission to this capability.
pub fn permission(mut self, permission: impl Into<String>) -> Self {
let permission = permission.into();
self.0.permissions.push(PermissionEntry::PermissionRef(
permission
.clone()
.try_into()
.unwrap_or_else(|_| panic!("invalid permission identifier '{permission}'")),
));
self
}
/// Add a new scoped permission to this capability.
pub fn permission_scoped<T: Serialize>(
mut self,
permission: impl Into<String>,
allowed: Vec<T>,
denied: Vec<T>,
) -> Self {
let permission = permission.into();
let identifier = permission
.clone()
.try_into()
.unwrap_or_else(|_| panic!("invalid permission identifier '{permission}'"));
let allowed_scope = allowed
.into_iter()
.map(|a| {
serde_json::to_value(a)
.expect("failed to serialize scope")
.into()
})
.collect();
let denied_scope = denied
.into_iter()
.map(|a| {
serde_json::to_value(a)
.expect("failed to serialize scope")
.into()
})
.collect();
let scope = Scopes {
allow: Some(allowed_scope),
deny: Some(denied_scope),
};
self
.0
.permissions
.push(PermissionEntry::ExtendedPermission { identifier, scope });
self
}
/// Adds a target platform for this capability.
///
/// By default all platforms are applied.
pub fn platform(mut self, platform: Target) -> Self {
self
.0
.platforms
.get_or_insert_with(Default::default)
.push(platform);
self
}
/// Adds target platforms for this capability.
///
/// By default all platforms are applied.
pub fn platforms(mut self, platforms: impl IntoIterator<Item = Target>) -> Self {
self
.0
.platforms
.get_or_insert_with(Default::default)
.extend(platforms);
self
}
}
impl RuntimeCapability for CapabilityBuilder {
fn build(self) -> CapabilityFile {
CapabilityFile::Capability(self.0)
}
/// This is used internally by [`crate::generate_handler!`]
/// to only include the raw ACL when it's needed
///
/// ## Stability
///
/// The output of this macro is managed internally by Tauri,
/// and should not be accessed directly on normal applications.
/// It may have breaking changes in the future.
#[cfg(not(any(feature = "dynamic-acl", debug_assertions)))]
#[doc(hidden)]
#[macro_export]
macro_rules! runtime_acl {
($func:path, $_acl:expr, $resolved_acl:expr) => {
$func($resolved_acl)
};
}
impl RuntimeAuthority {
#[doc(hidden)]
pub fn new(acl: BTreeMap<String, Manifest>, resolved_acl: Resolved) -> Self {
pub fn new(
#[cfg(any(feature = "dynamic-acl", debug_assertions))] acl: BTreeMap<String, Manifest>,
resolved_acl: Resolved,
) -> Self {
let command_cache = resolved_acl
.command_scope
.keys()
.map(|key| (*key, StateManager::new()))
.collect();
Self {
#[cfg(any(feature = "dynamic-acl", debug_assertions))]
acl,
has_app_acl: resolved_acl.has_app_acl,
allowed_commands: resolved_acl.allowed_commands,
denied_commands: resolved_acl.denied_commands,
scope_manager: ScopeManager {
@ -248,7 +127,7 @@ impl RuntimeAuthority {
}
pub(crate) fn has_app_manifest(&self) -> bool {
has_app_manifest(&self.acl)
self.has_app_acl
}
#[doc(hidden)]
@ -264,9 +143,15 @@ impl RuntimeAuthority {
}
/// Adds the given capability to the runtime authority.
pub fn add_capability(&mut self, capability: impl RuntimeCapability) -> crate::Result<()> {
#[cfg(feature = "dynamic-acl")]
pub fn add_capability(&mut self, capability: impl super::RuntimeCapability) -> crate::Result<()> {
self.add_capability_inner(capability.build())
}
#[cfg(feature = "dynamic-acl")]
fn add_capability_inner(&mut self, capability: CapabilityFile) -> crate::Result<()> {
let mut capabilities = BTreeMap::new();
match capability.build() {
match capability {
CapabilityFile::Capability(c) => {
capabilities.insert(c.identifier.clone(), c);
}
@ -407,7 +292,7 @@ impl RuntimeAuthority {
}
fn has_permissions_allowing_command(
manifest: &crate::utils::acl::manifest::Manifest,
manifest: &Manifest,
set: &crate::utils::acl::PermissionSet,
command: &str,
) -> bool {

View File

@ -0,0 +1,170 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use serde::Serialize;
use tauri_utils::{
acl::{
capability::{Capability, CapabilityFile, PermissionEntry},
Scopes,
},
platform::Target,
};
/// A capability that can be added at runtime.
pub trait RuntimeCapability {
/// Creates the capability file.
fn build(self) -> CapabilityFile;
}
impl<T: AsRef<str>> RuntimeCapability for T {
fn build(self) -> CapabilityFile {
self.as_ref().parse().expect("invalid capability")
}
}
/// A builder for a [`Capability`].
pub struct CapabilityBuilder(Capability);
impl CapabilityBuilder {
/// Creates a new capability builder with a unique identifier.
pub fn new(identifier: impl Into<String>) -> Self {
Self(Capability {
identifier: identifier.into(),
description: "".into(),
remote: None,
local: true,
windows: Vec::new(),
webviews: Vec::new(),
permissions: Vec::new(),
platforms: None,
})
}
/// Allows this capability to be used by a remote URL.
pub fn remote(mut self, url: String) -> Self {
self
.0
.remote
.get_or_insert_with(Default::default)
.urls
.push(url);
self
}
/// Whether this capability is applied on local app URLs or not. Defaults to `true`.
pub fn local(mut self, local: bool) -> Self {
self.0.local = local;
self
}
/// Link this capability to the given window label.
pub fn window(mut self, window: impl Into<String>) -> Self {
self.0.windows.push(window.into());
self
}
/// Link this capability to the a list of window labels.
pub fn windows(mut self, windows: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.0.windows.extend(windows.into_iter().map(|w| w.into()));
self
}
/// Link this capability to the given webview label.
pub fn webview(mut self, webview: impl Into<String>) -> Self {
self.0.webviews.push(webview.into());
self
}
/// Link this capability to the a list of window labels.
pub fn webviews(mut self, webviews: impl IntoIterator<Item = impl Into<String>>) -> Self {
self
.0
.webviews
.extend(webviews.into_iter().map(|w| w.into()));
self
}
/// Add a new permission to this capability.
pub fn permission(mut self, permission: impl Into<String>) -> Self {
let permission = permission.into();
self.0.permissions.push(PermissionEntry::PermissionRef(
permission
.clone()
.try_into()
.unwrap_or_else(|_| panic!("invalid permission identifier '{permission}'")),
));
self
}
/// Add a new scoped permission to this capability.
pub fn permission_scoped<T: Serialize>(
mut self,
permission: impl Into<String>,
allowed: Vec<T>,
denied: Vec<T>,
) -> Self {
let permission = permission.into();
let identifier = permission
.clone()
.try_into()
.unwrap_or_else(|_| panic!("invalid permission identifier '{permission}'"));
let allowed_scope = allowed
.into_iter()
.map(|a| {
serde_json::to_value(a)
.expect("failed to serialize scope")
.into()
})
.collect();
let denied_scope = denied
.into_iter()
.map(|a| {
serde_json::to_value(a)
.expect("failed to serialize scope")
.into()
})
.collect();
let scope = Scopes {
allow: Some(allowed_scope),
deny: Some(denied_scope),
};
self
.0
.permissions
.push(PermissionEntry::ExtendedPermission { identifier, scope });
self
}
/// Adds a target platform for this capability.
///
/// By default all platforms are applied.
pub fn platform(mut self, platform: Target) -> Self {
self
.0
.platforms
.get_or_insert_with(Default::default)
.push(platform);
self
}
/// Adds target platforms for this capability.
///
/// By default all platforms are applied.
pub fn platforms(mut self, platforms: impl IntoIterator<Item = Target>) -> Self {
self
.0
.platforms
.get_or_insert_with(Default::default)
.extend(platforms);
self
}
}
impl RuntimeCapability for CapabilityBuilder {
fn build(self) -> CapabilityFile {
CapabilityFile::Capability(self.0)
}
}

View File

@ -22,15 +22,18 @@ use tauri_utils::acl::resolved::ResolvedCommand;
use crate::{webview::Webview, Runtime, StateManager};
mod authority;
#[cfg(feature = "dynamic-acl")]
mod capability_builder;
pub(crate) mod channel;
mod command;
pub(crate) mod format_callback;
pub(crate) mod protocol;
pub use authority::{
CapabilityBuilder, CommandScope, GlobalScope, Origin, RuntimeAuthority, RuntimeCapability,
ScopeObject, ScopeObjectMatch, ScopeValue,
CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeObject, ScopeObjectMatch, ScopeValue,
};
#[cfg(feature = "dynamic-acl")]
pub use capability_builder::{CapabilityBuilder, RuntimeCapability};
pub use channel::{Channel, JavaScriptChannelId};
pub use command::{private, CommandArg, CommandItem};

View File

@ -35,6 +35,7 @@
//! - **image-png**: Adds support to parse `.png` image, see [`Image`].
//! - **macos-proxy**: Adds support for [`WebviewBuilder::proxy_url`] on macOS. Requires macOS 14+.
//! - **specta**: Add support for [`specta::specta`](https://docs.rs/specta/%5E2.0.0-rc.9/specta/attr.specta.html) with Tauri arguments such as [`State`](crate::State), [`Window`](crate::Window) and [`AppHandle`](crate::AppHandle)
//! - **dynamic-acl** *(enabled by default)*: Enables you to add ACLs at runtime, notably it enables the [`Manager::add_capability`] function.
//!
//! ## Cargo allowlist features
//!
@ -64,7 +65,9 @@ macro_rules! ios_plugin_binding {
#[doc(hidden)]
pub use embed_plist;
pub use error::{Error, Result};
use ipc::{RuntimeAuthority, RuntimeCapability};
use ipc::RuntimeAuthority;
#[cfg(feature = "dynamic-acl")]
use ipc::RuntimeCapability;
pub use resources::{Resource, ResourceId, ResourceTable};
#[cfg(target_os = "ios")]
#[doc(hidden)]
@ -820,6 +823,7 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
///
/// [`tauri.conf.json > app > security > capabilities`]: https://tauri.app/reference/config/#capabilities
/// [tauri_build::Attributes::capabilities_path_pattern]: https://docs.rs/tauri-build/2/tauri_build/struct.Attributes.html#method.capabilities_path_pattern
#[cfg(feature = "dynamic-acl")]
fn add_capability(&self, capability: impl RuntimeCapability) -> Result<()> {
self
.manager()