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}