Skip to content

Commit

Permalink
feat(core): add Manager::add_capability, closes #8799 (#8806)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
lucasfernog committed Feb 19, 2024
1 parent f284f9c commit 258494b
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 55 deletions.
5 changes: 5 additions & 0 deletions .changes/acl-scope-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:breaking
---

Removed the lifetime parameter from `ipc::GlobalScope` and `ipc::CommandScope`.
5 changes: 5 additions & 0 deletions .changes/runtime-add-capability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:enhance
---

Added `Manager::add_capability` to add a capability file at runtime.
1 change: 1 addition & 0 deletions core/tauri-utils/src/acl/resolved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
88 changes: 45 additions & 43 deletions core/tauri/src/ipc/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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();
Expand All @@ -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)
}
}
}
Expand All @@ -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
Expand All @@ -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)
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions core/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions core/tauri/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion core/tauri/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
24 changes: 15 additions & 9 deletions core/tauri/src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,8 @@ fn main() {
};
let resolved_acl = manager
.runtime_authority
.lock()
.unwrap()
.resolve_access(
&request.cmd,
message.webview.label(),
Expand All @@ -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
Expand Down

0 comments on commit 258494b

Please sign in to comment.