Skip to content

Commit

Permalink
feat(core): add WebviewWindow::resolve_command_scope (#11439)
Browse files Browse the repository at this point in the history
* feat(core): add WebviewWindow::resolve_command_scope

This new functionality exposes the `CommandScope` resolution as a function (currently only commands can resolve them as a dependency injection via CommandItem)

This function is useful to validate the configuration at runtime (do some asserts at setup phase to ensure capabilities are properly configured) and to resolve scopes in a separate thread or context

* adjust return type
  • Loading branch information
lucasfernog authored Oct 21, 2024
1 parent a5bf48e commit f0da0bd
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changes/resolve_command_scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:feat
---

Added `WebviewWindow::resolve_command_scope` to check a command scope at runtime.
2 changes: 1 addition & 1 deletion crates/tauri-cli/src/mobile/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ fn get_str_array(helper: &Helper, formatter: impl Fn(&str) -> String) -> Option<
.map(|val| {
val.as_str().map(
#[allow(clippy::redundant_closure)]
|s| formatter(s),
&formatter,
)
})
.collect()
Expand Down
83 changes: 45 additions & 38 deletions crates/tauri/src/ipc/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use tauri_utils::platform::Target;
use url::Url;

use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
use crate::{AppHandle, Manager, StateManager};
use crate::{AppHandle, Manager, StateManager, Webview};

use super::{CommandArg, CommandItem};

Expand Down Expand Up @@ -614,6 +614,33 @@ pub struct CommandScope<T: ScopeObject> {
}

impl<T: ScopeObject> CommandScope<T> {
pub(crate) fn resolve<R: Runtime>(
webview: &Webview<R>,
scope_ids: Vec<u64>,
) -> crate::Result<Self> {
let mut allow = Vec::new();
let mut deny = Vec::new();

for scope_id in scope_ids {
let scope = webview
.manager()
.runtime_authority
.lock()
.unwrap()
.scope_manager
.get_command_scope_typed::<R, T>(webview.app_handle(), &scope_id)?;

for s in scope.allows() {
allow.push(s.clone());
}
for s in scope.denies() {
deny.push(s.clone());
}
}

Ok(CommandScope { allow, deny })
}

/// What this access scope allows.
pub fn allows(&self) -> &Vec<Arc<T>> {
&self.allow
Expand Down Expand Up @@ -698,29 +725,7 @@ impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<T> {
.collect::<Vec<_>>()
});
if let Some(scope_ids) = scope_ids {
let mut allow = Vec::new();
let mut deny = Vec::new();

for scope_id in scope_ids {
let scope = command
.message
.webview
.manager()
.runtime_authority
.lock()
.unwrap()
.scope_manager
.get_command_scope_typed::<R, T>(command.message.webview.app_handle(), &scope_id)?;

for s in scope.allows() {
allow.push(s.clone());
}
for s in scope.denies() {
deny.push(s.clone());
}
}

Ok(CommandScope { allow, deny })
CommandScope::resolve(&command.message.webview, scope_ids).map_err(Into::into)
} else {
Ok(CommandScope {
allow: Default::default(),
Expand All @@ -735,6 +740,17 @@ impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<T> {
pub struct GlobalScope<T: ScopeObject>(ScopeValue<T>);

impl<T: ScopeObject> GlobalScope<T> {
pub(crate) fn resolve<R: Runtime>(webview: &Webview<R>, plugin: &str) -> crate::Result<Self> {
webview
.manager()
.runtime_authority
.lock()
.unwrap()
.scope_manager
.get_global_scope_typed(webview.app_handle(), plugin)
.map(Self)
}

/// What this access scope allows.
pub fn allows(&self) -> &Vec<Arc<T>> {
&self.0.allow
Expand All @@ -749,20 +765,11 @@ impl<T: ScopeObject> GlobalScope<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
.message
.webview
.manager()
.runtime_authority
.lock()
.unwrap()
.scope_manager
.get_global_scope_typed(
command.message.webview.app_handle(),
command.plugin.unwrap_or(APP_ACL_KEY),
)
.map_err(InvokeError::from_error)
.map(GlobalScope)
GlobalScope::resolve(
&command.message.webview,
command.plugin.unwrap_or(APP_ACL_KEY),
)
.map_err(InvokeError::from_error)
}
}

Expand Down
99 changes: 97 additions & 2 deletions crates/tauri/src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ use crate::{
app::{UriSchemeResponder, WebviewEvent},
event::{EmitArgs, EventTarget},
ipc::{
CallbackFn, CommandArg, CommandItem, Invoke, InvokeBody, InvokeError, InvokeMessage,
InvokeResolver, Origin, OwnedInvokeResponder,
CallbackFn, CommandArg, CommandItem, CommandScope, GlobalScope, Invoke, InvokeBody,
InvokeError, InvokeMessage, InvokeResolver, Origin, OwnedInvokeResponder, ScopeObject,
},
manager::AppManager,
sealed::{ManagerBase, RuntimeOrDispatch},
Expand Down Expand Up @@ -880,6 +880,83 @@ impl<R: Runtime> Webview<R> {
.dispatcher
.on_webview_event(move |event| f(&event.clone().into()));
}

/// Resolves the given command scope for this webview on the currently loaded URL.
///
/// If the command is not allowed, returns None.
///
/// If the scope cannot be deserialized to the given type, an error is returned.
///
/// In a command context this can be directly resolved from the command arguments via [CommandScope]:
///
/// ```
/// use tauri::ipc::CommandScope;
///
/// #[derive(Debug, serde::Deserialize)]
/// struct ScopeType {
/// some_value: String,
/// }
/// #[tauri::command]
/// fn my_command(scope: CommandScope<ScopeType>) {
/// // check scope
/// }
/// ```
///
/// # Examples
///
/// ```
/// use tauri::Manager;
///
/// #[derive(Debug, serde::Deserialize)]
/// struct ScopeType {
/// some_value: String,
/// }
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let webview = app.get_webview_window("main").unwrap();
/// let scope = webview.resolve_command_scope::<ScopeType>("my-plugin", "read");
/// Ok(())
/// });
/// ```
pub fn resolve_command_scope<T: ScopeObject>(
&self,
plugin: &str,
command: &str,
) -> crate::Result<Option<ResolvedScope<T>>> {
let current_url = self.url()?;
let is_local = self.is_local_url(&current_url);
let origin = if is_local {
Origin::Local
} else {
Origin::Remote { url: current_url }
};

let cmd_name = format!("plugin:{plugin}|{command}");
let resolved_access = self
.manager()
.runtime_authority
.lock()
.unwrap()
.resolve_access(&cmd_name, self.window().label(), self.label(), &origin);

if let Some(access) = resolved_access {
let scope_ids = access
.iter()
.filter_map(|cmd| cmd.scope_id)
.collect::<Vec<_>>();

let command_scope = CommandScope::resolve(self, scope_ids)?;
let global_scope = GlobalScope::resolve(self, plugin)?;

Ok(Some(ResolvedScope {
global_scope,
command_scope,
}))
} else {
Ok(None)
}
}
}

/// Desktop webview setters and actions.
Expand Down Expand Up @@ -1702,6 +1779,24 @@ impl<'de, R: Runtime> CommandArg<'de, R> for Webview<R> {
}
}

/// Resolved scope that can be obtained via [`Webview::resolve_command_scope`].
pub struct ResolvedScope<T: ScopeObject> {
command_scope: CommandScope<T>,
global_scope: GlobalScope<T>,
}

impl<T: ScopeObject> ResolvedScope<T> {
/// The global plugin scope.
pub fn global_scope(&self) -> &GlobalScope<T> {
&self.global_scope
}

/// The command-specific scope.
pub fn command_scope(&self) -> &CommandScope<T> {
&self.command_scope
}
}

#[cfg(test)]
mod tests {
#[test]
Expand Down
51 changes: 49 additions & 2 deletions crates/tauri/src/webview/webview_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::{

use crate::{
event::EventTarget,
ipc::ScopeObject,
runtime::dpi::{PhysicalPosition, PhysicalSize},
window::Monitor,
Emitter, Listener, ResourceTable, Window,
Expand Down Expand Up @@ -48,7 +49,7 @@ use tauri_macros::default_runtime;
#[cfg(windows)]
use windows::Win32::Foundation::HWND;

use super::DownloadEvent;
use super::{DownloadEvent, ResolvedScope};

/// A builder for [`WebviewWindow`], a window that hosts a single webview.
pub struct WebviewWindowBuilder<'a, R: Runtime, M: Manager<R>> {
Expand Down Expand Up @@ -989,6 +990,52 @@ impl<R: Runtime> WebviewWindow<R> {
pub fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {
self.window.on_window_event(f);
}

/// Resolves the given command scope for this webview on the currently loaded URL.
///
/// If the command is not allowed, returns None.
///
/// If the scope cannot be deserialized to the given type, an error is returned.
///
/// In a command context this can be directly resolved from the command arguments via [crate::ipc::CommandScope]:
///
/// ```
/// use tauri::ipc::CommandScope;
///
/// #[derive(Debug, serde::Deserialize)]
/// struct ScopeType {
/// some_value: String,
/// }
/// #[tauri::command]
/// fn my_command(scope: CommandScope<ScopeType>) {
/// // check scope
/// }
/// ```
///
/// # Examples
///
/// ```
/// use tauri::Manager;
///
/// #[derive(Debug, serde::Deserialize)]
/// struct ScopeType {
/// some_value: String,
/// }
///
/// tauri::Builder::default()
/// .setup(|app| {
/// let webview = app.get_webview_window("main").unwrap();
/// let scope = webview.resolve_command_scope::<ScopeType>("my-plugin", "read");
/// Ok(())
/// });
/// ```
pub fn resolve_command_scope<T: ScopeObject>(
&self,
plugin: &str,
command: &str,
) -> crate::Result<Option<ResolvedScope<T>>> {
self.webview.resolve_command_scope(plugin, command)
}
}

/// Menu APIs
Expand Down Expand Up @@ -1038,7 +1085,7 @@ impl<R: Runtime> WebviewWindow<R> {
self.window.on_menu_event(f)
}

/// Returns this window menu .
/// Returns this window menu.
pub fn menu(&self) -> Option<Menu<R>> {
self.window.menu()
}
Expand Down
4 changes: 2 additions & 2 deletions examples/api/src-tauri/tauri-plugin-sample/src/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ pub struct Sample<R: Runtime>(AppHandle<R>);

impl<R: Runtime> Sample<R> {
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
let _ = payload.on_event.send(Event {
payload.on_event.send(Event {
kind: "ping".to_string(),
value: payload.value.clone(),
});
})?;
Ok(PingResponse {
value: payload.value,
})
Expand Down
2 changes: 2 additions & 0 deletions examples/api/src-tauri/tauri-plugin-sample/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub enum Error {
#[cfg(mobile)]
#[error(transparent)]
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
#[error(transparent)]
Tauri(#[from] tauri::Error),
}

pub type Result<T> = std::result::Result<T, Error>;

0 comments on commit f0da0bd

Please sign in to comment.