Skip to content

Commit

Permalink
feat: add ScopeObjectMatch trait for easy scope validation (#11132)
Browse files Browse the repository at this point in the history
  • Loading branch information
chippers authored Sep 25, 2024
1 parent 2a654fd commit 5621174
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changes/scope-object-match.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch:feat
---

Add `ScopeObjectMatch` for easy scope validation those that can be represented by a boolean return value.
108 changes: 108 additions & 0 deletions crates/tauri/src/ipc/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,69 @@ impl<T: ScopeObject> CommandScope<T> {
}
}

impl<T: ScopeObjectMatch> CommandScope<T> {
/// Ensure all deny scopes were not matched and any allow scopes were.
///
/// This **WILL** return `true` if the allow scopes are empty and the deny
/// scopes did not trigger. If you require at least one allow scope, then
/// ensure the allow scopes are not empty before calling this method.
///
/// ```
/// # use tauri::ipc::CommandScope;
/// # fn command(scope: CommandScope<()>) -> Result<(), &'static str> {
/// if scope.allows().is_empty() {
/// return Err("you need to specify at least 1 allow scope!");
/// }
/// # Ok(())
/// # }
/// ```
///
/// # Example
///
/// ```
/// # use serde::{Serialize, Deserialize};
/// # use url::Url;
/// # use tauri::{ipc::{CommandScope, ScopeObjectMatch}, command};
/// #
/// #[derive(Debug, Clone, Serialize, Deserialize)]
/// # pub struct Scope;
/// #
/// # impl ScopeObjectMatch for Scope {
/// # type Input = str;
/// #
/// # fn matches(&self, input: &str) -> bool {
/// # true
/// # }
/// # }
/// #
/// # fn do_work(_: String) -> Result<String, &'static str> {
/// # Ok("Output".into())
/// # }
/// #
/// #[command]
/// fn my_command(scope: CommandScope<Scope>, input: String) -> Result<String, &'static str> {
/// if scope.matches(&input) {
/// do_work(input)
/// } else {
/// Err("Scope didn't match input")
/// }
/// }
/// ```
pub fn matches(&self, input: &T::Input) -> bool {
// first make sure the input doesn't match any existing deny scope
if self.deny.iter().any(|s| s.matches(input)) {
return false;
}

// if there are allow scopes, ensure the input matches at least 1
if self.allow.is_empty() {
true
} else {
self.allow.iter().any(|s| s.matches(input))
}
}
}

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> {
Expand Down Expand Up @@ -729,6 +792,51 @@ impl<T: Send + Sync + Debug + DeserializeOwned + 'static> ScopeObject for T {
}
}

/// A [`ScopeObject`] whose validation can be represented as a `bool`.
///
/// # Example
///
/// ```
/// # use serde::{Deserialize, Serialize};
/// # use tauri::{ipc::ScopeObjectMatch, Url};
/// #
/// #[derive(Debug, Clone, Serialize, Deserialize)]
/// #[serde(rename_all = "camelCase")]
/// pub enum Scope {
/// Domain(Url),
/// StartsWith(String),
/// }
///
/// impl ScopeObjectMatch for Scope {
/// type Input = str;
///
/// fn matches(&self, input: &str) -> bool {
/// match self {
/// Scope::Domain(url) => {
/// let parsed: Url = match input.parse() {
/// Ok(parsed) => parsed,
/// Err(_) => return false,
/// };
///
/// let domain = parsed.domain();
///
/// domain.is_some() && domain == url.domain()
/// }
/// Scope::StartsWith(start) => input.starts_with(start),
/// }
/// }
/// }
/// ```
pub trait ScopeObjectMatch: ScopeObject {
/// The type of input expected to validate against the scope.
///
/// This will be borrowed, so if you want to match on a `&str` this type should be `str`.
type Input: ?Sized;

/// Check if the input matches against the scope.
fn matches(&self, input: &Self::Input) -> bool;
}

impl ScopeManager {
pub(crate) fn get_global_scope_typed<R: Runtime, T: ScopeObject>(
&self,
Expand Down
2 changes: 1 addition & 1 deletion crates/tauri/src/ipc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub(crate) mod protocol;

pub use authority::{
CapabilityBuilder, CommandScope, GlobalScope, Origin, RuntimeAuthority, RuntimeCapability,
ScopeObject, ScopeValue,
ScopeObject, ScopeObjectMatch, ScopeValue,
};
pub use channel::{Channel, JavaScriptChannelId};
pub use command::{private, CommandArg, CommandItem};
Expand Down

0 comments on commit 5621174

Please sign in to comment.