axum_gate/permissions/
mod.rs

1//! Zero-synchronization permission system using deterministic hashing.
2//!
3//! This module provides a permission system where permission IDs are computed deterministically
4//! from permission names using cryptographic hashing. This eliminates the need for synchronization
5//! between distributed nodes while maintaining high performance through bitmap operations.
6//!
7//! # Key Features
8//!
9//! - **Deterministic hashing** - Same permission names always produce the same IDs
10//! - **Zero synchronization** - No coordination needed between distributed nodes
11//! - **Fast lookups** - Bitmap-based storage for O(1) permission checks
12//! - **Collision detection** - Compile-time and runtime validation to prevent hash collisions
13//!
14//! # Using Permissions in Your Application
15//!
16//! ## 1. Validating Permissions at Compile Time
17//!
18//! ```rust
19//! use axum_gate::permissions::PermissionId;
20//! axum_gate::validate_permissions![
21//!     "read:resource1",
22//!     "write:resource1",
23//!     "admin:system",
24//! ];
25//! ```
26//!
27//! ## 2. Working with Account Permissions
28//!
29//! ```rust
30//! use axum_gate::permissions::{PermissionId, Permissions};
31//! use axum_gate::accounts::Account;
32//! use axum_gate::prelude::{Role, Group};
33//!
34//! let mut account = Account::<Role, Group>::new("user123", &[Role::User], &[Group::new("staff")]);
35//!
36//! // Grant permissions to an account
37//! account.grant_permission("read:resource1");
38//! account.grant_permission("write:resource1");
39//!
40//! // Check if account has permission
41//! if account.permissions.has("read:resource1") {
42//!     // Account has permission
43//! }
44//!
45//! // Revoke permissions from an account
46//! account.revoke_permission("write:resource1");
47//! ```
48//!
49//! ## 3. Using Permissions with Access Policies
50//!
51//! ```rust
52//! use axum_gate::authz::AccessPolicy;
53//! use axum_gate::permissions::PermissionId;
54//! use axum_gate::prelude::{Gate, Role, Group};
55//! use axum_gate::codecs::jwt::{JsonWebToken, JwtClaims};
56//! use axum_gate::accounts::Account;
57//! use std::sync::Arc;
58//! use axum::{routing::get, Router};
59//!
60//! # let jwt_codec = Arc::new(JsonWebToken::<JwtClaims<Account<Role, Group>>>::default());
61//! let app = Router::<()>::new()
62//!     .route("/protected", get(protected_handler))
63//!     .layer(
64//!         Gate::cookie("issuer", jwt_codec)
65//!             .with_policy(AccessPolicy::<Role, Group>::require_permission(
66//!                 PermissionId::from("read:resource1")
67//!             ))
68//!     );
69//!
70//! async fn protected_handler() -> &'static str {
71//!     "Access granted!"
72//! }
73//! ```
74//!
75//! ## 4. Working with Permission Collections
76//!
77//! ```rust
78//! use axum_gate::permissions::Permissions;
79//!
80//! // Create permissions from an iterator
81//! let permissions: Permissions = ["read:api", "write:api", "admin:system"].into_iter().collect();
82//!
83//! // Check for multiple permissions
84//! if permissions.has_all(["read:api", "write:api"]) {
85//!     println!("Has both read and write access");
86//! }
87//!
88//! if permissions.has_any(["admin:system", "super:admin"]) {
89//!     println!("Has admin access");
90//! }
91//! ```
92//!
93//! ## 5. Using Custom Enums with `AsPermissionName`
94//!
95//! Define your permissions as enums, implement `AsPermissionName` to map them to stable, readable names, and use them anywhere a permission is accepted.
96//!
97//! ```rust
98//! use axum_gate::authz::AccessPolicy;
99//! use axum_gate::permissions::{AsPermissionName, Permissions};
100//! use axum_gate::prelude::{Role, Group};
101//!
102//! #[derive(Debug)]
103//! enum Api {
104//!     Read,
105//!     Write,
106//! }
107//!
108//! #[derive(Debug)]
109//! enum AppPermission {
110//!     Api(Api),
111//!     System(&'static str),
112//! }
113//!
114//! // Map enums to stable permission names used across your app
115//! impl AsPermissionName for AppPermission {
116//!     fn as_permission_name(&self) -> String {
117//!         match self {
118//!             AppPermission::Api(api) => format!("api:{:?}", api).to_lowercase(),
119//!             AppPermission::System(s) => format!("system:{s}"),
120//!         }
121//!     }
122//! }
123//!
124//! // Grant/check with your enums
125//! let mut perms = Permissions::new();
126//! perms.grant(&AppPermission::Api(Api::Read));
127//! assert!(perms.has(&AppPermission::Api(Api::Read)));
128//!
129//! // Use in access policies
130//! let policy: AccessPolicy<Role, Group> =
131//!     AccessPolicy::require_permission(&AppPermission::Api(Api::Read));
132//! ```
133
134#[cfg(feature = "server")]
135mod server_impl {
136    pub use super::application_validator::ApplicationValidator;
137    pub use super::collision_checker::PermissionCollisionChecker;
138    pub use super::errors::PermissionsError;
139    pub use super::permission_collision::PermissionCollision;
140    pub use super::validation_report::ValidationReport;
141}
142
143#[cfg(feature = "server")]
144pub use server_impl::*;
145
146pub use self::as_permission_name::AsPermissionName;
147pub use self::permission_id::PermissionId;
148use roaring::RoaringTreemap;
149use serde::{Deserialize, Serialize};
150use std::fmt;
151
152#[cfg(feature = "server")]
153mod application_validator;
154mod as_permission_name;
155#[cfg(feature = "server")]
156mod collision_checker;
157#[cfg(feature = "server")]
158pub mod errors;
159#[cfg(feature = "server")]
160pub mod mapping;
161#[cfg(feature = "server")]
162mod permission_collision;
163mod permission_id;
164#[cfg(feature = "server")]
165pub mod validate_permissions;
166#[cfg(feature = "server")]
167mod validation_report;
168
169/// A collection of permissions with efficient storage and fast operations.
170///
171/// Uses compressed bitmap storage internally for optimal memory usage and O(1)
172/// permission checks. Designed for high-performance authorization systems that
173/// need to handle thousands of permissions per user efficiently.
174///
175/// # Examples
176///
177/// ```rust
178/// use axum_gate::permissions::Permissions;
179///
180/// // Create and populate permissions
181/// let mut permissions = Permissions::new();
182/// permissions
183///     .grant("read:profile")
184///     .grant("write:profile")
185///     .grant("delete:profile");
186///
187/// // Check individual permissions
188/// assert!(permissions.has("read:profile"));
189/// assert!(!permissions.has("admin:users"));
190///
191/// // Check multiple permissions
192/// assert!(permissions.has_all(["read:profile", "write:profile"]));
193/// assert!(permissions.has_any(["read:profile", "admin:users"]));
194/// ```
195///
196/// # Builder Pattern
197///
198/// ```rust
199/// use axum_gate::permissions::Permissions;
200///
201/// let permissions = Permissions::new()
202///     .with("read:api")
203///     .with("write:api")
204///     .build();
205/// ```
206#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
207pub struct Permissions {
208    bitmap: RoaringTreemap,
209}
210
211impl Permissions {
212    /// Creates a new empty permission set.
213    ///
214    /// # Examples
215    ///
216    /// ```rust
217    /// use axum_gate::permissions::Permissions;
218    ///
219    /// let permissions = Permissions::new();
220    /// assert!(permissions.is_empty());
221    /// ```
222    pub fn new() -> Self {
223        Self {
224            bitmap: RoaringTreemap::new(),
225        }
226    }
227
228    /// Grants a permission to this permission set.
229    ///
230    /// Returns a mutable reference to self for method chaining.
231    ///
232    /// # Examples
233    ///
234    /// ```rust
235    /// use axum_gate::permissions::{Permissions, PermissionId};
236    ///
237    /// let mut permissions = Permissions::new();
238    /// permissions
239    ///     .grant("read:profile")
240    ///     .grant(PermissionId::from("write:profile"));
241    ///
242    /// assert!(permissions.has("read:profile"));
243    /// assert!(permissions.has(PermissionId::from("write:profile")));
244    /// ```
245    pub fn grant<P>(&mut self, permission: P) -> &mut Self
246    where
247        P: Into<PermissionId>,
248    {
249        let permission_id = permission.into();
250        self.bitmap.insert(permission_id.as_u64());
251        self
252    }
253
254    /// Revokes a permission from this permission set.
255    ///
256    /// Returns a mutable reference to self for method chaining.
257    ///
258    /// # Examples
259    ///
260    /// ```rust
261    /// use axum_gate::permissions::{Permissions, PermissionId};
262    ///
263    /// let mut permissions: Permissions = ["read:profile", "write:profile"].into_iter().collect();
264    /// permissions.revoke(PermissionId::from("write:profile"));
265    ///
266    /// assert!(permissions.has("read:profile"));
267    /// assert!(!permissions.has("write:profile"));
268    /// ```
269    pub fn revoke<P>(&mut self, permission: P) -> &mut Self
270    where
271        P: Into<PermissionId>,
272    {
273        let permission_id = permission.into();
274        self.bitmap.remove(permission_id.as_u64());
275        self
276    }
277
278    /// Checks if a specific permission is granted.
279    ///
280    /// # Examples
281    ///
282    /// ```rust
283    /// use axum_gate::permissions::{Permissions, PermissionId};
284    ///
285    /// let permissions: Permissions = ["read:profile"].into_iter().collect();
286    ///
287    /// assert!(permissions.has("read:profile"));
288    /// assert!(permissions.has(PermissionId::from("read:profile")));
289    /// assert!(!permissions.has("write:profile"));
290    /// ```
291    pub fn has<P>(&self, permission: P) -> bool
292    where
293        P: Into<PermissionId>,
294    {
295        let permission_id = permission.into();
296        self.bitmap.contains(permission_id.as_u64())
297    }
298
299    /// Checks if all of the specified permissions are granted.
300    ///
301    /// # Examples
302    ///
303    /// ```rust
304    /// use axum_gate::permissions::{Permissions, PermissionId};
305    ///
306    /// let permissions: Permissions = [
307    ///     "read:profile",
308    ///     "write:profile",
309    ///     "read:posts",
310    /// ].into_iter().collect();
311    ///
312    /// assert!(permissions.has_all(["read:profile", "write:profile"]));
313    /// assert!(permissions.has_all([PermissionId::from("read:profile")]));
314    /// assert!(!permissions.has_all(["read:profile", "admin:users"]));
315    /// ```
316    pub fn has_all<I, P>(&self, permissions: I) -> bool
317    where
318        I: IntoIterator<Item = P>,
319        P: Into<PermissionId>,
320    {
321        permissions.into_iter().all(|p| self.has(p))
322    }
323
324    /// Checks if any of the specified permissions are granted.
325    ///
326    /// # Examples
327    ///
328    /// ```rust
329    /// use axum_gate::permissions::{Permissions, PermissionId};
330    ///
331    /// let permissions: Permissions = ["read:profile"].into_iter().collect();
332    ///
333    /// assert!(permissions.has_any(["read:profile", "write:profile"]));
334    /// assert!(permissions.has_any([PermissionId::from("read:profile")]));
335    /// assert!(!permissions.has_any(["write:profile", "admin:users"]));
336    /// ```
337    pub fn has_any<I, P>(&self, permissions: I) -> bool
338    where
339        I: IntoIterator<Item = P>,
340        P: Into<PermissionId>,
341    {
342        permissions.into_iter().any(|p| self.has(p))
343    }
344
345    /// Returns the number of permissions in this set.
346    ///
347    /// # Examples
348    ///
349    /// ```rust
350    /// use axum_gate::permissions::Permissions;
351    ///
352    /// let permissions: Permissions = ["read:profile", "write:profile"].into_iter().collect();
353    /// assert_eq!(permissions.len(), 2);
354    /// ```
355    pub fn len(&self) -> usize {
356        self.bitmap.len() as usize
357    }
358
359    /// Returns `true` if the permission set contains no permissions.
360    ///
361    /// # Examples
362    ///
363    /// ```rust
364    /// use axum_gate::permissions::Permissions;
365    ///
366    /// let permissions = Permissions::new();
367    /// assert!(permissions.is_empty());
368    ///
369    /// let mut permissions = Permissions::new();
370    /// permissions.grant("read:profile");
371    /// assert!(!permissions.is_empty());
372    /// ```
373    pub fn is_empty(&self) -> bool {
374        self.bitmap.is_empty()
375    }
376
377    /// Removes all permissions from this set.
378    ///
379    /// # Examples
380    ///
381    /// ```rust
382    /// use axum_gate::permissions::Permissions;
383    ///
384    /// let mut permissions: Permissions = ["read:profile", "write:profile"].into_iter().collect();
385    /// assert!(!permissions.is_empty());
386    ///
387    /// permissions.clear();
388    /// assert!(permissions.is_empty());
389    /// ```
390    pub fn clear(&mut self) {
391        self.bitmap.clear();
392    }
393
394    /// Computes the union of this permission set with another.
395    ///
396    /// This grants all permissions that exist in either set.
397    ///
398    /// # Examples
399    ///
400    /// ```rust
401    /// use axum_gate::permissions::Permissions;
402    ///
403    /// let mut permissions1: Permissions = ["read:profile"].into_iter().collect();
404    /// let permissions2: Permissions = ["write:profile"].into_iter().collect();
405    ///
406    /// permissions1.union(&permissions2);
407    ///
408    /// assert!(permissions1.has("read:profile"));
409    /// assert!(permissions1.has("write:profile"));
410    /// ```
411    pub fn union(&mut self, other: &Permissions) -> &mut Self {
412        self.bitmap |= &other.bitmap;
413        self
414    }
415
416    /// Computes the intersection of this permission set with another.
417    ///
418    /// This keeps only permissions that exist in both sets.
419    ///
420    /// # Examples
421    ///
422    /// ```rust
423    /// use axum_gate::permissions::Permissions;
424    ///
425    /// let mut permissions1: Permissions = ["read:profile", "write:profile"].into_iter().collect();
426    /// let permissions2: Permissions = ["read:profile", "admin:users"].into_iter().collect();
427    ///
428    /// permissions1.intersection(&permissions2);
429    ///
430    /// assert!(permissions1.has("read:profile"));
431    /// assert!(!permissions1.has("write:profile"));
432    /// assert!(!permissions1.has("admin:users"));
433    /// ```
434    pub fn intersection(&mut self, other: &Permissions) -> &mut Self {
435        self.bitmap &= &other.bitmap;
436        self
437    }
438
439    /// Computes the difference of this permission set with another.
440    ///
441    /// This removes all permissions that exist in the other set.
442    ///
443    /// # Examples
444    ///
445    /// ```rust
446    /// use axum_gate::permissions::Permissions;
447    ///
448    /// let mut permissions1: Permissions = ["read:profile", "write:profile"].into_iter().collect();
449    /// let permissions2: Permissions = ["write:profile"].into_iter().collect();
450    ///
451    /// permissions1.difference(&permissions2);
452    ///
453    /// assert!(permissions1.has("read:profile"));
454    /// assert!(!permissions1.has("write:profile"));
455    /// ```
456    pub fn difference(&mut self, other: &Permissions) -> &mut Self {
457        self.bitmap -= &other.bitmap;
458        self
459    }
460
461    /// Builder method for granting a permission (immutable version).
462    ///
463    /// Use this when building permissions in a functional style or when you need
464    /// to create permissions without mutable access. Prefer `grant()` for
465    /// performance-critical code where you're modifying existing permission sets.
466    ///
467    /// # Examples
468    ///
469    /// ```rust
470    /// use axum_gate::permissions::{Permissions, PermissionId};
471    ///
472    /// let permissions = Permissions::new()
473    ///     .with("read:profile")
474    ///     .with(PermissionId::from("write:profile"))
475    ///     .build();
476    ///
477    /// assert!(permissions.has("read:profile"));
478    /// assert!(permissions.has("write:profile"));
479    /// ```
480    pub fn with<P>(mut self, permission: P) -> Self
481    where
482        P: Into<PermissionId>,
483    {
484        self.grant(permission);
485        self
486    }
487
488    /// Finalizes the builder pattern.
489    ///
490    /// This method returns self unchanged, providing a clean conclusion to
491    /// the builder pattern. Use this when you want to clearly signal the
492    /// end of permission configuration.
493    ///
494    /// # Examples
495    ///
496    /// ```rust
497    /// use axum_gate::permissions::Permissions;
498    ///
499    /// let permissions = Permissions::new()
500    ///     .with("read:profile")
501    ///     .with("write:profile")
502    ///     .build();
503    /// ```
504    pub fn build(self) -> Self {
505        self
506    }
507
508    /// Returns an iterator over the permission IDs in this collection.
509    ///
510    /// Use this when you need to examine all granted permissions or integrate
511    /// with external systems that work with permission IDs directly.
512    ///
513    /// # Examples
514    ///
515    /// ```rust
516    /// use axum_gate::permissions::Permissions;
517    ///
518    /// let permissions: Permissions = ["read:profile", "write:profile"].into_iter().collect();
519    /// let ids: Vec<u64> = permissions.iter().collect();
520    /// assert_eq!(ids.len(), 2);
521    /// ```
522    pub fn iter(&self) -> impl Iterator<Item = u64> + '_ {
523        self.bitmap.iter()
524    }
525
526    /// Internal method for testing access.
527    #[cfg(feature = "server")]
528    pub(crate) fn bitmap_mut(&mut self) -> &mut roaring::RoaringTreemap {
529        &mut self.bitmap
530    }
531}
532
533impl Default for Permissions {
534    fn default() -> Self {
535        Self::new()
536    }
537}
538
539impl fmt::Display for Permissions {
540    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541        write!(f, "Permissions({})", self.len())
542    }
543}
544
545impl From<roaring::RoaringTreemap> for Permissions {
546    fn from(bitmap: roaring::RoaringTreemap) -> Self {
547        Self { bitmap }
548    }
549}
550
551impl From<Permissions> for roaring::RoaringTreemap {
552    fn from(permissions: Permissions) -> Self {
553        permissions.bitmap
554    }
555}
556
557impl AsRef<roaring::RoaringTreemap> for Permissions {
558    fn as_ref(&self) -> &roaring::RoaringTreemap {
559        &self.bitmap
560    }
561}
562
563impl<P> std::iter::FromIterator<P> for Permissions
564where
565    P: Into<PermissionId>,
566{
567    /// Creates a permission set from an iterator of permission names.
568    ///
569    /// # Examples
570    ///
571    /// ```rust
572    /// use axum_gate::permissions::Permissions;
573    ///
574    /// let permissions: Permissions = ["read:profile", "write:profile", "read:posts"]
575    ///     .into_iter()
576    ///     .collect();
577    ///
578    /// assert!(permissions.has("read:profile"));
579    /// assert!(permissions.has("write:profile"));
580    /// assert!(permissions.has("read:posts"));
581    /// ```
582    fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
583        let mut perms = Self::new();
584        for permission in iter {
585            perms.grant(permission);
586        }
587        perms
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use super::*;
594
595    #[test]
596    fn new_permissions_is_empty() {
597        let permissions = Permissions::new();
598        assert!(permissions.is_empty());
599        assert_eq!(permissions.len(), 0);
600    }
601
602    #[test]
603    fn grant_and_has_permission() {
604        let mut permissions = Permissions::new();
605        permissions.grant("read:profile");
606
607        assert!(permissions.has("read:profile"));
608        assert!(!permissions.has("write:profile"));
609        assert_eq!(permissions.len(), 1);
610        assert!(!permissions.is_empty());
611    }
612
613    #[test]
614    fn grant_chaining() {
615        let mut permissions = Permissions::new();
616        permissions
617            .grant("read:profile")
618            .grant("write:profile")
619            .grant("delete:profile");
620
621        assert!(permissions.has("read:profile"));
622        assert!(permissions.has("write:profile"));
623        assert!(permissions.has("delete:profile"));
624        assert_eq!(permissions.len(), 3);
625    }
626
627    #[test]
628    fn revoke_permission() {
629        let mut permissions: Permissions = ["read:profile", "write:profile"].into_iter().collect();
630        permissions.revoke("write:profile");
631
632        assert!(permissions.has("read:profile"));
633        assert!(!permissions.has("write:profile"));
634        assert_eq!(permissions.len(), 1);
635    }
636
637    #[test]
638    fn has_all_permissions() {
639        let permissions: Permissions = ["read:profile", "write:profile", "read:posts"]
640            .into_iter()
641            .collect();
642
643        assert!(permissions.has_all(["read:profile", "write:profile"]));
644        assert!(permissions.has_all(["read:profile"]));
645        assert!(!permissions.has_all(["read:profile", "admin:users"]));
646        assert!(permissions.has_all(Vec::<&str>::new())); // empty set
647    }
648
649    #[test]
650    fn has_any_permission() {
651        let permissions: Permissions = ["read:profile"].into_iter().collect();
652
653        assert!(permissions.has_any(["read:profile", "write:profile"]));
654        assert!(permissions.has_any(["write:profile", "read:profile"]));
655        assert!(!permissions.has_any(["write:profile", "admin:users"]));
656        assert!(!permissions.has_any(Vec::<&str>::new())); // empty set
657    }
658
659    #[test]
660    fn clear_permissions() {
661        let mut permissions: Permissions = ["read:profile", "write:profile"].into_iter().collect();
662        assert!(!permissions.is_empty());
663
664        permissions.clear();
665        assert!(permissions.is_empty());
666        assert_eq!(permissions.len(), 0);
667    }
668
669    #[test]
670    fn union_permissions() {
671        let mut permissions1: Permissions = ["read:profile"].into_iter().collect();
672        let permissions2: Permissions = ["write:profile", "read:posts"].into_iter().collect();
673
674        permissions1.union(&permissions2);
675
676        assert!(permissions1.has("read:profile"));
677        assert!(permissions1.has("write:profile"));
678        assert!(permissions1.has("read:posts"));
679        assert_eq!(permissions1.len(), 3);
680    }
681
682    #[test]
683    fn intersection_permissions() {
684        let mut permissions1: Permissions = ["read:profile", "write:profile"].into_iter().collect();
685        let permissions2: Permissions = ["read:profile", "admin:users"].into_iter().collect();
686
687        permissions1.intersection(&permissions2);
688
689        assert!(permissions1.has("read:profile"));
690        assert!(!permissions1.has("write:profile"));
691        assert!(!permissions1.has("admin:users"));
692        assert_eq!(permissions1.len(), 1);
693    }
694
695    #[test]
696    fn difference_permissions() {
697        let mut permissions1 = Permissions::from_iter(["read:profile", "write:profile"]);
698        let permissions2 = Permissions::from_iter(["write:profile"]);
699
700        permissions1.difference(&permissions2);
701
702        assert!(permissions1.has("read:profile"));
703        assert!(!permissions1.has("write:profile"));
704        assert_eq!(permissions1.len(), 1);
705    }
706
707    #[test]
708    fn builder_pattern() {
709        let permissions = Permissions::new()
710            .with("read:profile")
711            .with("write:profile")
712            .build();
713
714        assert!(permissions.has("read:profile"));
715        assert!(permissions.has("write:profile"));
716        assert_eq!(permissions.len(), 2);
717    }
718
719    #[test]
720    fn from_iter() {
721        let permissions: Permissions = ["read:profile", "write:profile", "read:posts"]
722            .into_iter()
723            .collect();
724
725        assert!(permissions.has("read:profile"));
726        assert!(permissions.has("write:profile"));
727        assert!(permissions.has("read:posts"));
728        assert_eq!(permissions.len(), 3);
729    }
730
731    #[test]
732    fn permissions_are_deterministic() {
733        let permissions1 = Permissions::from_iter(["read:profile"]);
734        let permissions2 = Permissions::from_iter(["read:profile"]);
735
736        assert_eq!(permissions1, permissions2);
737        assert!(permissions1.has("read:profile"));
738        assert!(permissions2.has("read:profile"));
739    }
740
741    #[test]
742    fn display_implementation() {
743        let permissions = Permissions::from_iter(["read:profile", "write:profile"]);
744        let display = format!("{}", permissions);
745        assert_eq!(display, "Permissions(2)");
746    }
747}