mcp_server_sqlite/access_control.rs
1//! Rule-based access control for the MCP SQLite server.
2//!
3//! This module implements the permission system that decides which SQL
4//! operations the server may execute. Permissions are configured at startup
5//! through the `--allow` and `--deny` CLI flags, each taking an *access control
6//! selector* that identifies the operation — and optionally the exact resources
7//! — the rule applies to.
8//!
9//! # Selector syntax
10//!
11//! Every SQLite authorization action (`Read`, `Insert`, `CreateTable`,
12//! `DropIndex`, `Function`, etc.) has a corresponding selector type. A selector
13//! is written as:
14//!
15//! - `Action` — matches **all** operations of that type (every field defaults
16//! to the wildcard `*`).
17//! - `Action(value)` — pins the first field to a specific value; remaining
18//! fields stay as wildcards.
19//! - `Action(value1.value2)` — pins both fields.
20//! - `Action(*.value2)` — wildcard first field, pinned second field.
21//!
22//! For example, the `Read` selector has two fields (`table_name` and
23//! `column_name`):
24//!
25//! | CLI flag | Meaning |
26//! |--------------------------------|--------------------------------|
27//! | `--allow Read` | allow all column reads |
28//! | `--deny Read(Secrets)` | deny reads on `Secrets` |
29//! | `--allow Read(Secrets.id)` | allow reads on `Secrets.id` |
30//! | `--deny Read(*.ssn)` | deny reads on any `ssn` column |
31//!
32//! # Specificity
33//!
34//! Each selector has a *specificity* equal to the number of its fields that are
35//! pinned to concrete values (i.e. not `*`). A bare `Read` has specificity 0,
36//! `Read(Students)` has specificity 1, and `Read(Students.name)` has
37//! specificity 2. More specific rules always outrank less specific ones.
38//!
39//! # Resolution algorithm
40//!
41//! When SQLite asks "is this operation allowed?", the [`AuthorizationResolver`]
42//! evaluates rules as follows:
43//!
44//! 1. **Collect matching rules.** For each configured rule whose selector
45//! covers the requested operation (globs match anything, values must match
46//! exactly), note its specificity.
47//!
48//! 2. **Most specific wins.** Rules are evaluated from the highest specificity
49//! level to the lowest. The first level that contains at least one matching
50//! rule determines the outcome.
51//!
52//! 3. **Deny breaks ties.** If both allow *and* deny rules match at the same
53//! specificity level, the result is **deny** (fail-closed).
54//!
55//! 4. **Default fallback.** If no rule matches at any level, the per-action
56//! default is used. The default depends on how the resolver was constructed
57//! (e.g. [`AuthorizationResolver::new_allow_everything`] ).
58//!
59//! One sentence summary: **the most specific matching rule wins; ties go to
60//! deny; no match defers to the default.**
61//!
62//! # Examples
63//!
64//! ## Read-only server with one table blocked
65//!
66//! ```text
67//! --allow Read --deny Read(Secrets)
68//! ```
69//!
70//! All column reads are allowed except anything touching the `Secrets` table.
71//! `Read(Secrets)` has specificity 1 which beats the blanket `Read` at
72//! specificity 0, so the deny wins for any column in `Secrets`.
73//!
74//! ## Read-only with a carve-out
75//!
76//! ```text
77//! --allow Read --deny Read(Secrets) --allow Read(Secrets.id)
78//! ```
79//!
80//! All reads are allowed. Reads on `Secrets` are denied — except for the `id`
81//! column, which is explicitly re-allowed at specificity 2 (both fields
82//! pinned), beating the specificity-1 deny.
83//!
84//! ## Conflicting rules at the same specificity
85//!
86//! ```text
87//! --deny Read(Students) --allow Read(*.name)
88//! ```
89//!
90//! A read on `Students.name` matches both rules. `Read(Students)` has
91//! specificity 1 (table pinned, column glob) and `Read(*.name)` also has
92//! specificity 1 (table glob, column pinned). Because both match at the same
93//! level, **deny wins**. To allow `Students.name`, add a more specific rule:
94//! `--allow Read(Students.name)` (specificity 2).
95//!
96//! ## Deny everything, allow specific functions
97//!
98//! ```text
99//! --deny Function --allow Function(count) --allow Function(sum)
100//! ```
101//!
102//! All SQL function calls are denied except `count` and `sum`, which win at
103//! specificity 1 over the blanket deny at specificity 0.
104//!
105//! ## Lock down DDL while permitting reads and inserts
106//!
107//! ```text
108//! --allow Read --allow Insert --allow Select --allow Transaction
109//! ```
110//!
111//! With a deny-everything default, only the explicitly allowed actions proceed.
112//! All DDL (`CreateTable`, `DropIndex`, etc.) remains denied because no allow
113//! rule exists for those actions.
114//!
115//! # Code generation
116//!
117//! The `define_access_control_selector_types!` macro generates the selector
118//! structs, the [`AccessControlSelector`] enum, the
119//! [`AccessControlSelectorSet`], and the [`AuthorizationResolver`] from a set
120//! of struct declarations. Each struct's field types are wrapped in
121//! [`ValueOrGlob`] so that every field can independently be a concrete value or
122//! a wildcard. The macro also generates `Display` / `FromStr` round-tripping,
123//! builder methods, and the specificity-aware resolution logic described above.
124
125use std::{error::Error, fmt::Display, str::FromStr};
126
127/// Counts the number of identifier tokens passed to it at compile time.
128///
129/// This is used internally by `define_access_control_selector_types!` to
130/// determine `FIELD_COUNT` for each generated selector struct, which in turn
131/// drives field-count validation during parsing.
132///
133/// # Examples
134///
135/// ```ignore
136/// count_idents!(a, b, c) // expands to 3
137/// count_idents!() // expands to 0
138/// ```
139macro_rules! count_idents {
140 ($ident: ident, $($rest_idents: ident),* $(,)?) => {
141 1 + count_idents!($($rest_idents),*)
142 };
143 ($ident: ident) => {
144 1
145 };
146 () => {
147 0
148 };
149}
150
151/// Builds a `format!`-compatible template string with `{}` placeholders joined
152/// by dots, matching the dot-separated field syntax used in selector strings.
153///
154/// For a selector struct with fields `table_name` and `column_name`, this
155/// produces `"{}.{}"`, which is interpolated inside `format!` to render
156/// something like `Students.name`.
157///
158/// # Examples
159///
160/// ```ignore
161/// formatting_string_literal!(a, b) // expands to "{}.{}"
162/// formatting_string_literal!(a) // expands to "{}"
163/// formatting_string_literal!() // expands to ""
164/// ```
165macro_rules! formatting_string_literal {
166 ($ident: ident, $($rest_idents: ident),* $(,)?) => {
167 concat!("{}.", formatting_string_literal!($($rest_idents),*))
168 };
169 ($ident: ident) => {
170 "{}"
171 };
172 () => {
173 ""
174 };
175}
176
177/// Generates access control selector structs, an umbrella
178/// [`AccessControlSelector`] enum, and all associated trait implementations
179/// from a set of struct declarations.
180///
181/// Each struct declaration passed to this macro produces:
182///
183/// - A public struct whose field types are each wrapped in [`ValueOrGlob<T>`],
184/// so every field can independently hold a concrete value or a wildcard glob.
185/// - A variant on [`AccessControlSelector`] named `{Struct}` that wraps the
186/// generated struct.
187/// - `Display` formatting that round-trips with the CLI string syntax: bare
188/// identifier when all fields are globs (e.g., `Read`), or identifier with
189/// parenthesized dot-separated fields otherwise (e.g.,
190/// `Read(Students.name)`).
191/// - `FromStr` parsing that accepts the same syntax and validates field counts
192/// and selector identifiers.
193/// - `From<T> for String` and `TryFrom<String> for T` conversions.
194/// - Builder methods: `new(...)`, `empty()`, and `with_{field}(...)` for
195/// ergonomic construction.
196/// - Inspection methods: `is_all_glob`, `is_all_value`, `is_any_glob`,
197/// `is_any_value`.
198///
199/// Doc comments (`#[doc = "..."]`) and other attributes placed on the struct or
200/// its fields in the macro invocation are forwarded to both the generated
201/// struct and the corresponding enum variant via the `$(#[$struct_meta])*` and
202/// `$(#[$field_meta])*` captures.
203///
204/// # Usage
205///
206/// ```ignore
207/// define_access_control_selector_types! {
208/// /// Controls read access at the table and column level.
209/// pub struct Read {
210/// /// The table targeted by this selector.
211/// pub table_name: String,
212/// /// The column targeted by this selector.
213/// pub column_name: String,
214/// }
215/// }
216/// ```
217macro_rules! define_access_control_selector_types {
218 (
219 $(
220 $(#[$struct_meta: meta])*
221 $struct_vis: vis struct $struct_ident: ident {
222 $(
223 $(#[$field_meta: meta])*
224 $field_vis: vis $field_ident: ident: $field_ty: ty
225 ),* $(,)?
226 }
227 )*
228 ) => {
229 paste::paste! {
230 /// Resolves incoming SQLite authorization requests against a set of
231 /// configured allow/deny rules and per-action default permissions.
232 ///
233 /// The resolver holds an [`AccessControlSelectorSet`] containing
234 /// all user-supplied rules, plus a default
235 /// [`rusqlite::hooks::Authorization`] for each action type that is
236 /// used when no rule matches. Call [`authorization`] with an
237 /// [`AuthContext`] to obtain the final verdict.
238 ///
239 /// [`AuthContext`]: rusqlite::hooks::AuthContext
240 ///
241 /// [`authorization`]: AuthorizationResolver::authorization
242 pub struct AuthorizationResolver {
243 /// The set of allow/deny rules to evaluate.
244 pub selector_set: AccessControlSelectorSet,
245 $(
246 /// The fallback authorization returned when no rule in the
247 /// selector set matches this action type.
248 pub [< $struct_ident:snake _default_permissions >]: rusqlite::hooks::Authorization,
249 )*
250 }
251
252 const _: () = {
253 impl AuthorizationResolver {
254 /// Creates a resolver that permits every action by default.
255 /// Rules added to the selector set can then selectively
256 /// deny specific operations.
257 pub fn new_allow_everything() -> Self {
258 Self {
259 selector_set: Default::default(),
260 $(
261 [< $struct_ident:snake _default_permissions >]: rusqlite::hooks::Authorization::Allow,
262 )*
263 }
264 }
265
266 /// Creates a resolver that denies every action by default.
267 /// Rules added to the selector set can then selectively
268 /// allow specific operations.
269 pub fn new_deny_everything() -> Self {
270 Self {
271 selector_set: Default::default(),
272 $(
273 [< $struct_ident:snake _default_permissions >]: rusqlite::hooks::Authorization::Deny,
274 )*
275 }
276 }
277
278 pub fn with_selector(mut self, selector: impl Into<AccessControlSelector>, allow: bool) -> Self {
279 self.selector_set = self.selector_set.with_selector(selector, allow);
280 self
281 }
282
283 $(
284 #[doc = concat!(
285 "Sets the default authorization returned for [`",
286 stringify!($struct_ident),
287 "`] actions when no rule matches."
288 )]
289 pub const fn [< with_ $struct_ident:snake _default_permissions >](
290 mut self,
291 default_permissions: rusqlite::hooks::Authorization
292 ) -> Self {
293 self.[< $struct_ident:snake _default_permissions >] = default_permissions;
294 self
295 }
296 )*
297
298 /// Evaluates the configured rules against an incoming
299 /// SQLite authorization request. Dispatches to the
300 /// appropriate type-specific check on the selector set and
301 /// falls back to the per-action default when no rule
302 /// matches. Unrecognized actions are denied
303 /// unconditionally.
304 pub fn authorization(
305 &self,
306 ctx: rusqlite::hooks::AuthContext<'_>
307 ) -> rusqlite::hooks::Authorization {
308 match ctx.action {
309 $(
310 rusqlite::hooks::AuthAction::$struct_ident { $($field_ident,)* .. } => {
311 self
312 .selector_set
313 .[< check_ $struct_ident: snake >]($(&$field_ident),*)
314 .map(Into::into)
315 .unwrap_or(self.[< $struct_ident:snake _default_permissions >])
316 }
317 )*,
318 _ => rusqlite::hooks::Authorization::Deny
319 }
320 }
321 }
322 };
323
324 /// Holds all configured access control rules, grouped by selector
325 /// type and indexed by specificity.
326 ///
327 /// Each selector type gets its own [`BTreeMap`] keyed by the
328 /// selector's [`specificity`] value (number of concrete
329 /// [`Value`](ValueOrGlob::Value) fields). This layout enables the
330 /// policy resolver to iterate from most-specific to least-specific
331 /// rules efficiently: the highest-specificity match wins, with deny
332 /// breaking ties.
333 ///
334 /// [`BTreeMap`]: std::collections::BTreeMap
335 /// [`specificity`]: #method.specificity
336 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
337 pub struct AccessControlSelectorSet {
338 $(
339 $(#[$struct_meta])*
340 pub [<$struct_ident: snake _selectors>]: std::collections::BTreeMap<usize, Vec<Permission<$struct_ident>>>,
341 )*
342 }
343
344 const _: () = {
345 impl AccessControlSelectorSet {
346 /// Creates an empty [`AccessControlSelectorSet`] with no
347 /// rules.
348 pub fn new() -> Self {
349 Default::default()
350 }
351
352 /// Adds a rule by converting any selector type into an
353 /// [`AccessControlSelector`] and dispatching it to the
354 /// appropriate type-specific builder. `allow` controls
355 /// whether the rule permits or denies the matched
356 /// operation.
357 pub fn with_selector(
358 self,
359 selector: impl Into<AccessControlSelector>,
360 allow: bool
361 ) -> Self {
362 let selector = selector.into();
363 match selector {
364 $(
365 AccessControlSelector::$struct_ident(selector)
366 => self.[<with_ $struct_ident: snake _selector>](
367 selector,
368 allow
369 )
370 ),*
371 }
372 }
373
374 $(
375 #[doc = concat!(
376 "Adds a [`", stringify!($struct_ident),
377 "`] rule, inserting it into the ",
378 "bucket matching its specificity."
379 )]
380 pub fn [<with_ $struct_ident: snake _selector>](
381 mut self,
382 selector: $struct_ident,
383 allow: bool
384 ) -> Self {
385 self.[<$struct_ident: snake _selectors>]
386 .entry(selector.specificity())
387 .or_default()
388 .push(Permission {
389 selector,
390 allow
391 });
392 self
393 }
394
395 #[doc = concat!(
396 "Resolves the configured [`",
397 stringify!($struct_ident),
398 "`] rules against the given field values. ",
399 "Iterates from highest to lowest specificity; ",
400 "at each level, deny wins if both allow and deny ",
401 "match. Returns `None` when no rule matches."
402 )]
403 #[allow(clippy::ptr_arg)]
404 pub fn [< check_ $struct_ident: snake >](&self, $($field_ident: &(impl PartialEq<$field_ty> + ?Sized)),*) -> Option<Authorization> {
405 for permissions in self.[<$struct_ident: snake _selectors>].values().rev() {
406 let mut allow_encountered = false;
407 let mut deny_encountered = false;
408 let authorizations = permissions.iter().filter_map(|permission| permission.check($($field_ident),*));
409 for authorization in authorizations {
410 match authorization {
411 Authorization::Allow => {
412 allow_encountered = true
413 }
414 Authorization::Deny => {
415 deny_encountered = true
416 }
417 }
418 if allow_encountered == true && deny_encountered == true {
419 return Some(Authorization::Deny)
420 }
421 }
422 match (allow_encountered, deny_encountered) {
423 (false, false) => {
424 continue
425 },
426 (true, false) => {
427 return Some(Authorization::Allow)
428 },
429 (_, true) => {
430 return Some(Authorization::Deny)
431 },
432 }
433 }
434 None
435 }
436 )*
437 }
438 };
439
440 /// Umbrella enum unifying all access control selector types into a
441 /// single type for use with the CLI argument parser.
442 ///
443 /// Each variant wraps a specific selector struct generated by
444 /// `define_access_control_selector_types!`. Parsing a selector
445 /// string through this enum automatically dispatches to the correct
446 /// variant based on the leading identifier.
447 #[allow(clippy::enum_variant_names)]
448 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
449 pub enum AccessControlSelector {
450 $(
451 $(#[$struct_meta])*
452 $struct_ident($struct_ident)
453 ),*
454 }
455
456 const _: () = {
457 use core::fmt::{Display, Formatter, Result as FmtResult};
458
459 use $crate::access_control::AccessControlSelectorParseError;
460 use $crate::access_control::AccessControlSelectorParseError::*;
461
462 impl AccessControlSelector {
463 $(
464 #[doc = concat!(
465 "Returns `true` if this is the [`",
466 stringify!($struct_ident),
467 "`] variant."
468 )]
469 pub const fn [<is_ $struct_ident: snake>](&self) -> bool {
470 matches!(self, Self::$struct_ident(..))
471 }
472
473 #[doc = concat!(
474 "Returns a reference to the inner [`",
475 stringify!($struct_ident),
476 "`] if this is that variant, ",
477 "or `None` otherwise."
478 )]
479 pub const fn [<as_ $struct_ident: snake>](&self) -> Option<&$struct_ident> {
480 if let Self::$struct_ident(value) = self {
481 Some(value)
482 } else {
483 None
484 }
485 }
486
487 #[doc = concat!(
488 "Consumes `self` and returns the ",
489 "inner [`",
490 stringify!($struct_ident),
491 "`] if this is that variant, ",
492 "or `None` otherwise."
493 )]
494 pub fn [<into_ $struct_ident: snake>](self) -> Option<$struct_ident> {
495 if let Self::$struct_ident(value) = self {
496 Some(value)
497 } else {
498 None
499 }
500 }
501 )*
502 }
503
504 /// Delegates to the wrapped selector variant's [`Display`]
505 /// implementation, producing the CLI selector syntax.
506 impl Display for AccessControlSelector {
507 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
508 match self {
509 $(
510 Self::$struct_ident(selector) => Display::fmt(selector, f),
511 )*
512 }
513 }
514 }
515
516 /// Converts an [`AccessControlSelector`] into its string
517 /// representation via [`Display`].
518 impl From<AccessControlSelector> for String {
519 fn from(v: AccessControlSelector) -> Self {
520 v.to_string()
521 }
522 }
523
524 /// Parses a selector string by extracting the leading
525 /// identifier and dispatching to the corresponding variant's
526 /// [`FromStr`] implementation.
527 ///
528 /// # Errors
529 ///
530 /// Returns
531 /// [`AccessControlSelectorParseError::InvalidAccessControlSelectorIdentifier`]
532 /// when the leading identifier does not match any known
533 /// selector variant, and propagates any error from the
534 /// variant-level parser.
535 impl FromStr for AccessControlSelector {
536 type Err = AccessControlSelectorParseError;
537
538 fn from_str(s: &str) -> Result<Self, Self::Err> {
539 let selector = s
540 .split("(")
541 .next()
542 .ok_or_else(|| InvalidAccessControlSelectorIdentifier {
543 found: None,
544 expected: &[$(stringify!($struct_ident)),*],
545 access_control_selector_string: s.into(),
546 })?;
547
548 match selector {
549 $(
550 stringify!($struct_ident) => s.parse().map(Self::$struct_ident),
551 )*
552 other => Err(InvalidAccessControlSelectorIdentifier {
553 found: Some(other.into()),
554 expected: &[$(stringify!($struct_ident)),*],
555 access_control_selector_string: s.into(),
556 })
557 }
558 }
559 }
560
561 /// Parses an owned `String` into an [`AccessControlSelector`]
562 /// by delegating to [`FromStr`].
563 impl TryFrom<String> for AccessControlSelector {
564 type Error = AccessControlSelectorParseError;
565
566 fn try_from(v: String) -> Result<Self, Self::Error> {
567 v.parse()
568 }
569 }
570
571 $(
572 #[doc = concat!(
573 "Converts a [`",
574 stringify!($struct_ident),
575 "`] into its corresponding [`",
576 "AccessControlSelector`] variant."
577 )]
578 impl From<$struct_ident> for AccessControlSelector {
579 fn from(value: $struct_ident) -> Self {
580 Self::$struct_ident(value)
581 }
582 }
583 )*
584 };
585
586 $(
587 $(#[$struct_meta])*
588 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
589 $struct_vis struct $struct_ident {
590 $(
591 $(#[$field_meta])*
592 $field_vis $field_ident: ValueOrGlob<$field_ty>
593 ),*
594 }
595
596 const _: () = {
597 use core::fmt::{Display, Formatter, Result as FmtResult};
598 use core::convert::*;
599 use core::str::*;
600
601 use $crate::access_control::{AccessControlSelectorParseError};
602 use $crate::access_control::AccessControlSelectorParseError::*;
603
604 impl $struct_ident {
605 #[doc = concat!(
606 "The number of fields declared on [`",
607 stringify!($struct_ident),
608 "`], used for field-count validation during parsing."
609 )]
610 const FIELD_COUNT: usize = count_idents!($($field_ident),*);
611
612 #[doc = concat!(
613 "Creates a new [`", stringify!($struct_ident),
614 "`] with the provided field values."
615 )]
616 #[doc = ""]
617 #[doc = concat!(
618 "Each argument accepts any type that implements ",
619 "`Into<ValueOrGlob<T>>`, so callers can pass a raw ",
620 "value, a [`ValueOrGlob`], or an `Option<T>`."
621 )]
622 pub fn new(
623 $(
624 $field_ident: impl Into<ValueOrGlob<$field_ty>>
625 ),*
626 ) -> Self {
627 Self {
628 $(
629 $field_ident: $field_ident.into()
630 ),*
631 }
632 }
633
634 #[doc = concat!(
635 "Creates a [`", stringify!($struct_ident),
636 "`] where every field is a [`Glob`](ValueOrGlob::Glob), ",
637 "matching all possible values."
638 )]
639 pub fn empty() -> Self {
640 Self {
641 $(
642 $field_ident: Default::default()
643 ),*
644 }
645 }
646
647 $(
648 #[doc = concat!(
649 "Sets [`", stringify!($struct_ident),
650 "::", stringify!($field_ident),
651 "`] and returns `self` for method chaining."
652 )]
653 #[doc = ""]
654 $(#[$field_meta])*
655 pub fn [< with_ $field_ident >](mut self, value: impl Into<ValueOrGlob<$field_ty>>) -> Self {
656 self.$field_ident = value.into();
657 self
658 }
659 )*
660
661 #[doc = concat!(
662 "Returns `true` if every field on this [`",
663 stringify!($struct_ident),
664 "`] is a [`Glob`](ValueOrGlob::Glob)."
665 )]
666 pub const fn is_all_glob(&self) -> bool {
667 if Self::FIELD_COUNT != 0 {
668 true $(&& ValueOrGlob::is_glob(&self.$field_ident))*
669 } else {
670 false
671 }
672 }
673
674 #[doc = concat!(
675 "Returns `true` if every field on this [`",
676 stringify!($struct_ident),
677 "`] is a [`Value`](ValueOrGlob::Value)."
678 )]
679 pub const fn is_all_value(&self) -> bool {
680 if Self::FIELD_COUNT != 0 {
681 true $(&& ValueOrGlob::is_value(&self.$field_ident))*
682 } else {
683 false
684 }
685 }
686
687 #[doc = concat!(
688 "Returns `true` if at least one field on this [`",
689 stringify!($struct_ident),
690 "`] is a [`Glob`](ValueOrGlob::Glob)."
691 )]
692 pub const fn is_any_glob(&self) -> bool {
693 if Self::FIELD_COUNT != 0 {
694 false $(|| ValueOrGlob::is_glob(&self.$field_ident))*
695 } else {
696 false
697 }
698 }
699
700 #[doc = concat!(
701 "Returns `true` if at least one field on this [`",
702 stringify!($struct_ident),
703 "`] is a [`Value`](ValueOrGlob::Value)."
704 )]
705 pub const fn is_any_value(&self) -> bool {
706 if Self::FIELD_COUNT != 0 {
707 false $(|| ValueOrGlob::is_value(&self.$field_ident))*
708 } else {
709 false
710 }
711 }
712
713 #[doc = concat!(
714 "Returns the number of ",
715 "[`Value`](ValueOrGlob::Value) ",
716 "fields on this [`",
717 stringify!($struct_ident),
718 "`], used to rank competing ",
719 "policy rules during resolution. ",
720 "A higher specificity means the ",
721 "rule targets a narrower set of ",
722 "operations."
723 )]
724 #[allow(unused_mut)]
725 pub const fn specificity(&self) -> usize {
726 let mut specificity = 0;
727
728 $(
729 if self.$field_ident.is_value() {
730 specificity += 1
731 }
732 )*
733
734 specificity
735 }
736
737 #[doc = concat!(
738 "Returns `true` if this selector ",
739 "covers the given field values. ",
740 "A [`Glob`](ValueOrGlob::Glob) ",
741 "field matches any value; a ",
742 "[`Value`](ValueOrGlob::Value) ",
743 "field matches only when equal."
744 )]
745 #[allow(clippy::ptr_arg)]
746 pub fn matches(&self, $($field_ident: &(impl PartialEq<$field_ty> + ?Sized)),*) -> bool {
747 true $(&& (self.$field_ident.is_glob() || self.$field_ident.as_value().is_some_and(|field_value| PartialEq::<$field_ty>::eq($field_ident, field_value))))*
748 }
749 }
750
751 #[allow(clippy::ptr_arg)]
752 impl Permission<$struct_ident> {
753 #[doc = concat!(
754 "If the inner [`",
755 stringify!($struct_ident),
756 "`] selector matches the given ",
757 "fields, returns the ",
758 "[`Authorization`] implied by ",
759 "this permission's `allow` flag. ",
760 "Returns `None` on no match."
761 )]
762 pub fn check(&self, $($field_ident: &(impl PartialEq<$field_ty> + ?Sized)),*) -> Option<Authorization> {
763 self.selector.matches($($field_ident),*).then(|| match self.allow {
764 true => Authorization::Allow,
765 false => Authorization::Deny,
766 })
767 }
768 }
769
770 #[doc = concat!(
771 "Defaults to [`", stringify!($struct_ident),
772 "::empty`], producing a selector where every field ",
773 "is a glob."
774 )]
775 impl Default for $struct_ident {
776 fn default() -> Self {
777 Self::empty()
778 }
779 }
780
781 #[doc = concat!(
782 "Formats this [`", stringify!($struct_ident),
783 "`] using the CLI selector syntax. When all fields ",
784 "are globs, renders the bare identifier `",
785 stringify!($struct_ident),
786 "`; otherwise renders the identifier with ",
787 "parenthesized dot-separated fields."
788 )]
789 impl Display for $struct_ident {
790 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
791 if Self::FIELD_COUNT == 0 || self.is_all_glob() {
792 core::write!(f, stringify!($struct_ident))
793 } else {
794 core::write!(
795 f,
796 concat!(
797 stringify!($struct_ident),
798 "(",
799 formatting_string_literal!($($field_ident),*),
800 ")"
801 ),
802 $(self.$field_ident),*
803 )
804 }
805 }
806 }
807
808 #[doc = concat!(
809 "Converts a [`", stringify!($struct_ident),
810 "`] into its string representation via [`Display`]."
811 )]
812 impl From<$struct_ident> for String {
813 fn from(value: $struct_ident) -> Self {
814 value.to_string()
815 }
816 }
817
818 #[doc = concat!(
819 "Parses a selector string into a [`",
820 stringify!($struct_ident),
821 "`]. Accepts the syntax `",
822 stringify!($struct_ident),
823 "` or `", stringify!($struct_ident),
824 "(field1.field2)`."
825 )]
826 #[allow(unused_mut, unused_variables)]
827 impl FromStr for $struct_ident {
828 type Err = AccessControlSelectorParseError;
829
830 fn from_str(s: &str) -> Result<Self, Self::Err> {
831 let parsed_access_control_selector = ParsedAccessControlSelector::<'_, {Self::FIELD_COUNT}>::new(s)?;
832
833 if parsed_access_control_selector.access_control_selector_ident != stringify!($struct_ident) {
834 return Err(IncorrectAccessControlSelectorIdentifier {
835 expected: stringify!($struct_ident).into(),
836 found: parsed_access_control_selector.access_control_selector_ident.into(),
837 access_control_selector_string: s.into()
838 })
839 }
840
841 let mut access_control_selector = Self::default();
842 let mut fields = parsed_access_control_selector.fields.into_iter().flatten().map(str::trim);
843 $(
844 let field = fields.next();
845 if let Some(field) = field {
846 let field_value = ValueOrGlob::<$field_ty>::from_str(field).map_err(|err| FailedToParseFieldValue {
847 field_ident: stringify!($field_ident).into(),
848 field_value: field.into(),
849 error: Box::new(err) as _
850 })?;
851 access_control_selector = access_control_selector.[< with_ $field_ident >](field_value)
852 }
853 )*
854
855 Ok(access_control_selector)
856 }
857 }
858
859 #[doc = concat!(
860 "Parses an owned `String` into a [`",
861 stringify!($struct_ident),
862 "`] by delegating to [`FromStr`]."
863 )]
864 impl TryFrom<String> for $struct_ident {
865 type Error = AccessControlSelectorParseError;
866
867 fn try_from(v: String) -> Result<Self, Self::Error> {
868 v.parse()
869 }
870 }
871 };
872 )*
873 }
874 };
875}
876
877define_access_control_selector_types! {
878 /// Selector for `CREATE INDEX` statements, scoped to a table and index
879 /// name.
880 ///
881 /// # String syntax examples
882 ///
883 /// - `CreateIndex` -- any index on any table.
884 /// - `CreateIndex(Students)` -- any index on `Students`.
885 /// - `CreateIndex(Students.idx_email)` -- one specific index.
886 pub struct CreateIndex {
887 /// The table the index is created on, or `*` for any.
888 pub table_name: String,
889 /// The name of the index being created, or `*` for any.
890 pub index_name: String,
891 }
892
893 /// Selector for `CREATE TABLE` statements.
894 ///
895 /// # String syntax examples
896 ///
897 /// - `CreateTable` -- any table.
898 /// - `CreateTable(Students)` -- only `Students`.
899 pub struct CreateTable {
900 /// The name of the table being created, or `*` for any.
901 pub table_name: String,
902 }
903
904 /// Selector for `CREATE TEMP INDEX` statements, scoped to a table and index
905 /// name.
906 ///
907 /// # String syntax examples
908 ///
909 /// - `CreateTempIndex` -- any temporary index on any table.
910 /// - `CreateTempIndex(Sessions)` -- temp indexes on `Sessions`.
911 pub struct CreateTempIndex {
912 /// The table the temp index is created on, or `*` for any.
913 pub table_name: String,
914 /// The name of the temp index being created, or `*` for any.
915 pub index_name: String,
916 }
917
918 /// Selector for `CREATE TEMP TABLE` statements.
919 ///
920 /// # String syntax examples
921 ///
922 /// - `CreateTempTable` -- any temporary table.
923 /// - `CreateTempTable(scratch)` -- only `scratch`.
924 pub struct CreateTempTable {
925 /// The name of the temp table being created, or `*` for any.
926 pub table_name: String,
927 }
928
929 /// Selector for `CREATE TEMP TRIGGER` statements, scoped to a table and
930 /// trigger name.
931 ///
932 /// # String syntax examples
933 ///
934 /// - `CreateTempTrigger` -- any temp trigger on any table.
935 /// - `CreateTempTrigger(Orders.trg_audit)` -- one specific trigger.
936 pub struct CreateTempTrigger {
937 /// The table the temp trigger fires on, or `*` for any.
938 pub table_name: String,
939 /// The name of the temp trigger, or `*` for any.
940 pub trigger_name: String,
941 }
942
943 /// Selector for `CREATE TEMP VIEW` statements.
944 ///
945 /// # String syntax examples
946 ///
947 /// - `CreateTempView` -- any temporary view.
948 /// - `CreateTempView(recent_orders)` -- only `recent_orders`.
949 pub struct CreateTempView {
950 /// The name of the temp view being created, or `*` for any.
951 pub view_name: String,
952 }
953
954 /// Selector for `CREATE TRIGGER` statements, scoped to a table and trigger
955 /// name.
956 ///
957 /// # String syntax examples
958 ///
959 /// - `CreateTrigger` -- any trigger on any table.
960 /// - `CreateTrigger(Orders.trg_audit)` -- one specific trigger.
961 pub struct CreateTrigger {
962 /// The table the trigger fires on, or `*` for any.
963 pub table_name: String,
964 /// The name of the trigger being created, or `*` for any.
965 pub trigger_name: String,
966 }
967
968 /// Selector for `CREATE VIEW` statements.
969 ///
970 /// # String syntax examples
971 ///
972 /// - `CreateView` -- any view.
973 /// - `CreateView(active_users)` -- only `active_users`.
974 pub struct CreateView {
975 /// The name of the view being created, or `*` for any.
976 pub view_name: String,
977 }
978
979 /// Selector for `DELETE` statements, scoped to a table.
980 ///
981 /// # String syntax examples
982 ///
983 /// - `Delete` -- delete from any table.
984 /// - `Delete(Sessions)` -- only from `Sessions`.
985 pub struct Delete {
986 /// The table being deleted from, or `*` for any.
987 pub table_name: String,
988 }
989
990 /// Selector for `DROP INDEX` statements, scoped to a table and index name.
991 ///
992 /// # String syntax examples
993 ///
994 /// - `DropIndex` -- any index on any table.
995 /// - `DropIndex(Students.idx_email)` -- one specific index.
996 pub struct DropIndex {
997 /// The table the index belongs to, or `*` for any.
998 pub table_name: String,
999 /// The name of the index being dropped, or `*` for any.
1000 pub index_name: String,
1001 }
1002
1003 /// Selector for `DROP TABLE` statements.
1004 ///
1005 /// # String syntax examples
1006 ///
1007 /// - `DropTable` -- any table.
1008 /// - `DropTable(Students)` -- only `Students`.
1009 pub struct DropTable {
1010 /// The name of the table being dropped, or `*` for any.
1011 pub table_name: String,
1012 }
1013
1014 /// Selector for `DROP TEMP INDEX` statements, scoped to a table and index
1015 /// name.
1016 ///
1017 /// # String syntax examples
1018 ///
1019 /// - `DropTempIndex` -- any temporary index.
1020 /// - `DropTempIndex(Sessions.idx_ts)` -- one specific index.
1021 pub struct DropTempIndex {
1022 /// The table the temp index belongs to, or `*` for any.
1023 pub table_name: String,
1024 /// The name of the temp index being dropped, or `*` for any.
1025 pub index_name: String,
1026 }
1027
1028 /// Selector for `DROP TEMP TABLE` statements.
1029 ///
1030 /// # String syntax examples
1031 ///
1032 /// - `DropTempTable` -- any temporary table.
1033 /// - `DropTempTable(scratch)` -- only `scratch`.
1034 pub struct DropTempTable {
1035 /// The name of the temp table being dropped, or `*` for any.
1036 pub table_name: String,
1037 }
1038
1039 /// Selector for `DROP TEMP TRIGGER` statements, scoped to a table and
1040 /// trigger name.
1041 ///
1042 /// # String syntax examples
1043 ///
1044 /// - `DropTempTrigger` -- any temp trigger.
1045 /// - `DropTempTrigger(Orders.trg_audit)` -- one specific trigger.
1046 pub struct DropTempTrigger {
1047 /// The table the temp trigger fires on, or `*` for any.
1048 pub table_name: String,
1049 /// The name of the temp trigger being dropped, or `*`.
1050 pub trigger_name: String,
1051 }
1052
1053 /// Selector for `DROP TEMP VIEW` statements.
1054 ///
1055 /// # String syntax examples
1056 ///
1057 /// - `DropTempView` -- any temporary view.
1058 /// - `DropTempView(recent_orders)` -- only `recent_orders`.
1059 pub struct DropTempView {
1060 /// The name of the temp view being dropped, or `*` for any.
1061 pub view_name: String,
1062 }
1063
1064 /// Selector for `DROP TRIGGER` statements, scoped to a table and trigger
1065 /// name.
1066 ///
1067 /// # String syntax examples
1068 ///
1069 /// - `DropTrigger` -- any trigger on any table.
1070 /// - `DropTrigger(Orders.trg_audit)` -- one specific trigger.
1071 pub struct DropTrigger {
1072 /// The table the trigger fires on, or `*` for any.
1073 pub table_name: String,
1074 /// The name of the trigger being dropped, or `*` for any.
1075 pub trigger_name: String,
1076 }
1077
1078 /// Selector for `DROP VIEW` statements.
1079 ///
1080 /// # String syntax examples
1081 ///
1082 /// - `DropView` -- any view.
1083 /// - `DropView(active_users)` -- only `active_users`.
1084 pub struct DropView {
1085 /// The name of the view being dropped, or `*` for any.
1086 pub view_name: String,
1087 }
1088
1089 /// Selector for `INSERT` statements, scoped to a table.
1090 ///
1091 /// # String syntax examples
1092 ///
1093 /// - `Insert` -- insert into any table.
1094 /// - `Insert(Students)` -- only into `Students`.
1095 pub struct Insert {
1096 /// The table being inserted into, or `*` for any.
1097 pub table_name: String,
1098 }
1099
1100 /// Selector for `PRAGMA` statements, scoped to a pragma name
1101 ///
1102 /// # String syntax examples
1103 ///
1104 /// - `Pragma` -- any pragma.
1105 /// - `Pragma(journal_mode)` -- only `journal_mode`.
1106 pub struct Pragma {
1107 /// The pragma name, or `*` for any.
1108 pub pragma_name: String,
1109 }
1110
1111 /// Selector for column-level read operations, scoped to a table and column.
1112 ///
1113 /// When both fields are globs, this matches all read operations across the
1114 /// entire database. Specifying a `table_name` narrows the scope to a single
1115 /// table, and additionally specifying a `column_name` narrows it to one
1116 /// column.
1117 ///
1118 /// # String syntax examples
1119 ///
1120 /// - `Read` -- all tables, all columns.
1121 /// - `Read(Students)` -- the `Students` table, all columns.
1122 /// - `Read(Students.name)` -- only the `name` column of `Students`.
1123 /// - `Read(*.name)` -- the `name` column across every table.
1124 pub struct Read {
1125 /// The target table name, or `*` to match any table.
1126 pub table_name: String,
1127 /// The target column name, or `*` to match any column.
1128 pub column_name: String,
1129 }
1130
1131 /// Selector for bare `SELECT` operations (the statement-level authorization
1132 /// check, not column-level reads).
1133 pub struct Select {}
1134
1135 /// Selector for transaction control operations (`BEGIN`, `COMMIT`,
1136 /// `ROLLBACK`).
1137 ///
1138 /// # String syntax examples
1139 ///
1140 /// - `Transaction` -- any transaction operation.
1141 /// - `Transaction(BEGIN)` -- only `BEGIN` statements.
1142 pub struct Transaction {
1143 /// The transaction operation name, or `*` for any.
1144 pub operation: TransactionOperation,
1145 }
1146
1147 /// Selector for `UPDATE` statements, scoped to a table and column.
1148 ///
1149 /// # String syntax examples
1150 ///
1151 /// - `Update` -- any column on any table.
1152 /// - `Update(Students)` -- any column on `Students`.
1153 /// - `Update(Students.email)` -- only the `email` column.
1154 pub struct Update {
1155 /// The table being updated, or `*` for any.
1156 pub table_name: String,
1157 /// The column being updated, or `*` for any.
1158 pub column_name: String,
1159 }
1160
1161 /// Selector for `ATTACH DATABASE` statements, scoped to a filename.
1162 ///
1163 /// # String syntax examples
1164 ///
1165 /// - `Attach` -- any attachment.
1166 /// - `Attach(other.db)` -- only `other.db`.
1167 pub struct Attach {
1168 /// The filename being attached, or `*` for any.
1169 pub filename: String,
1170 }
1171
1172 /// Selector for `DETACH DATABASE` statements, scoped to a database name.
1173 ///
1174 /// # String syntax examples
1175 ///
1176 /// - `Detach` -- any detachment.
1177 /// - `Detach(aux)` -- only the `aux` database.
1178 pub struct Detach {
1179 /// The database name being detached, or `*` for any.
1180 pub database_name: String,
1181 }
1182
1183 /// Selector for `ALTER TABLE` statements, scoped to a database and table.
1184 ///
1185 /// # String syntax examples
1186 ///
1187 /// - `AlterTable` -- any alter on any table.
1188 /// - `AlterTable(main.Students)` -- `Students` in `main`.
1189 pub struct AlterTable {
1190 /// The database the table belongs to, or `*` for any.
1191 pub database_name: String,
1192 /// The table being altered, or `*` for any.
1193 pub table_name: String,
1194 }
1195
1196 /// Selector for `REINDEX` statements, scoped to an index name.
1197 ///
1198 /// # String syntax examples
1199 ///
1200 /// - `Reindex` -- any index.
1201 /// - `Reindex(idx_email)` -- only `idx_email`.
1202 pub struct Reindex {
1203 /// The name of the index being reindexed, or `*` for any.
1204 pub index_name: String,
1205 }
1206
1207 /// Selector for `ANALYZE` statements, scoped to a table.
1208 ///
1209 /// # String syntax examples
1210 ///
1211 /// - `Analyze` -- any table.
1212 /// - `Analyze(Students)` -- only `Students`.
1213 pub struct Analyze {
1214 /// The table being analyzed, or `*` for any.
1215 pub table_name: String,
1216 }
1217
1218 /// Selector for `CREATE VIRTUAL TABLE` statements, scoped to a table and
1219 /// module name.
1220 ///
1221 /// # String syntax examples
1222 ///
1223 /// - `CreateVtable` -- any virtual table.
1224 /// - `CreateVtable(docs.fts5)` -- `docs` using `fts5`.
1225 pub struct CreateVtable {
1226 /// The name of the virtual table, or `*` for any.
1227 pub table_name: String,
1228 /// The module backing the virtual table, or `*` for any.
1229 pub module_name: String,
1230 }
1231
1232 /// Selector for `DROP VIRTUAL TABLE` statements, scoped to a table and
1233 /// module name.
1234 ///
1235 /// # String syntax examples
1236 ///
1237 /// - `DropVtable` -- any virtual table.
1238 /// - `DropVtable(docs.fts5)` -- `docs` backed by `fts5`.
1239 pub struct DropVtable {
1240 /// The name of the virtual table, or `*` for any.
1241 pub table_name: String,
1242 /// The module backing the virtual table, or `*` for any.
1243 pub module_name: String,
1244 }
1245
1246 /// Selector for SQL function invocations within a query.
1247 ///
1248 /// # String syntax examples
1249 ///
1250 /// - `Function` -- any function.
1251 /// - `Function(load_extension)` -- only `load_extension`.
1252 pub struct Function {
1253 /// The name of the function being called, or `*` for any.
1254 pub function_name: String,
1255 }
1256
1257 /// Selector for `SAVEPOINT`, `RELEASE`, and `ROLLBACK TO` operations on
1258 /// named savepoints.
1259 ///
1260 /// # String syntax examples
1261 ///
1262 /// - `Savepoint` -- any savepoint operation.
1263 /// - `Savepoint(RELEASE)` -- only `RELEASE` operations.
1264 /// - `Savepoint(RELEASE.sp1)` -- `RELEASE` on savepoint `sp1`.
1265 pub struct Savepoint {
1266 /// The savepoint operation, or `*` for any.
1267 pub operation: TransactionOperation,
1268 /// The savepoint name, or `*` for any.
1269 pub savepoint_name: String,
1270 }
1271
1272 /// Selector for recursive query authorization checks.
1273 pub struct Recursive {}
1274}
1275
1276/// A wildcard marker that matches any value in a selector field.
1277///
1278/// `Glob` is the unit type behind [`ValueOrGlob::Glob`]. It displays as `*` and
1279/// is produced whenever a selector field is omitted or explicitly set to `*` in
1280/// the CLI string syntax.
1281#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1282pub struct Glob;
1283
1284/// Renders the glob as the wildcard character `*`.
1285impl Display for Glob {
1286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1287 write!(f, "*")
1288 }
1289}
1290
1291/// Either a concrete value of type `T` or a wildcard [`Glob`].
1292///
1293/// Every field on a generated selector struct is wrapped in this enum so that
1294/// it can independently be pinned to a specific value or left as a glob to
1295/// match anything. This is the core abstraction that allows selectors to
1296/// express patterns like `Read(Students.*)` (specific table, any column) or
1297/// `Read(*.name)` (any table, specific column).
1298///
1299/// The `Default` implementation returns `Glob`, matching the convention that an
1300/// unspecified field matches everything.
1301#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1302pub enum ValueOrGlob<T> {
1303 /// A concrete, pinned value that this selector field must match exactly.
1304 Value(T),
1305 /// A wildcard that matches any value for this selector field.
1306 Glob(Glob),
1307}
1308
1309/// Defaults to [`ValueOrGlob::Glob`] so that omitted fields match everything.
1310impl<T> Default for ValueOrGlob<T> {
1311 fn default() -> Self {
1312 Self::new_glob()
1313 }
1314}
1315
1316impl<T> ValueOrGlob<T> {
1317 /// Creates a [`ValueOrGlob::Value`] by converting the argument into `T`.
1318 ///
1319 /// Accepts anything that implements `Into<T>` so callers can pass owned
1320 /// values, string slices (when `T = String`), and other convertible types
1321 /// without explicit conversion at the call site.
1322 pub fn new_value(value: impl Into<T>) -> Self {
1323 Self::Value(value.into())
1324 }
1325
1326 /// Creates a [`ValueOrGlob::Glob`], representing a wildcard that matches
1327 /// any value.
1328 pub const fn new_glob() -> Self {
1329 Self::Glob(Glob)
1330 }
1331
1332 /// Returns `true` if this is a concrete [`Value`](ValueOrGlob::Value).
1333 pub const fn is_value(&self) -> bool {
1334 matches!(self, Self::Value(..))
1335 }
1336
1337 /// Returns `true` if this is a wildcard [`Glob`](ValueOrGlob::Glob).
1338 pub const fn is_glob(&self) -> bool {
1339 matches!(self, Self::Glob(..))
1340 }
1341
1342 /// Returns a reference to the inner value if this is a
1343 /// [`Value`](ValueOrGlob::Value), or `None` if it is a glob.
1344 pub const fn as_value(&self) -> Option<&T> {
1345 match self {
1346 ValueOrGlob::Value(value) => Some(value),
1347 ValueOrGlob::Glob(_) => None,
1348 }
1349 }
1350
1351 /// Returns a reference to the inner [`Glob`] if this is a
1352 /// [`Glob`](ValueOrGlob::Glob) variant, or `None` if it is a value.
1353 pub const fn as_glob(&self) -> Option<&Glob> {
1354 match self {
1355 ValueOrGlob::Value(_) => None,
1356 ValueOrGlob::Glob(glob) => Some(glob),
1357 }
1358 }
1359
1360 /// Consumes `self` and returns the inner `T` if this is a
1361 /// [`Value`](ValueOrGlob::Value), or `None` if it is a glob.
1362 pub fn into_value(self) -> Option<T> {
1363 match self {
1364 ValueOrGlob::Value(value) => Some(value),
1365 ValueOrGlob::Glob(_) => None,
1366 }
1367 }
1368
1369 /// Consumes `self` and returns the inner [`Glob`] if this is a
1370 /// [`Glob`](ValueOrGlob::Glob) variant, or `None` if it is a value.
1371 pub fn into_glob(self) -> Option<Glob> {
1372 match self {
1373 ValueOrGlob::Value(_) => None,
1374 ValueOrGlob::Glob(glob) => Some(glob),
1375 }
1376 }
1377}
1378
1379/// Delegates to the inner value's `Display` for [`Value`](ValueOrGlob::Value),
1380/// or renders `*` for [`Glob`](ValueOrGlob::Glob).
1381impl<T> Display for ValueOrGlob<T>
1382where
1383 T: Display,
1384{
1385 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1386 match self {
1387 ValueOrGlob::Value(value) => Display::fmt(value, f),
1388 ValueOrGlob::Glob(glob) => Display::fmt(glob, f),
1389 }
1390 }
1391}
1392
1393/// Converts a [`ValueOrGlob`] to its string representation, enabling `.into()`
1394/// at call sites that expect a `String`.
1395impl<T> From<ValueOrGlob<T>> for String
1396where
1397 T: Display,
1398{
1399 fn from(value: ValueOrGlob<T>) -> Self {
1400 value.to_string()
1401 }
1402}
1403
1404/// Parses `"*"` as a [`Glob`](ValueOrGlob::Glob) and anything else by
1405/// delegating to `T::from_str`, yielding a [`Value`](ValueOrGlob::Value) on
1406/// success.
1407impl<T> FromStr for ValueOrGlob<T>
1408where
1409 T: FromStr,
1410{
1411 type Err = T::Err;
1412
1413 fn from_str(s: &str) -> Result<Self, Self::Err> {
1414 if s == "*" {
1415 Ok(Self::Glob(Glob))
1416 } else {
1417 T::from_str(s).map(Self::Value)
1418 }
1419 }
1420}
1421
1422/// Parses an owned `String` into a [`ValueOrGlob`] by delegating to
1423/// [`FromStr`], enabling `value.try_into()` at call sites.
1424impl<T> TryFrom<String> for ValueOrGlob<T>
1425where
1426 T: FromStr,
1427{
1428 type Error = T::Err;
1429
1430 fn try_from(value: String) -> Result<Self, Self::Error> {
1431 value.parse()
1432 }
1433}
1434
1435/// Converts an `Option<T>` into a [`ValueOrGlob`]: `Some(v)` becomes
1436/// [`Value(v)`](ValueOrGlob::Value) and `None` becomes
1437/// [`Glob`](ValueOrGlob::Glob), mirroring the semantics of "present means
1438/// pinned, absent means match-all."
1439impl<T> From<Option<T>> for ValueOrGlob<T> {
1440 fn from(value: Option<T>) -> Self {
1441 match value {
1442 Some(value) => Self::Value(value),
1443 None => Self::Glob(Glob),
1444 }
1445 }
1446}
1447
1448/// Intermediate representation produced by splitting a raw selector string into
1449/// its identifier and optional dot-separated field values.
1450///
1451/// This is an internal parsing helper consumed by the `FromStr` implementations
1452/// generated by `define_access_control_selector_types!`. The const generic
1453/// `FIELD_COUNT` is set to the number of fields declared on the target struct,
1454/// and is used to validate that the input does not contain more fields than the
1455/// struct expects.
1456struct ParsedAccessControlSelector<'a, const FIELD_COUNT: usize> {
1457 /// The selector identifier portion of the input string (e.g., `"Read"`).
1458 pub access_control_selector_ident: &'a str,
1459 /// The dot-separated field values inside the parentheses, if any were
1460 /// present. `None` when the input was a bare identifier like `"Read"`.
1461 pub fields: Option<std::str::Split<'a, char>>,
1462}
1463
1464impl<'a, const FIELD_COUNT: usize>
1465 ParsedAccessControlSelector<'a, FIELD_COUNT>
1466{
1467 /// Parses a raw selector string into its identifier and optional fields.
1468 ///
1469 /// The input `string` is expected to follow the syntax `Identifier` or
1470 /// `Identifier(field1.field2)`. The method validates balanced parentheses
1471 /// and enforces that the number of dot-separated fields does not exceed
1472 /// `FIELD_COUNT`.
1473 ///
1474 /// # Errors
1475 ///
1476 /// Returns an [`AccessControlSelectorParseError`] when the identifier is
1477 /// missing, the closing parenthesis is absent, or there are more fields
1478 /// than the target struct declares.
1479 pub fn new(
1480 string: &'a str,
1481 ) -> Result<Self, AccessControlSelectorParseError> {
1482 use AccessControlSelectorParseError::*;
1483
1484 let mut brace_split = string.splitn(2, '(');
1485
1486 // Get the identifier of the access_control_selector. If it doesn't exist then error
1487 // out.
1488 let access_control_selector_ident = brace_split
1489 .next()
1490 .ok_or_else(|| NoAccessControlSelectorIdentifierFound {
1491 expected: stringify!($struct_ident).into(),
1492 access_control_selector_string: string.to_string(),
1493 })?
1494 .trim();
1495
1496 // Check if there's a remaining portion to the string. If there isn't,
1497 // then it means that this was a access_control_selector identifier without anything
1498 // else which is accepted.
1499 if let Some(remaining_string) = brace_split.next() {
1500 // Split once at the closing brace, if we fail to do that then this
1501 // is a malformed access_control_selector.
1502 let Some((inner_fields, _)) = remaining_string.split_once(')')
1503 else {
1504 return Err(NoClosingBraceFound {
1505 access_control_selector_string: string.to_string(),
1506 });
1507 };
1508
1509 // Count the number of dot separators in the remaining string. This
1510 // number can be at most `FIELD_COUNT - 1`. For access_control_selectors with no
1511 // fields the subtraction fails which would return a correct invalid
1512 // number of fields error.
1513 let number_of_fields =
1514 inner_fields.chars().filter(|c| matches!(c, '.')).count() + 1;
1515 if number_of_fields > FIELD_COUNT {
1516 return Err(InvalidNumberOfFields {
1517 found: number_of_fields,
1518 maximum_expected: FIELD_COUNT,
1519 access_control_selector_string: string.to_string(),
1520 });
1521 }
1522
1523 // Split the inner fields (without the closing paren) by dots and return.
1524 let fields = inner_fields.split('.');
1525
1526 Ok(Self {
1527 access_control_selector_ident,
1528 fields: Some(fields),
1529 })
1530 } else {
1531 Ok(Self {
1532 access_control_selector_ident,
1533 fields: None,
1534 })
1535 }
1536 }
1537}
1538
1539/// Pairs a selector with a permission disposition (allow or deny).
1540///
1541/// Stored inside [`AccessControlSelectorSet`] and evaluated by the policy
1542/// resolver. When a [`Permission`] 's selector matches an incoming operation,
1543/// its `allow` flag determines whether the operation is permitted or forbidden.
1544#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1545pub struct Permission<T> {
1546 /// The selector that defines which operations this rule applies to.
1547 pub selector: T,
1548 /// `true` to permit the matched operation, `false` to deny it.
1549 pub allow: bool,
1550}
1551
1552/// The outcome of a policy check against the configured rule set.
1553#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1554pub enum Authorization {
1555 /// The operation is permitted.
1556 Allow,
1557 /// The operation is forbidden.
1558 Deny,
1559}
1560
1561/// Maps this crate's [`Authorization`] to rusqlite's equivalent so that policy
1562/// verdicts can be returned directly from the authorization hook.
1563impl From<Authorization> for rusqlite::hooks::Authorization {
1564 fn from(value: Authorization) -> Self {
1565 match value {
1566 Authorization::Allow => Self::Allow,
1567 Authorization::Deny => Self::Deny,
1568 }
1569 }
1570}
1571
1572/// The type of transaction or savepoint operation being authorized.
1573///
1574/// Mirrors [`rusqlite::hooks::TransactionOperation`] but is owned by this crate
1575/// so it can derive [`Display`] and [`FromStr`] via `strum`, enabling
1576/// round-trip parsing in selector strings like `Transaction(BEGIN)`.
1577#[derive(
1578 Clone,
1579 Copy,
1580 Debug,
1581 Eq,
1582 PartialEq,
1583 PartialOrd,
1584 Ord,
1585 Hash,
1586 strum::Display,
1587 strum::EnumString,
1588)]
1589#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
1590pub enum TransactionOperation {
1591 /// An unrecognized or unknown operation type.
1592 Unknown,
1593 /// A `BEGIN` transaction statement.
1594 Begin,
1595 /// A `RELEASE` savepoint statement.
1596 Release,
1597 /// A `ROLLBACK` statement.
1598 Rollback,
1599}
1600
1601/// Converts this crate's [`TransactionOperation`] into rusqlite's equivalent
1602/// for use in authorization hook responses.
1603impl From<TransactionOperation> for rusqlite::hooks::TransactionOperation {
1604 fn from(value: TransactionOperation) -> Self {
1605 match value {
1606 TransactionOperation::Unknown => Self::Unknown,
1607 TransactionOperation::Begin => Self::Begin,
1608 TransactionOperation::Release => Self::Release,
1609 TransactionOperation::Rollback => Self::Rollback,
1610 }
1611 }
1612}
1613
1614/// Allows comparing this crate's [`TransactionOperation`] against rusqlite's
1615/// variant by converting to the rusqlite type first.
1616impl PartialEq<rusqlite::hooks::TransactionOperation> for TransactionOperation {
1617 fn eq(&self, other: &rusqlite::hooks::TransactionOperation) -> bool {
1618 &rusqlite::hooks::TransactionOperation::from(*self) == other
1619 }
1620}
1621
1622/// Allows comparing rusqlite's `TransactionOperation` against this crate's
1623/// [`TransactionOperation`] by converting to the rusqlite type first.
1624impl PartialEq<TransactionOperation> for rusqlite::hooks::TransactionOperation {
1625 fn eq(&self, other: &TransactionOperation) -> bool {
1626 &rusqlite::hooks::TransactionOperation::from(*other) == self
1627 }
1628}
1629
1630/// Enumerates the ways parsing an access control selector string can fail.
1631///
1632/// Each variant captures the original input string together with contextual
1633/// information about what went wrong, so that error messages can be actionable
1634/// and specific.
1635#[derive(Debug, thiserror::Error)]
1636pub enum AccessControlSelectorParseError {
1637 /// The input string could not be split into an identifier portion at all.
1638 ///
1639 /// This typically means the string was empty or otherwise malformed in a
1640 /// way that prevented even the first token from being extracted.
1641 #[error(
1642 "no access control selector identifier found in \"{access_control_selector_string}\", \
1643 expected \"{expected}\""
1644 )]
1645 NoAccessControlSelectorIdentifierFound {
1646 /// The identifier that was expected (set by the specific struct's
1647 /// `FromStr`).
1648 expected: String,
1649 /// The raw input string that failed to parse.
1650 access_control_selector_string: String,
1651 },
1652 /// The identifier was successfully extracted but does not match the target
1653 /// struct's name.
1654 ///
1655 /// Returned when parsing into a specific selector type (e.g.,
1656 /// `"Write".parse::<Read>()`) where the identifier does not agree with the
1657 /// struct being parsed into.
1658 #[error(
1659 "incorrect access control selector identifier in \"{access_control_selector_string}\": \
1660 expected \"{expected}\", found \"{found}\""
1661 )]
1662 IncorrectAccessControlSelectorIdentifier {
1663 /// The identifier that was expected.
1664 expected: String,
1665 /// The identifier that was actually found in the input.
1666 found: String,
1667 /// The raw input string that failed to parse.
1668 access_control_selector_string: String,
1669 },
1670 /// The identifier does not match any known selector variant in the
1671 /// [`AccessControlSelector`] enum.
1672 ///
1673 /// Returned when parsing through the umbrella enum and the leading token is
1674 /// not recognized.
1675 #[error(
1676 "invalid access control selector identifier in \"{access_control_selector_string}\": \
1677 found {found:?}, expected one of {expected:?}"
1678 )]
1679 InvalidAccessControlSelectorIdentifier {
1680 /// The identifier that was found, or `None` if extraction failed.
1681 found: Option<String>,
1682 /// The set of valid selector identifiers.
1683 expected: &'static [&'static str],
1684 /// The raw input string that failed to parse.
1685 access_control_selector_string: String,
1686 },
1687 /// A `(` was found but the matching `)` was missing.
1688 #[error(
1689 "no closing parenthesis found in \"{access_control_selector_string}\""
1690 )]
1691 NoClosingBraceFound {
1692 /// The raw input string that failed to parse.
1693 access_control_selector_string: String,
1694 },
1695 /// The number of dot-separated fields inside the parentheses exceeds the
1696 /// maximum that the target struct declares.
1697 #[error(
1698 "invalid number of fields in \"{access_control_selector_string}\": \
1699 found {found}, maximum expected {maximum_expected}"
1700 )]
1701 InvalidNumberOfFields {
1702 /// The number of fields found in the input.
1703 found: usize,
1704 /// The maximum number of fields the target struct accepts.
1705 maximum_expected: usize,
1706 /// The raw input string that failed to parse.
1707 access_control_selector_string: String,
1708 },
1709 /// An individual field value inside the parentheses could not be parsed
1710 /// into its target type.
1711 #[error(
1712 "failed to parse field \"{field_ident}\" with value \"{field_value}\": {error}"
1713 )]
1714 FailedToParseFieldValue {
1715 /// The name of the struct field that failed to parse.
1716 field_ident: String,
1717 /// The raw string value that could not be converted.
1718 field_value: String,
1719 /// The underlying parse error.
1720 error: Box<dyn Error + Send + Sync>,
1721 },
1722}
1723
1724/// Predefined permission presets for common access control configurations.
1725///
1726/// Each variant maps to an [`AuthorizationResolver`] constructor that sets
1727/// sensible defaults for a particular use case. Variants are ordered from most
1728/// restrictive to least restrictive. The preset is selected via the `--preset`
1729/// CLI flag and determines the baseline permissions before any `--allow` /
1730/// `--deny` overrides are applied.
1731#[derive(
1732 Clone,
1733 Copy,
1734 Debug,
1735 Default,
1736 PartialEq,
1737 Eq,
1738 PartialOrd,
1739 Ord,
1740 Hash,
1741 strum::Display,
1742 strum::EnumString,
1743 clap::ValueEnum,
1744)]
1745#[strum(serialize_all = "kebab-case")]
1746pub enum Preset {
1747 /// Denies all operations. Useful as a starting point when every permitted
1748 /// action must be explicitly allowed via `--allow` flags.
1749 DenyEverything,
1750 /// Allows reads, selects, transactions, SQL functions, recursive CTEs, and
1751 /// pragmas. Denies all data modification and DDL.
1752 #[default]
1753 ReadOnly,
1754 /// Extends read-only with insert, update, delete, savepoints, analyze,
1755 /// reindex, and temporary object operations. Permanent DDL, attach, and
1756 /// detach remain denied.
1757 ReadWrite,
1758 /// Extends read-write with permanent DDL (create/drop tables, indexes,
1759 /// triggers, views, and alter table). Attach, detach, and virtual table
1760 /// operations remain denied.
1761 FullDdl,
1762 /// Allows all operations with no restrictions. Intended for development and
1763 /// testing only.
1764 AllowEverything,
1765}
1766
1767/// Converts a [`Preset`] into the corresponding [`AuthorizationResolver`] by
1768/// calling the matching constructor.
1769impl From<Preset> for AuthorizationResolver {
1770 fn from(preset: Preset) -> Self {
1771 match preset {
1772 Preset::DenyEverything => Self::new_deny_everything(),
1773 Preset::ReadOnly => Self::new_read_only(),
1774 Preset::ReadWrite => Self::new_read_write(),
1775 Preset::FullDdl => Self::new_full_ddl(),
1776 Preset::AllowEverything => Self::new_allow_everything(),
1777 }
1778 }
1779}
1780
1781impl AuthorizationResolver {
1782 /// Creates a read-only resolver.
1783 ///
1784 /// Allows reads, selects, transactions, SQL functions, recursive CTEs, and
1785 /// pragmas. Denies all data modification (insert, update, delete) and all
1786 /// DDL (create, drop, alter, attach, detach). Users can layer additional
1787 /// `--allow` / `--deny` rules on top to fine-tune access.
1788 pub fn new_read_only() -> Self {
1789 use rusqlite::hooks::Authorization::Allow;
1790
1791 Self::new_deny_everything()
1792 .with_read_default_permissions(Allow)
1793 .with_select_default_permissions(Allow)
1794 .with_transaction_default_permissions(Allow)
1795 .with_function_default_permissions(Allow)
1796 .with_recursive_default_permissions(Allow)
1797 .with_pragma_default_permissions(Allow)
1798 }
1799
1800 /// Creates a read-write resolver that forbids schema changes.
1801 ///
1802 /// Extends [`new_read_only`](Self::new_read_only) with insert, update,
1803 /// delete, savepoints, analyze, reindex, and temporary object
1804 /// creation/deletion. Permanent DDL (create/drop table, index, trigger,
1805 /// view, virtual table), attach, and detach remain denied.
1806 pub fn new_read_write() -> Self {
1807 use rusqlite::hooks::Authorization::Allow;
1808
1809 Self::new_read_only()
1810 .with_insert_default_permissions(Allow)
1811 .with_update_default_permissions(Allow)
1812 .with_delete_default_permissions(Allow)
1813 .with_savepoint_default_permissions(Allow)
1814 .with_analyze_default_permissions(Allow)
1815 .with_reindex_default_permissions(Allow)
1816 .with_create_temp_table_default_permissions(Allow)
1817 .with_create_temp_index_default_permissions(Allow)
1818 .with_create_temp_view_default_permissions(Allow)
1819 .with_create_temp_trigger_default_permissions(Allow)
1820 .with_drop_temp_table_default_permissions(Allow)
1821 .with_drop_temp_index_default_permissions(Allow)
1822 .with_drop_temp_view_default_permissions(Allow)
1823 .with_drop_temp_trigger_default_permissions(Allow)
1824 }
1825
1826 /// Creates a resolver that allows all data and DDL operations but denies
1827 /// operations that reach outside the database file.
1828 ///
1829 /// Extends [`new_read_write`](Self::new_read_write) with permanent DDL
1830 /// (create/drop tables, indexes, triggers, views, and alter table). Attach,
1831 /// detach, and virtual table operations remain denied because they can
1832 /// access the filesystem or load arbitrary code.
1833 pub fn new_full_ddl() -> Self {
1834 use rusqlite::hooks::Authorization::Allow;
1835
1836 Self::new_read_write()
1837 .with_create_table_default_permissions(Allow)
1838 .with_drop_table_default_permissions(Allow)
1839 .with_alter_table_default_permissions(Allow)
1840 .with_create_index_default_permissions(Allow)
1841 .with_drop_index_default_permissions(Allow)
1842 .with_create_trigger_default_permissions(Allow)
1843 .with_drop_trigger_default_permissions(Allow)
1844 .with_create_view_default_permissions(Allow)
1845 .with_drop_view_default_permissions(Allow)
1846 }
1847}
1848
1849#[cfg(test)]
1850mod tests {
1851 use super::*;
1852
1853 #[test]
1854 fn glob_display_shows_asterisk() {
1855 // Arrange
1856 let glob = Glob;
1857
1858 // Act
1859 let displayed = glob.to_string();
1860
1861 // Assert
1862 assert_eq!(displayed, "*");
1863 }
1864
1865 #[test]
1866 fn value_or_glob_new_value_creates_value_variant() {
1867 // Arrange
1868 let input = "hello";
1869
1870 // Act
1871 let vog = ValueOrGlob::<String>::new_value(input);
1872
1873 // Assert
1874 assert_eq!(vog, ValueOrGlob::Value("hello".to_string()));
1875 }
1876
1877 #[test]
1878 fn value_or_glob_new_glob_creates_glob_variant() {
1879 // Arrange
1880 // (no setup needed)
1881
1882 // Act
1883 let vog = ValueOrGlob::<String>::new_glob();
1884
1885 // Assert
1886 assert_eq!(vog, ValueOrGlob::Glob(Glob));
1887 }
1888
1889 #[test]
1890 fn value_or_glob_default_returns_glob() {
1891 // Arrange
1892 // (no setup needed)
1893
1894 // Act
1895 let vog = ValueOrGlob::<String>::default();
1896
1897 // Assert
1898 assert_eq!(vog, ValueOrGlob::Glob(Glob));
1899 }
1900
1901 #[test]
1902 fn value_or_glob_is_value_returns_true_for_value() {
1903 // Arrange
1904 let vog = ValueOrGlob::<String>::new_value("test");
1905
1906 // Act
1907 let result = vog.is_value();
1908
1909 // Assert
1910 assert!(result);
1911 }
1912
1913 #[test]
1914 fn value_or_glob_is_value_returns_false_for_glob() {
1915 // Arrange
1916 let vog = ValueOrGlob::<String>::new_glob();
1917
1918 // Act
1919 let result = vog.is_value();
1920
1921 // Assert
1922 assert!(!result);
1923 }
1924
1925 #[test]
1926 fn value_or_glob_is_glob_returns_true_for_glob() {
1927 // Arrange
1928 let vog = ValueOrGlob::<String>::new_glob();
1929
1930 // Act
1931 let result = vog.is_glob();
1932
1933 // Assert
1934 assert!(result);
1935 }
1936
1937 #[test]
1938 fn value_or_glob_is_glob_returns_false_for_value() {
1939 // Arrange
1940 let vog = ValueOrGlob::<String>::new_value("test");
1941
1942 // Act
1943 let result = vog.is_glob();
1944
1945 // Assert
1946 assert!(!result);
1947 }
1948
1949 #[test]
1950 fn value_or_glob_as_value_returns_some_for_value() {
1951 // Arrange
1952 let vog = ValueOrGlob::<String>::new_value("hello");
1953
1954 // Act
1955 let result = vog.as_value();
1956
1957 // Assert
1958 assert_eq!(result, Some(&"hello".to_string()));
1959 }
1960
1961 #[test]
1962 fn value_or_glob_as_value_returns_none_for_glob() {
1963 // Arrange
1964 let vog = ValueOrGlob::<String>::new_glob();
1965
1966 // Act
1967 let result = vog.as_value();
1968
1969 // Assert
1970 assert_eq!(result, None);
1971 }
1972
1973 #[test]
1974 fn value_or_glob_as_glob_returns_some_for_glob() {
1975 // Arrange
1976 let vog = ValueOrGlob::<String>::new_glob();
1977
1978 // Act
1979 let result = vog.as_glob();
1980
1981 // Assert
1982 assert_eq!(result, Some(&Glob));
1983 }
1984
1985 #[test]
1986 fn value_or_glob_as_glob_returns_none_for_value() {
1987 // Arrange
1988 let vog = ValueOrGlob::<String>::new_value("test");
1989
1990 // Act
1991 let result = vog.as_glob();
1992
1993 // Assert
1994 assert_eq!(result, None);
1995 }
1996
1997 #[test]
1998 fn value_or_glob_into_value_returns_some_for_value() {
1999 // Arrange
2000 let vog = ValueOrGlob::<String>::new_value("hello");
2001
2002 // Act
2003 let result = vog.into_value();
2004
2005 // Assert
2006 assert_eq!(result, Some("hello".to_string()));
2007 }
2008
2009 #[test]
2010 fn value_or_glob_into_value_returns_none_for_glob() {
2011 // Arrange
2012 let vog = ValueOrGlob::<String>::new_glob();
2013
2014 // Act
2015 let result = vog.into_value();
2016
2017 // Assert
2018 assert_eq!(result, None);
2019 }
2020
2021 #[test]
2022 fn value_or_glob_into_glob_returns_some_for_glob() {
2023 // Arrange
2024 let vog = ValueOrGlob::<String>::new_glob();
2025
2026 // Act
2027 let result = vog.into_glob();
2028
2029 // Assert
2030 assert_eq!(result, Some(Glob));
2031 }
2032
2033 #[test]
2034 fn value_or_glob_into_glob_returns_none_for_value() {
2035 // Arrange
2036 let vog = ValueOrGlob::<String>::new_value("test");
2037
2038 // Act
2039 let result = vog.into_glob();
2040
2041 // Assert
2042 assert_eq!(result, None);
2043 }
2044
2045 #[test]
2046 fn value_or_glob_display_shows_inner_value_for_value() {
2047 // Arrange
2048 let vog = ValueOrGlob::<String>::new_value("Students");
2049
2050 // Act
2051 let displayed = vog.to_string();
2052
2053 // Assert
2054 assert_eq!(displayed, "Students");
2055 }
2056
2057 #[test]
2058 fn value_or_glob_display_shows_asterisk_for_glob() {
2059 // Arrange
2060 let vog = ValueOrGlob::<String>::new_glob();
2061
2062 // Act
2063 let displayed = vog.to_string();
2064
2065 // Assert
2066 assert_eq!(displayed, "*");
2067 }
2068
2069 #[test]
2070 fn value_or_glob_from_str_with_asterisk_returns_glob() {
2071 // Arrange
2072 let input = "*";
2073
2074 // Act
2075 let result = input.parse::<ValueOrGlob<String>>().unwrap();
2076
2077 // Assert
2078 assert_eq!(result, ValueOrGlob::Glob(Glob));
2079 }
2080
2081 #[test]
2082 fn value_or_glob_from_str_with_text_returns_value() {
2083 // Arrange
2084 let input = "Students";
2085
2086 // Act
2087 let result = input.parse::<ValueOrGlob<String>>().unwrap();
2088
2089 // Assert
2090 assert_eq!(result, ValueOrGlob::Value("Students".to_string()));
2091 }
2092
2093 #[test]
2094 fn value_or_glob_from_some_option_gives_value() {
2095 // Arrange
2096 let opt = Some("name".to_string());
2097
2098 // Act
2099 let vog = ValueOrGlob::<String>::from(opt);
2100
2101 // Assert
2102 assert_eq!(vog, ValueOrGlob::Value("name".to_string()));
2103 }
2104
2105 #[test]
2106 fn value_or_glob_from_none_option_gives_glob() {
2107 // Arrange
2108 let opt = Option::<String>::None;
2109
2110 // Act
2111 let vog = ValueOrGlob::<String>::from(opt);
2112
2113 // Assert
2114 assert_eq!(vog, ValueOrGlob::Glob(Glob));
2115 }
2116
2117 #[test]
2118 fn value_or_glob_into_string_for_value() {
2119 // Arrange
2120 let vog = ValueOrGlob::<String>::new_value("hello");
2121
2122 // Act
2123 let s = String::from(vog);
2124
2125 // Assert
2126 assert_eq!(s, "hello");
2127 }
2128
2129 #[test]
2130 fn value_or_glob_into_string_for_glob() {
2131 // Arrange
2132 let vog = ValueOrGlob::<String>::new_glob();
2133
2134 // Act
2135 let s = String::from(vog);
2136
2137 // Assert
2138 assert_eq!(s, "*");
2139 }
2140
2141 #[test]
2142 fn read_new_creates_with_given_values() {
2143 // Arrange
2144 let table = ValueOrGlob::<String>::new_value("Students");
2145 let column = ValueOrGlob::<String>::new_value("name");
2146
2147 // Act
2148 let read = Read::new(table, column);
2149
2150 // Assert
2151 assert_eq!(read.table_name, ValueOrGlob::Value("Students".to_string()));
2152 assert_eq!(read.column_name, ValueOrGlob::Value("name".to_string()));
2153 }
2154
2155 #[test]
2156 fn read_empty_creates_with_all_globs() {
2157 // Arrange
2158 // (no setup needed)
2159
2160 // Act
2161 let read = Read::empty();
2162
2163 // Assert
2164 assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2165 assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2166 }
2167
2168 #[test]
2169 fn read_with_table_name_sets_table_name() {
2170 // Arrange
2171 let read = Read::empty();
2172
2173 // Act
2174 let read =
2175 read.with_table_name(ValueOrGlob::<String>::new_value("Grades"));
2176
2177 // Assert
2178 assert_eq!(read.table_name, ValueOrGlob::Value("Grades".to_string()));
2179 assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2180 }
2181
2182 #[test]
2183 fn read_with_column_name_sets_column_name() {
2184 // Arrange
2185 let read = Read::empty();
2186
2187 // Act
2188 let read =
2189 read.with_column_name(ValueOrGlob::<String>::new_value("score"));
2190
2191 // Assert
2192 assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2193 assert_eq!(read.column_name, ValueOrGlob::Value("score".to_string()));
2194 }
2195
2196 #[test]
2197 fn read_is_all_glob_true_when_both_glob() {
2198 // Arrange
2199 let read = Read::empty();
2200
2201 // Act
2202 let result = read.is_all_glob();
2203
2204 // Assert
2205 assert!(result);
2206 }
2207
2208 #[test]
2209 fn read_is_all_glob_false_when_table_is_value() {
2210 // Arrange
2211 let read = Read::empty()
2212 .with_table_name(ValueOrGlob::<String>::new_value("Students"));
2213
2214 // Act
2215 let result = read.is_all_glob();
2216
2217 // Assert
2218 assert!(!result);
2219 }
2220
2221 #[test]
2222 fn read_is_all_glob_false_when_column_is_value() {
2223 // Arrange
2224 let read = Read::empty()
2225 .with_column_name(ValueOrGlob::<String>::new_value("name"));
2226
2227 // Act
2228 let result = read.is_all_glob();
2229
2230 // Assert
2231 assert!(!result);
2232 }
2233
2234 #[test]
2235 fn read_is_all_value_true_when_both_value() {
2236 // Arrange
2237 let read = Read::new(
2238 ValueOrGlob::<String>::new_value("Students"),
2239 ValueOrGlob::<String>::new_value("name"),
2240 );
2241
2242 // Act
2243 let result = read.is_all_value();
2244
2245 // Assert
2246 assert!(result);
2247 }
2248
2249 #[test]
2250 fn read_is_all_value_false_when_one_is_glob() {
2251 // Arrange
2252 let read = Read::empty()
2253 .with_table_name(ValueOrGlob::<String>::new_value("Students"));
2254
2255 // Act
2256 let result = read.is_all_value();
2257
2258 // Assert
2259 assert!(!result);
2260 }
2261
2262 #[test]
2263 fn read_is_any_glob_true_when_one_is_glob() {
2264 // Arrange
2265 let read = Read::empty()
2266 .with_table_name(ValueOrGlob::<String>::new_value("Students"));
2267
2268 // Act
2269 let result = read.is_any_glob();
2270
2271 // Assert
2272 assert!(result);
2273 }
2274
2275 #[test]
2276 fn read_is_any_glob_false_when_both_value() {
2277 // Arrange
2278 let read = Read::new(
2279 ValueOrGlob::<String>::new_value("Students"),
2280 ValueOrGlob::<String>::new_value("name"),
2281 );
2282
2283 // Act
2284 let result = read.is_any_glob();
2285
2286 // Assert
2287 assert!(!result);
2288 }
2289
2290 #[test]
2291 fn read_is_any_value_true_when_one_is_value() {
2292 // Arrange
2293 let read = Read::empty()
2294 .with_column_name(ValueOrGlob::<String>::new_value("name"));
2295
2296 // Act
2297 let result = read.is_any_value();
2298
2299 // Assert
2300 assert!(result);
2301 }
2302
2303 #[test]
2304 fn read_is_any_value_false_when_both_glob() {
2305 // Arrange
2306 let read = Read::empty();
2307
2308 // Act
2309 let result = read.is_any_value();
2310
2311 // Assert
2312 assert!(!result);
2313 }
2314
2315 #[test]
2316 fn read_display_all_glob_shows_read_without_parens() {
2317 // Arrange
2318 let read = Read::empty();
2319
2320 // Act
2321 let displayed = read.to_string();
2322
2323 // Assert
2324 assert_eq!(displayed, "Read");
2325 }
2326
2327 #[test]
2328 fn read_display_specific_shows_read_with_table_and_column() {
2329 // Arrange
2330 let read = Read::new(
2331 ValueOrGlob::<String>::new_value("Students"),
2332 ValueOrGlob::<String>::new_value("name"),
2333 );
2334
2335 // Act
2336 let displayed = read.to_string();
2337
2338 // Assert
2339 assert_eq!(displayed, "Read(Students.name)");
2340 }
2341
2342 #[test]
2343 fn read_display_glob_table_value_column() {
2344 // Arrange
2345 let read = Read::empty()
2346 .with_column_name(ValueOrGlob::<String>::new_value("name"));
2347
2348 // Act
2349 let displayed = read.to_string();
2350
2351 // Assert
2352 assert_eq!(displayed, "Read(*.name)");
2353 }
2354
2355 #[test]
2356 fn read_display_value_table_glob_column() {
2357 // Arrange
2358 let read = Read::empty()
2359 .with_table_name(ValueOrGlob::<String>::new_value("Students"));
2360
2361 // Act
2362 let displayed = read.to_string();
2363
2364 // Assert
2365 assert_eq!(displayed, "Read(Students.*)");
2366 }
2367
2368 #[test]
2369 fn read_from_str_bare_read_parses_to_all_glob() {
2370 // Arrange
2371 let input = "Read";
2372
2373 // Act
2374 let read = input.parse::<Read>().unwrap();
2375
2376 // Assert
2377 assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2378 assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2379 }
2380
2381 #[test]
2382 fn read_from_str_specific_table_and_column_parses_correctly() {
2383 // Arrange
2384 let input = "Read(Students.name)";
2385
2386 // Act
2387 let read = input.parse::<Read>().unwrap();
2388
2389 // Assert
2390 assert_eq!(read.table_name, ValueOrGlob::Value("Students".to_string()));
2391 assert_eq!(read.column_name, ValueOrGlob::Value("name".to_string()));
2392 }
2393
2394 #[test]
2395 fn read_from_str_glob_table_value_column_parses_correctly() {
2396 // Arrange
2397 let input = "Read(*.name)";
2398
2399 // Act
2400 let read = input.parse::<Read>().unwrap();
2401
2402 // Assert
2403 assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2404 assert_eq!(read.column_name, ValueOrGlob::Value("name".to_string()));
2405 }
2406
2407 #[test]
2408 fn read_from_str_value_table_glob_column_parses_correctly() {
2409 // Arrange
2410 let input = "Read(Students.*)";
2411
2412 // Act
2413 let read = input.parse::<Read>().unwrap();
2414
2415 // Assert
2416 assert_eq!(read.table_name, ValueOrGlob::Value("Students".to_string()));
2417 assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2418 }
2419
2420 #[test]
2421 fn read_from_str_round_trip_produces_same_result() {
2422 // Arrange
2423 let input = "Read(Students.name)";
2424
2425 // Act
2426 let read = input.parse::<Read>().unwrap();
2427 let displayed = read.to_string();
2428 let reparsed = displayed.parse::<Read>().unwrap();
2429
2430 // Assert
2431 assert_eq!(
2432 reparsed.table_name,
2433 ValueOrGlob::Value("Students".to_string())
2434 );
2435 assert_eq!(
2436 reparsed.column_name,
2437 ValueOrGlob::Value("name".to_string())
2438 );
2439 }
2440
2441 #[test]
2442 fn read_from_str_round_trip_bare_read_produces_same_result() {
2443 // Arrange
2444 let input = "Read";
2445
2446 // Act
2447 let read = input.parse::<Read>().unwrap();
2448 let displayed = read.to_string();
2449 let reparsed = displayed.parse::<Read>().unwrap();
2450
2451 // Assert
2452 assert_eq!(reparsed.table_name, ValueOrGlob::Glob(Glob));
2453 assert_eq!(reparsed.column_name, ValueOrGlob::Glob(Glob));
2454 assert_eq!(displayed, "Read");
2455 }
2456
2457 #[test]
2458 fn read_into_string_works() {
2459 // Arrange
2460 let read = Read::new(
2461 ValueOrGlob::<String>::new_value("Students"),
2462 ValueOrGlob::<String>::new_value("name"),
2463 );
2464
2465 // Act
2466 let s = String::from(read);
2467
2468 // Assert
2469 assert_eq!(s, "Read(Students.name)");
2470 }
2471
2472 #[test]
2473 fn read_try_from_string_works() {
2474 // Arrange
2475 let input = "Read(Students.name)".to_string();
2476
2477 // Act
2478 let read = Read::try_from(input).unwrap();
2479
2480 // Assert
2481 assert_eq!(read.table_name, ValueOrGlob::Value("Students".to_string()));
2482 assert_eq!(read.column_name, ValueOrGlob::Value("name".to_string()));
2483 }
2484
2485 #[test]
2486 fn access_control_selector_from_str_bare_read_parses_to_read_selector() {
2487 // Arrange
2488 let input = "Read";
2489
2490 // Act
2491 let selector = input.parse::<AccessControlSelector>().unwrap();
2492
2493 // Assert
2494 match selector {
2495 AccessControlSelector::Read(read) => {
2496 assert_eq!(read.table_name, ValueOrGlob::Glob(Glob));
2497 assert_eq!(read.column_name, ValueOrGlob::Glob(Glob));
2498 }
2499 other => panic!("expected ReadSelector, got {other}"),
2500 }
2501 }
2502
2503 #[test]
2504 fn access_control_selector_from_str_specific_parses_correctly() {
2505 // Arrange
2506 let input = "Read(Students.name)";
2507
2508 // Act
2509 let selector = input.parse::<AccessControlSelector>().unwrap();
2510
2511 // Assert
2512 match selector {
2513 AccessControlSelector::Read(read) => {
2514 assert_eq!(
2515 read.table_name,
2516 ValueOrGlob::Value("Students".to_string())
2517 );
2518 assert_eq!(
2519 read.column_name,
2520 ValueOrGlob::Value("name".to_string())
2521 );
2522 }
2523 other => panic!("expected ReadSelector, got {other}"),
2524 }
2525 }
2526
2527 #[test]
2528 fn access_control_selector_display_round_trip_works() {
2529 // Arrange
2530 let input = "Read(Students.name)";
2531
2532 // Act
2533 let selector = input.parse::<AccessControlSelector>().unwrap();
2534 let displayed = selector.to_string();
2535 let reparsed = displayed.parse::<AccessControlSelector>().unwrap();
2536
2537 // Assert
2538 match reparsed {
2539 AccessControlSelector::Read(read) => {
2540 assert_eq!(
2541 read.table_name,
2542 ValueOrGlob::Value("Students".to_string())
2543 );
2544 assert_eq!(
2545 read.column_name,
2546 ValueOrGlob::Value("name".to_string())
2547 );
2548 }
2549 other => panic!("expected ReadSelector, got {other}"),
2550 }
2551 }
2552
2553 #[test]
2554 fn access_control_selector_try_from_string_works() {
2555 // Arrange
2556 let input = "Read(Students.name)".to_string();
2557
2558 // Act
2559 let selector = AccessControlSelector::try_from(input).unwrap();
2560
2561 // Assert
2562 match selector {
2563 AccessControlSelector::Read(read) => {
2564 assert_eq!(
2565 read.table_name,
2566 ValueOrGlob::Value("Students".to_string())
2567 );
2568 assert_eq!(
2569 read.column_name,
2570 ValueOrGlob::Value("name".to_string())
2571 );
2572 }
2573 other => panic!("expected ReadSelector, got {other}"),
2574 }
2575 }
2576
2577 #[test]
2578 fn parsing_with_missing_closing_paren_returns_error() {
2579 // Arrange
2580 let input = "Read(Students.name";
2581
2582 // Act
2583 let result = input.parse::<Read>();
2584
2585 // Assert
2586 assert!(matches!(
2587 result,
2588 Err(AccessControlSelectorParseError::NoClosingBraceFound { .. })
2589 ));
2590 }
2591
2592 #[test]
2593 fn parsing_with_too_many_fields_returns_error() {
2594 // Arrange
2595 let input = "Read(Students.name.extra)";
2596
2597 // Act
2598 let result = input.parse::<Read>();
2599
2600 // Assert
2601 assert!(matches!(
2602 result,
2603 Err(AccessControlSelectorParseError::InvalidNumberOfFields { .. })
2604 ));
2605 }
2606
2607 #[test]
2608 fn parsing_with_wrong_identifier_returns_error_for_struct() {
2609 // Arrange
2610 let input = "Write(Students.name)";
2611
2612 // Act
2613 let result = input.parse::<Read>();
2614
2615 // Assert
2616 assert!(matches!(
2617 result,
2618 Err(
2619 AccessControlSelectorParseError::IncorrectAccessControlSelectorIdentifier {
2620 ..
2621 }
2622 )
2623 ));
2624 }
2625
2626 #[test]
2627 fn parsing_with_wrong_identifier_returns_error_for_enum() {
2628 // Arrange
2629 let input = "Write(Students.name)";
2630
2631 // Act
2632 let result = input.parse::<AccessControlSelector>();
2633
2634 // Assert
2635 assert!(matches!(
2636 result,
2637 Err(
2638 AccessControlSelectorParseError::InvalidAccessControlSelectorIdentifier {
2639 ..
2640 }
2641 )
2642 ));
2643 }
2644
2645 #[test]
2646 fn parsing_empty_string_returns_error_for_enum() {
2647 // Arrange
2648 let input = "";
2649
2650 // Act
2651 let result = input.parse::<AccessControlSelector>();
2652
2653 // Assert
2654 assert!(result.is_err());
2655 }
2656
2657 #[test]
2658 fn parsing_empty_string_returns_error_for_struct() {
2659 // Arrange
2660 let input = "";
2661
2662 // Act
2663 let result = input.parse::<Read>();
2664
2665 // Assert
2666 assert!(matches!(
2667 result,
2668 Err(
2669 AccessControlSelectorParseError::IncorrectAccessControlSelectorIdentifier {
2670 ..
2671 }
2672 )
2673 ));
2674 }
2675
2676 #[test]
2677 fn empty_selector_set_returns_none_for_read() {
2678 // Arrange
2679 let set = AccessControlSelectorSet::new();
2680
2681 // Act
2682 let result = set.check_read("Students", "name");
2683
2684 // Assert
2685 assert_eq!(result, None);
2686 }
2687
2688 #[test]
2689 fn single_allow_rule_matches_and_returns_allow() {
2690 // Arrange
2691 let set = AccessControlSelectorSet::new()
2692 .with_read_selector(Read::empty(), true);
2693
2694 // Act
2695 let result = set.check_read("Students", "name");
2696
2697 // Assert
2698 assert_eq!(result, Some(Authorization::Allow));
2699 }
2700
2701 #[test]
2702 fn single_deny_rule_matches_and_returns_deny() {
2703 // Arrange
2704 let set = AccessControlSelectorSet::new()
2705 .with_read_selector(Read::empty(), false);
2706
2707 // Act
2708 let result = set.check_read("Students", "name");
2709
2710 // Assert
2711 assert_eq!(result, Some(Authorization::Deny));
2712 }
2713
2714 #[test]
2715 fn single_allow_rule_that_does_not_match_returns_none() {
2716 // Arrange
2717 let set = AccessControlSelectorSet::new().with_read_selector(
2718 Read::new(
2719 ValueOrGlob::new_value("Secrets"),
2720 ValueOrGlob::new_glob(),
2721 ),
2722 true,
2723 );
2724
2725 // Act
2726 let result = set.check_read("Students", "name");
2727
2728 // Assert
2729 assert_eq!(result, None);
2730 }
2731
2732 #[test]
2733 fn single_deny_rule_that_does_not_match_returns_none() {
2734 // Arrange
2735 let set = AccessControlSelectorSet::new().with_read_selector(
2736 Read::new(
2737 ValueOrGlob::new_value("Secrets"),
2738 ValueOrGlob::new_glob(),
2739 ),
2740 false,
2741 );
2742
2743 // Act
2744 let result = set.check_read("Students", "name");
2745
2746 // Assert
2747 assert_eq!(result, None);
2748 }
2749
2750 #[test]
2751 fn higher_specificity_allow_beats_lower_specificity_deny() {
2752 // Arrange
2753 let set = AccessControlSelectorSet::new()
2754 .with_read_selector(Read::empty(), false)
2755 .with_read_selector(
2756 Read::new(
2757 ValueOrGlob::new_value("Students"),
2758 ValueOrGlob::new_glob(),
2759 ),
2760 true,
2761 );
2762
2763 // Act
2764 let result = set.check_read("Students", "name");
2765
2766 // Assert
2767 assert_eq!(result, Some(Authorization::Allow));
2768 }
2769
2770 #[test]
2771 fn higher_specificity_deny_beats_lower_specificity_allow() {
2772 // Arrange
2773 let set = AccessControlSelectorSet::new()
2774 .with_read_selector(Read::empty(), true)
2775 .with_read_selector(
2776 Read::new(
2777 ValueOrGlob::new_value("Secrets"),
2778 ValueOrGlob::new_glob(),
2779 ),
2780 false,
2781 );
2782
2783 // Act
2784 let result = set.check_read("Secrets", "ssn");
2785
2786 // Assert
2787 assert_eq!(result, Some(Authorization::Deny));
2788 }
2789
2790 #[test]
2791 fn specificity_2_beats_specificity_1_beats_specificity_0() {
2792 // Arrange
2793 let set = AccessControlSelectorSet::new()
2794 .with_read_selector(Read::empty(), true)
2795 .with_read_selector(
2796 Read::new(
2797 ValueOrGlob::new_value("Secrets"),
2798 ValueOrGlob::new_glob(),
2799 ),
2800 false,
2801 )
2802 .with_read_selector(
2803 Read::new(
2804 ValueOrGlob::new_value("Secrets"),
2805 ValueOrGlob::new_value("id"),
2806 ),
2807 true,
2808 );
2809
2810 // Act
2811 let allow_all = set.check_read("Public", "data");
2812 let deny_table = set.check_read("Secrets", "ssn");
2813 let allow_carveout = set.check_read("Secrets", "id");
2814
2815 // Assert
2816 assert_eq!(allow_all, Some(Authorization::Allow));
2817 assert_eq!(deny_table, Some(Authorization::Deny));
2818 assert_eq!(allow_carveout, Some(Authorization::Allow));
2819 }
2820
2821 #[test]
2822 fn same_specificity_allow_and_deny_resolves_to_deny() {
2823 // Arrange
2824 let set = AccessControlSelectorSet::new()
2825 .with_read_selector(
2826 Read::new(
2827 ValueOrGlob::new_value("Students"),
2828 ValueOrGlob::new_glob(),
2829 ),
2830 true,
2831 )
2832 .with_read_selector(
2833 Read::new(
2834 ValueOrGlob::new_glob(),
2835 ValueOrGlob::new_value("name"),
2836 ),
2837 false,
2838 );
2839
2840 // Act
2841 let result = set.check_read("Students", "name");
2842
2843 // Assert
2844 assert_eq!(result, Some(Authorization::Deny));
2845 }
2846
2847 #[test]
2848 fn same_specificity_multiple_allows_no_deny_returns_allow() {
2849 // Arrange
2850 let set = AccessControlSelectorSet::new()
2851 .with_read_selector(
2852 Read::new(ValueOrGlob::new_value("A"), ValueOrGlob::new_glob()),
2853 true,
2854 )
2855 .with_read_selector(
2856 Read::new(ValueOrGlob::new_glob(), ValueOrGlob::new_value("x")),
2857 true,
2858 );
2859
2860 // Act
2861 let result = set.check_read("A", "x");
2862
2863 // Assert
2864 assert_eq!(result, Some(Authorization::Allow));
2865 }
2866
2867 #[test]
2868 fn same_specificity_multiple_denys_no_allow_returns_deny() {
2869 // Arrange
2870 let set = AccessControlSelectorSet::new()
2871 .with_read_selector(
2872 Read::new(ValueOrGlob::new_value("A"), ValueOrGlob::new_glob()),
2873 false,
2874 )
2875 .with_read_selector(
2876 Read::new(ValueOrGlob::new_glob(), ValueOrGlob::new_value("x")),
2877 false,
2878 );
2879
2880 // Act
2881 let result = set.check_read("A", "x");
2882
2883 // Assert
2884 assert_eq!(result, Some(Authorization::Deny));
2885 }
2886
2887 #[test]
2888 fn non_matching_rules_are_skipped_to_lower_specificity() {
2889 // Arrange
2890 let set = AccessControlSelectorSet::new()
2891 .with_read_selector(Read::empty(), true)
2892 .with_read_selector(
2893 Read::new(
2894 ValueOrGlob::new_value("Other"),
2895 ValueOrGlob::new_glob(),
2896 ),
2897 false,
2898 );
2899
2900 // Act
2901 let result = set.check_read("Students", "name");
2902
2903 // Assert
2904 assert_eq!(result, Some(Authorization::Allow));
2905 }
2906
2907 #[test]
2908 fn rules_with_specificity_gap_still_resolve_correctly() {
2909 // Arrange
2910 let set = AccessControlSelectorSet::new()
2911 .with_read_selector(Read::empty(), false)
2912 .with_read_selector(
2913 Read::new(
2914 ValueOrGlob::new_value("Students"),
2915 ValueOrGlob::new_value("name"),
2916 ),
2917 true,
2918 );
2919
2920 // Act
2921 let carveout = set.check_read("Students", "name");
2922 let blocked = set.check_read("Students", "ssn");
2923
2924 // Assert
2925 assert_eq!(carveout, Some(Authorization::Allow));
2926 assert_eq!(blocked, Some(Authorization::Deny));
2927 }
2928
2929 #[test]
2930 fn glob_table_pinned_column_matches_any_table() {
2931 // Arrange
2932 let set = AccessControlSelectorSet::new().with_read_selector(
2933 Read::new(ValueOrGlob::new_glob(), ValueOrGlob::new_value("ssn")),
2934 false,
2935 );
2936
2937 // Act
2938 let result_a = set.check_read("Students", "ssn");
2939 let result_b = set.check_read("Employees", "ssn");
2940 let result_c = set.check_read("Students", "name");
2941
2942 // Assert
2943 assert_eq!(result_a, Some(Authorization::Deny));
2944 assert_eq!(result_b, Some(Authorization::Deny));
2945 assert_eq!(result_c, None);
2946 }
2947
2948 #[test]
2949 fn pinned_table_glob_column_matches_any_column() {
2950 // Arrange
2951 let set = AccessControlSelectorSet::new().with_read_selector(
2952 Read::new(
2953 ValueOrGlob::new_value("Secrets"),
2954 ValueOrGlob::new_glob(),
2955 ),
2956 false,
2957 );
2958
2959 // Act
2960 let result_a = set.check_read("Secrets", "ssn");
2961 let result_b = set.check_read("Secrets", "id");
2962 let result_c = set.check_read("Public", "data");
2963
2964 // Assert
2965 assert_eq!(result_a, Some(Authorization::Deny));
2966 assert_eq!(result_b, Some(Authorization::Deny));
2967 assert_eq!(result_c, None);
2968 }
2969
2970 #[test]
2971 fn fully_pinned_selector_matches_only_exact_pair() {
2972 // Arrange
2973 let set = AccessControlSelectorSet::new().with_read_selector(
2974 Read::new(
2975 ValueOrGlob::new_value("Students"),
2976 ValueOrGlob::new_value("name"),
2977 ),
2978 false,
2979 );
2980
2981 // Act
2982 let exact = set.check_read("Students", "name");
2983 let wrong_col = set.check_read("Students", "id");
2984 let wrong_table = set.check_read("Other", "name");
2985
2986 // Assert
2987 assert_eq!(exact, Some(Authorization::Deny));
2988 assert_eq!(wrong_col, None);
2989 assert_eq!(wrong_table, None);
2990 }
2991
2992 #[test]
2993 fn read_rules_do_not_affect_delete_checks() {
2994 // Arrange
2995 let set = AccessControlSelectorSet::new()
2996 .with_read_selector(Read::empty(), false);
2997
2998 // Act
2999 let read_result = set.check_read("Students", "name");
3000 let delete_result = set.check_delete("Students");
3001
3002 // Assert
3003 assert_eq!(read_result, Some(Authorization::Deny));
3004 assert_eq!(delete_result, None);
3005 }
3006
3007 #[test]
3008 fn delete_single_field_selector_works() {
3009 // Arrange
3010 let set = AccessControlSelectorSet::new()
3011 .with_delete_selector(Delete::empty(), true)
3012 .with_delete_selector(
3013 Delete::new(ValueOrGlob::new_value("AuditLog")),
3014 false,
3015 );
3016
3017 // Act
3018 let allowed = set.check_delete("Students");
3019 let denied = set.check_delete("AuditLog");
3020
3021 // Assert
3022 assert_eq!(allowed, Some(Authorization::Allow));
3023 assert_eq!(denied, Some(Authorization::Deny));
3024 }
3025
3026 #[test]
3027 fn select_zero_field_selector_allow_works() {
3028 // Arrange
3029 let set = AccessControlSelectorSet::new()
3030 .with_select_selector(Select {}, true);
3031
3032 // Act
3033 let result = set.check_select();
3034
3035 // Assert
3036 assert_eq!(result, Some(Authorization::Allow));
3037 }
3038
3039 #[test]
3040 fn select_zero_field_allow_and_deny_resolves_to_deny() {
3041 // Arrange
3042 let set = AccessControlSelectorSet::new()
3043 .with_select_selector(Select {}, true)
3044 .with_select_selector(Select {}, false);
3045
3046 // Act
3047 let result = set.check_select();
3048
3049 // Assert
3050 assert_eq!(result, Some(Authorization::Deny));
3051 }
3052
3053 #[test]
3054 fn with_selector_dispatches_to_correct_type() {
3055 // Arrange
3056 let read = AccessControlSelector::from(Read::empty());
3057 let set = AccessControlSelectorSet::new().with_selector(read, false);
3058
3059 // Act
3060 let result = set.check_read("anything", "anything");
3061
3062 // Assert
3063 assert_eq!(result, Some(Authorization::Deny));
3064 }
3065
3066 #[test]
3067 fn resolver_allow_everything_defaults_to_allow() {
3068 // Arrange
3069 let resolver = AuthorizationResolver::new_allow_everything();
3070
3071 // Act
3072 let result = resolver.selector_set.check_read("X", "Y");
3073
3074 // Assert
3075 assert_eq!(result, None);
3076 assert_eq!(
3077 resolver.read_default_permissions,
3078 rusqlite::hooks::Authorization::Allow,
3079 );
3080 }
3081
3082 #[test]
3083 fn resolver_deny_everything_defaults_to_deny() {
3084 // Arrange
3085 let resolver = AuthorizationResolver::new_deny_everything();
3086
3087 // Act
3088 let result = resolver.selector_set.check_read("X", "Y");
3089
3090 // Assert
3091 assert_eq!(result, None);
3092 assert_eq!(
3093 resolver.read_default_permissions,
3094 rusqlite::hooks::Authorization::Deny,
3095 );
3096 }
3097
3098 #[test]
3099 fn resolver_per_action_default_override_works() {
3100 // Arrange
3101 let resolver = AuthorizationResolver::new_allow_everything()
3102 .with_read_default_permissions(
3103 rusqlite::hooks::Authorization::Deny,
3104 );
3105
3106 // Act / Assert
3107 assert_eq!(
3108 resolver.read_default_permissions,
3109 rusqlite::hooks::Authorization::Deny,
3110 );
3111 assert_eq!(
3112 resolver.insert_default_permissions,
3113 rusqlite::hooks::Authorization::Allow,
3114 );
3115 }
3116
3117 #[test]
3118 fn doc_example_read_only_with_table_blocked() {
3119 // Arrange
3120 let set = AccessControlSelectorSet::new()
3121 .with_selector("Read".parse::<Read>().unwrap(), true)
3122 .with_selector("Read(Secrets)".parse::<Read>().unwrap(), false);
3123
3124 // Act
3125 let public_read = set.check_read("Public", "data");
3126 let secrets_read = set.check_read("Secrets", "ssn");
3127
3128 // Assert
3129 assert_eq!(public_read, Some(Authorization::Allow));
3130 assert_eq!(secrets_read, Some(Authorization::Deny));
3131 }
3132
3133 #[test]
3134 fn doc_example_read_only_with_carveout() {
3135 // Arrange
3136 let set = AccessControlSelectorSet::new()
3137 .with_selector("Read".parse::<Read>().unwrap(), true)
3138 .with_selector("Read(Secrets)".parse::<Read>().unwrap(), false)
3139 .with_selector("Read(Secrets.id)".parse::<Read>().unwrap(), true);
3140
3141 // Act
3142 let public = set.check_read("Public", "data");
3143 let secrets_ssn = set.check_read("Secrets", "ssn");
3144 let secrets_id = set.check_read("Secrets", "id");
3145
3146 // Assert
3147 assert_eq!(public, Some(Authorization::Allow));
3148 assert_eq!(secrets_ssn, Some(Authorization::Deny));
3149 assert_eq!(secrets_id, Some(Authorization::Allow));
3150 }
3151
3152 #[test]
3153 fn doc_example_conflicting_same_specificity() {
3154 // Arrange
3155 let set = AccessControlSelectorSet::new()
3156 .with_selector("Read(Students)".parse::<Read>().unwrap(), false)
3157 .with_selector("Read(*.name)".parse::<Read>().unwrap(), true);
3158
3159 // Act
3160 let result = set.check_read("Students", "name");
3161
3162 // Assert
3163 assert_eq!(result, Some(Authorization::Deny));
3164 }
3165
3166 #[test]
3167 fn doc_example_deny_functions_allow_specific() {
3168 // Arrange
3169 let set = AccessControlSelectorSet::new()
3170 .with_selector("Function".parse::<Function>().unwrap(), false)
3171 .with_selector("Function(count)".parse::<Function>().unwrap(), true)
3172 .with_selector("Function(sum)".parse::<Function>().unwrap(), true);
3173
3174 // Act
3175 let count = set.check_function("count");
3176 let sum = set.check_function("sum");
3177 let evil = set.check_function("load_extension");
3178
3179 // Assert
3180 assert_eq!(count, Some(Authorization::Allow));
3181 assert_eq!(sum, Some(Authorization::Allow));
3182 assert_eq!(evil, Some(Authorization::Deny));
3183 }
3184
3185 #[test]
3186 fn multiple_non_matching_rules_returns_none() {
3187 // Arrange
3188 let set = AccessControlSelectorSet::new()
3189 .with_read_selector(
3190 Read::new(
3191 ValueOrGlob::new_value("A"),
3192 ValueOrGlob::new_value("x"),
3193 ),
3194 true,
3195 )
3196 .with_read_selector(
3197 Read::new(
3198 ValueOrGlob::new_value("B"),
3199 ValueOrGlob::new_value("y"),
3200 ),
3201 false,
3202 );
3203
3204 // Act
3205 let result = set.check_read("C", "z");
3206
3207 // Assert
3208 assert_eq!(result, None);
3209 }
3210
3211 #[test]
3212 fn deny_at_higher_specificity_is_not_overridden_by_lower_allow() {
3213 // Arrange
3214 let set = AccessControlSelectorSet::new()
3215 .with_read_selector(Read::empty(), true)
3216 .with_read_selector(
3217 Read::new(
3218 ValueOrGlob::new_value("Secrets"),
3219 ValueOrGlob::new_value("ssn"),
3220 ),
3221 false,
3222 );
3223
3224 // Act
3225 let result = set.check_read("Secrets", "ssn");
3226
3227 // Assert
3228 assert_eq!(result, Some(Authorization::Deny));
3229 }
3230
3231 #[test]
3232 fn only_matching_rules_at_a_level_contribute_to_verdict() {
3233 // Arrange
3234 let set = AccessControlSelectorSet::new()
3235 .with_read_selector(
3236 Read::new(ValueOrGlob::new_value("A"), ValueOrGlob::new_glob()),
3237 false,
3238 )
3239 .with_read_selector(
3240 Read::new(ValueOrGlob::new_value("B"), ValueOrGlob::new_glob()),
3241 true,
3242 );
3243
3244 // Act
3245 let result_a = set.check_read("A", "x");
3246 let result_b = set.check_read("B", "x");
3247
3248 // Assert
3249 assert_eq!(result_a, Some(Authorization::Deny));
3250 assert_eq!(result_b, Some(Authorization::Allow));
3251 }
3252
3253 #[test]
3254 fn insertion_order_does_not_affect_specificity_ranking() {
3255 // Arrange
3256 let set = AccessControlSelectorSet::new()
3257 .with_read_selector(
3258 Read::new(
3259 ValueOrGlob::new_value("T"),
3260 ValueOrGlob::new_value("c"),
3261 ),
3262 true,
3263 )
3264 .with_read_selector(Read::empty(), false);
3265
3266 // Act
3267 let result = set.check_read("T", "c");
3268
3269 // Assert
3270 assert_eq!(result, Some(Authorization::Allow));
3271 }
3272
3273 #[test]
3274 fn three_specificity_levels_middle_deny_overridden_by_top() {
3275 // Arrange
3276 let set = AccessControlSelectorSet::new()
3277 .with_read_selector(Read::empty(), true)
3278 .with_read_selector(
3279 Read::new(ValueOrGlob::new_value("T"), ValueOrGlob::new_glob()),
3280 false,
3281 )
3282 .with_read_selector(
3283 Read::new(
3284 ValueOrGlob::new_value("T"),
3285 ValueOrGlob::new_value("ok"),
3286 ),
3287 true,
3288 );
3289
3290 // Act
3291 let other_table = set.check_read("X", "y");
3292 let denied_col = set.check_read("T", "secret");
3293 let carveout = set.check_read("T", "ok");
3294
3295 // Assert
3296 assert_eq!(other_table, Some(Authorization::Allow));
3297 assert_eq!(denied_col, Some(Authorization::Deny));
3298 assert_eq!(carveout, Some(Authorization::Allow));
3299 }
3300
3301 #[test]
3302 fn duplicate_allow_rules_still_return_allow() {
3303 // Arrange
3304 let set = AccessControlSelectorSet::new()
3305 .with_read_selector(Read::empty(), true)
3306 .with_read_selector(Read::empty(), true);
3307
3308 // Act
3309 let result = set.check_read("T", "c");
3310
3311 // Assert
3312 assert_eq!(result, Some(Authorization::Allow));
3313 }
3314
3315 #[test]
3316 fn duplicate_deny_rules_still_return_deny() {
3317 // Arrange
3318 let set = AccessControlSelectorSet::new()
3319 .with_read_selector(Read::empty(), false)
3320 .with_read_selector(Read::empty(), false);
3321
3322 // Act
3323 let result = set.check_read("T", "c");
3324
3325 // Assert
3326 assert_eq!(result, Some(Authorization::Deny));
3327 }
3328
3329 #[test]
3330 fn read_only_allows_reads_and_selects() {
3331 // Arrange
3332 let r = AuthorizationResolver::new_read_only();
3333
3334 // Act / Assert
3335 assert_eq!(
3336 r.read_default_permissions,
3337 rusqlite::hooks::Authorization::Allow,
3338 );
3339 assert_eq!(
3340 r.select_default_permissions,
3341 rusqlite::hooks::Authorization::Allow,
3342 );
3343 assert_eq!(
3344 r.transaction_default_permissions,
3345 rusqlite::hooks::Authorization::Allow,
3346 );
3347 assert_eq!(
3348 r.function_default_permissions,
3349 rusqlite::hooks::Authorization::Allow,
3350 );
3351 assert_eq!(
3352 r.recursive_default_permissions,
3353 rusqlite::hooks::Authorization::Allow,
3354 );
3355 assert_eq!(
3356 r.pragma_default_permissions,
3357 rusqlite::hooks::Authorization::Allow,
3358 );
3359 }
3360
3361 #[test]
3362 fn read_only_denies_writes_and_ddl() {
3363 // Arrange
3364 let r = AuthorizationResolver::new_read_only();
3365
3366 // Act / Assert
3367 assert_eq!(
3368 r.insert_default_permissions,
3369 rusqlite::hooks::Authorization::Deny,
3370 );
3371 assert_eq!(
3372 r.update_default_permissions,
3373 rusqlite::hooks::Authorization::Deny,
3374 );
3375 assert_eq!(
3376 r.delete_default_permissions,
3377 rusqlite::hooks::Authorization::Deny,
3378 );
3379 assert_eq!(
3380 r.create_table_default_permissions,
3381 rusqlite::hooks::Authorization::Deny,
3382 );
3383 assert_eq!(
3384 r.drop_table_default_permissions,
3385 rusqlite::hooks::Authorization::Deny,
3386 );
3387 assert_eq!(
3388 r.attach_default_permissions,
3389 rusqlite::hooks::Authorization::Deny,
3390 );
3391 }
3392
3393 #[test]
3394 fn read_write_allows_data_modification() {
3395 // Arrange
3396 let r = AuthorizationResolver::new_read_write();
3397
3398 // Act / Assert
3399 assert_eq!(
3400 r.insert_default_permissions,
3401 rusqlite::hooks::Authorization::Allow,
3402 );
3403 assert_eq!(
3404 r.update_default_permissions,
3405 rusqlite::hooks::Authorization::Allow,
3406 );
3407 assert_eq!(
3408 r.delete_default_permissions,
3409 rusqlite::hooks::Authorization::Allow,
3410 );
3411 assert_eq!(
3412 r.savepoint_default_permissions,
3413 rusqlite::hooks::Authorization::Allow,
3414 );
3415 assert_eq!(
3416 r.analyze_default_permissions,
3417 rusqlite::hooks::Authorization::Allow,
3418 );
3419 }
3420
3421 #[test]
3422 fn read_write_allows_temp_objects_but_denies_permanent_ddl() {
3423 // Arrange
3424 let r = AuthorizationResolver::new_read_write();
3425
3426 // Act / Assert
3427 assert_eq!(
3428 r.create_temp_table_default_permissions,
3429 rusqlite::hooks::Authorization::Allow,
3430 );
3431 assert_eq!(
3432 r.drop_temp_table_default_permissions,
3433 rusqlite::hooks::Authorization::Allow,
3434 );
3435 assert_eq!(
3436 r.create_table_default_permissions,
3437 rusqlite::hooks::Authorization::Deny,
3438 );
3439 assert_eq!(
3440 r.drop_table_default_permissions,
3441 rusqlite::hooks::Authorization::Deny,
3442 );
3443 assert_eq!(
3444 r.alter_table_default_permissions,
3445 rusqlite::hooks::Authorization::Deny,
3446 );
3447 assert_eq!(
3448 r.attach_default_permissions,
3449 rusqlite::hooks::Authorization::Deny,
3450 );
3451 }
3452
3453 #[test]
3454 fn full_ddl_allows_permanent_schema_changes() {
3455 // Arrange
3456 let r = AuthorizationResolver::new_full_ddl();
3457
3458 // Act / Assert
3459 assert_eq!(
3460 r.create_table_default_permissions,
3461 rusqlite::hooks::Authorization::Allow,
3462 );
3463 assert_eq!(
3464 r.drop_table_default_permissions,
3465 rusqlite::hooks::Authorization::Allow,
3466 );
3467 assert_eq!(
3468 r.alter_table_default_permissions,
3469 rusqlite::hooks::Authorization::Allow,
3470 );
3471 assert_eq!(
3472 r.create_index_default_permissions,
3473 rusqlite::hooks::Authorization::Allow,
3474 );
3475 assert_eq!(
3476 r.create_view_default_permissions,
3477 rusqlite::hooks::Authorization::Allow,
3478 );
3479 assert_eq!(
3480 r.create_trigger_default_permissions,
3481 rusqlite::hooks::Authorization::Allow,
3482 );
3483 }
3484
3485 #[test]
3486 fn full_ddl_still_denies_attach_detach_and_vtables() {
3487 // Arrange
3488 let r = AuthorizationResolver::new_full_ddl();
3489
3490 // Act / Assert
3491 assert_eq!(
3492 r.attach_default_permissions,
3493 rusqlite::hooks::Authorization::Deny,
3494 );
3495 assert_eq!(
3496 r.detach_default_permissions,
3497 rusqlite::hooks::Authorization::Deny,
3498 );
3499 assert_eq!(
3500 r.create_vtable_default_permissions,
3501 rusqlite::hooks::Authorization::Deny,
3502 );
3503 assert_eq!(
3504 r.drop_vtable_default_permissions,
3505 rusqlite::hooks::Authorization::Deny,
3506 );
3507 }
3508
3509 #[test]
3510 fn presets_are_composable_with_per_action_overrides() {
3511 // Arrange
3512 let r = AuthorizationResolver::new_read_only()
3513 .with_insert_default_permissions(
3514 rusqlite::hooks::Authorization::Allow,
3515 );
3516
3517 // Act / Assert
3518 assert_eq!(
3519 r.insert_default_permissions,
3520 rusqlite::hooks::Authorization::Allow,
3521 );
3522 assert_eq!(
3523 r.update_default_permissions,
3524 rusqlite::hooks::Authorization::Deny,
3525 );
3526 assert_eq!(
3527 r.read_default_permissions,
3528 rusqlite::hooks::Authorization::Allow,
3529 );
3530 }
3531}