mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-02-06 11:41:56 +00:00
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:
parent
85baacd18b
commit
1686296463
7
.changes/dynamic-acl-feature.md
Normal file
7
.changes/dynamic-acl-feature.md
Normal 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
82
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -60,6 +60,7 @@ pub struct Options {
|
||||
ios_color: String,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Source {
|
||||
Svg(resvg::usvg::Tree),
|
||||
DynamicImage(DynamicImage),
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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]]
|
||||
|
||||
@ -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 {
|
||||
|
||||
170
crates/tauri/src/ipc/capability_builder.rs
Normal file
170
crates/tauri/src/ipc/capability_builder.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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};
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user