feat(core): add Manager::add_capability, closes #8799 (#8806)

* refactor(core): capabilities must be referenced on the Tauri config file

* add all capabilities by default

* feat(codegen): allow defining additional capabilities, closes #8798

* undo example

* lint

* move add_capability to runtime authority

* feat(core): add Manager::add_capability, closes #8799

* add change file
This commit is contained in:
Lucas Fernandes Nogueira 2024-02-19 11:59:20 -03:00 committed by GitHub
parent f284f9c545
commit 258494bd24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 98 additions and 55 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch:breaking
---
Removed the lifetime parameter from `ipc::GlobalScope` and `ipc::CommandScope`.

View File

@ -0,0 +1,5 @@
---
"tauri": patch:enhance
---
Added `Manager::add_capability` to add a capability file at runtime.

View File

@ -338,6 +338,7 @@ struct ResolvedCommandTemp {
pub scope: Vec<ScopeKey>,
pub resolved_scope_key: Option<ScopeKey>,
}
fn resolve_command(
commands: &mut BTreeMap<CommandKey, ResolvedCommandTemp>,
command: String,

View File

@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::collections::BTreeMap;
use std::fmt::{Debug, Display};
use std::{collections::BTreeMap, ops::Deref};
use std::sync::Arc;
use serde::de::DeserializeOwned;
use state::TypeMap;
@ -335,11 +336,18 @@ impl RuntimeAuthority {
/// List of allowed and denied objects that match either the command-specific or plugin global scope criterias.
#[derive(Debug)]
pub struct ScopeValue<T: ScopeObject> {
allow: Vec<T>,
deny: Vec<T>,
allow: Arc<Vec<T>>,
deny: Arc<Vec<T>>,
}
impl<T: ScopeObject> ScopeValue<T> {
fn clone(&self) -> Self {
Self {
allow: self.allow.clone(),
deny: self.deny.clone(),
}
}
/// What this access scope allows.
pub fn allows(&self) -> &Vec<T> {
&self.allow
@ -351,27 +359,11 @@ impl<T: ScopeObject> ScopeValue<T> {
}
}
#[derive(Debug)]
enum OwnedOrRef<'a, T: Debug> {
Owned(T),
Ref(&'a T),
}
impl<'a, T: Debug> Deref for OwnedOrRef<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Self::Owned(t) => t,
Self::Ref(r) => r,
}
}
}
/// Access scope for a command that can be retrieved directly in the command function.
#[derive(Debug)]
pub struct CommandScope<'a, T: ScopeObject>(OwnedOrRef<'a, ScopeValue<T>>);
pub struct CommandScope<T: ScopeObject>(ScopeValue<T>);
impl<'a, T: ScopeObject> CommandScope<'a, T> {
impl<T: ScopeObject> CommandScope<T> {
/// What this access scope allows.
pub fn allows(&self) -> &Vec<T> {
&self.0.allow
@ -383,33 +375,35 @@ impl<'a, T: ScopeObject> CommandScope<'a, T> {
}
}
impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<'a, T> {
impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<T> {
/// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`CommandScope`].
fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
if let Some(scope_id) = command.acl.as_ref().and_then(|resolved| resolved.scope) {
Ok(CommandScope(OwnedOrRef::Ref(
Ok(CommandScope(
command
.message
.webview
.manager()
.runtime_authority
.lock()
.unwrap()
.scope_manager
.get_command_scope_typed(command.message.webview.app_handle(), &scope_id)?,
)))
))
} else {
Ok(CommandScope(OwnedOrRef::Owned(ScopeValue {
allow: Vec::new(),
deny: Vec::new(),
})))
Ok(CommandScope(ScopeValue {
allow: Default::default(),
deny: Default::default(),
}))
}
}
}
/// Global access scope that can be retrieved directly in the command function.
#[derive(Debug)]
pub struct GlobalScope<'a, T: ScopeObject>(&'a ScopeValue<T>);
pub struct GlobalScope<T: ScopeObject>(ScopeValue<T>);
impl<'a, T: ScopeObject> GlobalScope<'a, T> {
impl<T: ScopeObject> GlobalScope<T> {
/// What this access scope allows.
pub fn allows(&self) -> &Vec<T> {
&self.0.allow
@ -421,7 +415,7 @@ impl<'a, T: ScopeObject> GlobalScope<'a, T> {
}
}
impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<'a, T> {
impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<T> {
/// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`].
fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
command
@ -437,6 +431,8 @@ impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<'a, T> {
.webview
.manager()
.runtime_authority
.lock()
.unwrap()
.scope_manager
.get_global_scope_typed(command.message.webview.app_handle(), plugin)
.map_err(InvokeError::from_error)
@ -476,9 +472,9 @@ impl ScopeManager {
&self,
app: &AppHandle<R>,
plugin: &str,
) -> crate::Result<&ScopeValue<T>> {
match self.global_scope_cache.try_get() {
Some(cached) => Ok(cached),
) -> crate::Result<ScopeValue<T>> {
match self.global_scope_cache.try_get::<ScopeValue<T>>() {
Some(cached) => Ok(cached.clone()),
None => {
let mut allow: Vec<T> = Vec::new();
let mut deny: Vec<T> = Vec::new();
@ -498,9 +494,12 @@ impl ScopeManager {
}
}
let scope = ScopeValue { allow, deny };
let _ = self.global_scope_cache.set(scope);
Ok(self.global_scope_cache.get())
let scope = ScopeValue {
allow: Arc::new(allow),
deny: Arc::new(deny),
};
self.global_scope_cache.set(scope.clone());
Ok(scope)
}
}
}
@ -509,10 +508,10 @@ impl ScopeManager {
&self,
app: &AppHandle<R>,
key: &ScopeKey,
) -> crate::Result<&ScopeValue<T>> {
) -> crate::Result<ScopeValue<T>> {
let cache = self.command_cache.get(key).unwrap();
match cache.try_get() {
Some(cached) => Ok(cached),
match cache.try_get::<ScopeValue<T>>() {
Some(cached) => Ok(cached.clone()),
None => {
let resolved_scope = self
.command_scope
@ -535,10 +534,13 @@ impl ScopeManager {
);
}
let value = ScopeValue { allow, deny };
let value = ScopeValue {
allow: Arc::new(allow),
deny: Arc::new(deny),
};
let _ = cache.set(value);
Ok(cache.get())
let _ = cache.set(value.clone());
Ok(value)
}
}
}

View File

@ -966,6 +966,28 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
fn path(&self) -> &crate::path::PathResolver<R> {
self.state::<crate::path::PathResolver<R>>().inner()
}
/// Adds a capability to the app.
///
/// # Examples
/// ```
/// use tauri::Manager;
///
/// tauri::Builder::default()
/// .setup(|app| {
/// #[cfg(feature = "beta")]
/// app.add_capability(include_str!("../capabilities/beta.json"));
/// Ok(())
/// });
/// ```
fn add_capability(&self, capability: &'static str) -> Result<()> {
self
.manager()
.runtime_authority
.lock()
.unwrap()
.add_capability(capability.parse().expect("invalid capability"))
}
}
/// Prevent implementation details from leaking out of the [`Manager`] trait.

View File

@ -175,7 +175,7 @@ pub struct Asset {
#[default_runtime(crate::Wry, wry)]
pub struct AppManager<R: Runtime> {
pub runtime_authority: RuntimeAuthority,
pub runtime_authority: Mutex<RuntimeAuthority>,
pub window: window::WindowManager<R>,
pub webview: webview::WebviewManager<R>,
#[cfg(all(desktop, feature = "tray-icon"))]
@ -245,7 +245,7 @@ impl<R: Runtime> AppManager<R> {
}
Self {
runtime_authority: context.runtime_authority,
runtime_authority: Mutex::new(context.runtime_authority),
window: window::WindowManager {
windows: Mutex::default(),
default_icon: context.default_window_icon,

View File

@ -139,11 +139,13 @@ impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
}
/// Gets the global scope defined on the permissions that are part of the app ACL.
pub fn scope<T: ScopeObject>(&self) -> crate::Result<&ScopeValue<T>> {
pub fn scope<T: ScopeObject>(&self) -> crate::Result<ScopeValue<T>> {
self
.handle
.manager
.runtime_authority
.lock()
.unwrap()
.scope_manager
.get_global_scope_typed(&self.handle, self.name)
}

View File

@ -1118,6 +1118,8 @@ fn main() {
};
let resolved_acl = manager
.runtime_authority
.lock()
.unwrap()
.resolve_access(
&request.cmd,
message.webview.label(),
@ -1142,15 +1144,19 @@ fn main() {
if request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND && invoke.acl.is_none() {
#[cfg(debug_assertions)]
{
invoke
.resolver
.reject(manager.runtime_authority.resolve_access_message(
plugin,
&command_name,
invoke.message.webview.window().label(),
invoke.message.webview.label(),
&acl_origin,
));
invoke.resolver.reject(
manager
.runtime_authority
.lock()
.unwrap()
.resolve_access_message(
plugin,
&command_name,
invoke.message.webview.window().label(),
invoke.message.webview.label(),
&acl_origin,
),
);
}
#[cfg(not(debug_assertions))]
invoke