feat(core): dynamic runtime capability (#9036)

* feat(core): dynamic runtime capability

* local(), windows(), webviews()

* enhance identation
This commit is contained in:
Lucas Fernandes Nogueira 2024-03-01 08:27:29 -03:00 committed by GitHub
parent a77be97474
commit 03098b5315
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 157 additions and 15 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch:enhance
---
`Manager::add_capability` now allows adding a dynamically defined capability instead of only relying on static strings.

View File

@ -7,15 +7,18 @@ use std::fmt::{Debug, Display};
use std::sync::Arc;
use serde::de::DeserializeOwned;
use serde::Serialize;
use state::TypeMap;
use tauri_utils::acl::capability::CapabilityFile;
use tauri_utils::acl::manifest::Manifest;
use tauri_utils::acl::{
capability::{Capability, CapabilityFile, PermissionEntry},
manifest::Manifest,
Value, APP_ACL_KEY,
};
use tauri_utils::acl::{
resolved::{CommandKey, Resolved, ResolvedCommand, ResolvedScope, ScopeKey},
ExecutionContext,
ExecutionContext, Scopes,
};
use tauri_utils::acl::{Value, APP_ACL_KEY};
use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
use crate::{AppHandle, Manager};
@ -62,6 +65,140 @@ impl Origin {
}
}
/// 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: Vec::new(),
})
}
/// 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
}
}
impl RuntimeCapability for CapabilityBuilder {
fn build(self) -> CapabilityFile {
CapabilityFile::Capability(self.0)
}
}
impl RuntimeAuthority {
#[doc(hidden)]
pub fn new(acl: BTreeMap<String, Manifest>, resolved_acl: Resolved) -> Self {
@ -102,9 +239,9 @@ impl RuntimeAuthority {
}
/// Adds the given capability to the runtime authority.
pub fn add_capability(&mut self, capability: CapabilityFile) -> crate::Result<()> {
pub fn add_capability(&mut self, capability: impl RuntimeCapability) -> crate::Result<()> {
let mut capabilities = BTreeMap::new();
match capability {
match capability.build() {
CapabilityFile::Capability(c) => {
capabilities.insert(c.identifier.clone(), c);
}

View File

@ -24,7 +24,10 @@ mod command;
pub(crate) mod format_callback;
pub(crate) mod protocol;
pub use authority::{CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeObject, ScopeValue};
pub use authority::{
CapabilityBuilder, CommandScope, GlobalScope, Origin, RuntimeAuthority, RuntimeCapability,
ScopeObject, ScopeValue,
};
pub use channel::{Channel, JavaScriptChannelId};
pub use command::{private, CommandArg, CommandItem};

View File

@ -69,7 +69,7 @@ pub use cocoa;
#[doc(hidden)]
pub use embed_plist;
pub use error::{Error, Result};
use ipc::RuntimeAuthority;
use ipc::{RuntimeAuthority, RuntimeCapability};
pub use resources::{Resource, ResourceId, ResourceTable};
#[cfg(target_os = "ios")]
#[doc(hidden)]
@ -888,13 +888,13 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
/// Ok(())
/// });
/// ```
fn add_capability(&self, capability: &'static str) -> Result<()> {
fn add_capability(&self, capability: impl RuntimeCapability) -> Result<()> {
self
.manager()
.runtime_authority
.lock()
.unwrap()
.add_capability(capability.parse().expect("invalid capability"))
.add_capability(capability)
}
}

View File

@ -22,10 +22,7 @@ use tauri_runtime::{
window::dpi::{PhysicalPosition, PhysicalSize, Position, Size},
WindowDispatch,
};
use tauri_utils::{
acl::APP_ACL_KEY,
config::{WebviewUrl, WindowConfig},
};
use tauri_utils::config::{WebviewUrl, WindowConfig};
pub use url::Url;
use crate::{
@ -1192,7 +1189,7 @@ fn main() {
{
let (key, command_name) = plugin_command
.clone()
.unwrap_or_else(|| (APP_ACL_KEY, request.cmd.clone()));
.unwrap_or_else(|| (tauri_utils::acl::APP_ACL_KEY, request.cmd.clone()));
invoke.resolver.reject(
manager
.runtime_authority