Skip to main content

tauri_plugin_biometry/
scope.rs

1//! Command-scope allowlist for the storage commands (`has_data`, `get_data`,
2//! `set_data`, `remove_data`).
3//!
4//! Each capability that grants one of these permissions can constrain the
5//! `domain` / `name` pairs the granted webview is allowed to touch:
6//!
7//! ```json
8//! {
9//!   "identifier": "biometry:allow-get-data",
10//!   "allow": [
11//!     { "domain": "com.myapp.creds" },
12//!     { "domain": "com.myapp.tokens", "name": "session-token" }
13//!   ],
14//!   "deny": [
15//!     { "domain": "com.myapp.creds", "name": "master-key" }
16//!   ]
17//! }
18//! ```
19//!
20//! Semantics:
21//! - An entry with `name` omitted matches **any** name in that domain.
22//! - `deny` is evaluated first and beats `allow`.
23//! - An empty `allow` list rejects every call — apps must opt in to the
24//!   domains they actually use. This is the intentional secure default.
25
26use serde::{Deserialize, Serialize};
27use tauri::ipc::CommandScope;
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Entry {
31    /// Domain this entry matches.
32    pub domain: String,
33    /// Optional exact-match name. Omit to match every name in `domain`.
34    #[serde(default)]
35    pub name: Option<String>,
36}
37
38impl Entry {
39    fn matches(&self, domain: &str, name: &str) -> bool {
40        self.domain == domain && self.name.as_deref().map_or(true, |n| n == name)
41    }
42}
43
44/// Checks `(domain, name)` against the scope merged into the calling
45/// webview's capability set. Returns `Err` if denied or not explicitly
46/// allowed.
47pub fn check(scope: &CommandScope<Entry>, domain: &str, name: &str) -> crate::Result<()> {
48    if scope.denies().iter().any(|e| e.matches(domain, name)) {
49        return Err(reject(domain, name, "denied by capability scope"));
50    }
51    if scope.allows().iter().any(|e| e.matches(domain, name)) {
52        return Ok(());
53    }
54    Err(reject(
55        domain,
56        name,
57        "not in capability allow-list — declare the (domain, name) in the capability's `allow` array",
58    ))
59}
60
61fn reject(domain: &str, name: &str, why: &str) -> crate::Error {
62    // Cross-platform path: scope.rs is shared with mobile, where
63    // `crate::error::PluginInvokeError` doesn't exist. The unified
64    // `crate::Error::Io` variant works everywhere, and the `scopeDenied:`
65    // prefix preserves the distinguishable error code in the message.
66    crate::Error::Io(std::io::Error::other(format!(
67        "scopeDenied: biometry ({domain}, {name}) {why}"
68    )))
69}