Skip to main content

reinhardt_auth/core/
permission_operators.rs

1//! Permission Operators
2//!
3//! Provides logical operators (AND, OR, NOT) for composing permissions.
4//! Supports both builder-style composition (`AndPermission::new()`) and
5//! operator-based composition (`&`, `|`, `!`).
6
7use crate::core::{Permission, PermissionContext};
8use async_trait::async_trait;
9use std::ops::{BitAnd, BitOr, Not};
10
11/// AND permission operator
12///
13/// Combines two permissions with logical AND. Both permissions must be satisfied.
14///
15/// # Examples
16///
17/// ```
18/// use reinhardt_auth::permission_operators::AndPermission;
19/// use reinhardt_auth::{IsAuthenticated, IsAdminUser, Permission, PermissionContext};
20/// use bytes::Bytes;
21/// use hyper::{HeaderMap, Method, Version};
22/// use reinhardt_http::Request;
23///
24/// #[tokio::main]
25/// async fn main() {
26///     let permission = AndPermission::new(IsAuthenticated, IsAdminUser);
27/// let request = Request::builder()
28///     .method(Method::GET)
29///     .uri("/")
30///     .version(Version::HTTP_11)
31///     .headers(HeaderMap::new())
32///     .body(Bytes::new())
33///     .build()
34///     .unwrap();
35///
36///     // Both authenticated AND admin required
37///     let context = PermissionContext {
38///         request: &request,
39///         is_authenticated: true,
40///         is_admin: true,
41///         is_active: true,
42///         user: None,
43///     };
44///     assert!(permission.has_permission(&context).await);
45///
46///     // Not admin - fails
47///     let context = PermissionContext {
48///         request: &request,
49///         is_authenticated: true,
50///         is_admin: false,
51///         is_active: true,
52///         user: None,
53///     };
54///     assert!(!permission.has_permission(&context).await);
55/// }
56/// ```
57pub struct AndPermission<A, B> {
58	left: A,
59	right: B,
60}
61
62impl<A, B> AndPermission<A, B> {
63	/// Create a new AND permission
64	///
65	/// # Examples
66	///
67	/// ```
68	/// use reinhardt_auth::permission_operators::AndPermission;
69	/// use reinhardt_auth::{IsAuthenticated, IsActiveUser};
70	///
71	/// let permission = AndPermission::new(IsAuthenticated, IsActiveUser);
72	/// ```
73	pub fn new(left: A, right: B) -> Self {
74		Self { left, right }
75	}
76}
77
78#[async_trait]
79impl<A, B> Permission for AndPermission<A, B>
80where
81	A: Permission + Send + Sync,
82	B: Permission + Send + Sync,
83{
84	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
85		self.left.has_permission(context).await && self.right.has_permission(context).await
86	}
87}
88
89/// OR permission operator
90///
91/// Combines two permissions with logical OR. Either permission can be satisfied.
92///
93/// # Examples
94///
95/// ```
96/// use reinhardt_auth::permission_operators::OrPermission;
97/// use reinhardt_auth::{IsAuthenticated, AllowAny, Permission, PermissionContext};
98/// use bytes::Bytes;
99/// use hyper::{HeaderMap, Method, Version};
100/// use reinhardt_http::Request;
101///
102/// #[tokio::main]
103/// async fn main() {
104///     let permission = OrPermission::new(IsAuthenticated, AllowAny);
105/// let request = Request::builder()
106///     .method(Method::GET)
107///     .uri("/")
108///     .version(Version::HTTP_11)
109///     .headers(HeaderMap::new())
110///     .body(Bytes::new())
111///     .build()
112///     .unwrap();
113///
114///     // Either authenticated OR allow any
115///     let context = PermissionContext {
116///         request: &request,
117///         is_authenticated: false,
118///         is_admin: false,
119///         is_active: false,
120///         user: None,
121///     };
122///     assert!(permission.has_permission(&context).await);
123/// }
124/// ```
125pub struct OrPermission<A, B> {
126	left: A,
127	right: B,
128}
129
130impl<A, B> OrPermission<A, B> {
131	/// Create a new OR permission
132	///
133	/// # Examples
134	///
135	/// ```
136	/// use reinhardt_auth::permission_operators::OrPermission;
137	/// use reinhardt_auth::{IsAdminUser, IsActiveUser};
138	///
139	/// let permission = OrPermission::new(IsAdminUser, IsActiveUser);
140	/// ```
141	pub fn new(left: A, right: B) -> Self {
142		Self { left, right }
143	}
144}
145
146#[async_trait]
147impl<A, B> Permission for OrPermission<A, B>
148where
149	A: Permission + Send + Sync,
150	B: Permission + Send + Sync,
151{
152	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
153		self.left.has_permission(context).await || self.right.has_permission(context).await
154	}
155}
156
157/// NOT permission operator
158///
159/// Negates a permission. Returns true if the inner permission is false.
160///
161/// # Examples
162///
163/// ```
164/// use reinhardt_auth::permission_operators::NotPermission;
165/// use reinhardt_auth::{IsAuthenticated, Permission, PermissionContext};
166/// use bytes::Bytes;
167/// use hyper::{HeaderMap, Method, Version};
168/// use reinhardt_http::Request;
169///
170/// #[tokio::main]
171/// async fn main() {
172///     let permission = NotPermission::new(IsAuthenticated);
173/// let request = Request::builder()
174///     .method(Method::GET)
175///     .uri("/")
176///     .version(Version::HTTP_11)
177///     .headers(HeaderMap::new())
178///     .body(Bytes::new())
179///     .build()
180///     .unwrap();
181///
182///     // NOT authenticated - only allows unauthenticated users
183///     let context = PermissionContext {
184///         request: &request,
185///         is_authenticated: false,
186///         is_admin: false,
187///         is_active: false,
188///         user: None,
189///     };
190///     assert!(permission.has_permission(&context).await);
191///
192///     // Authenticated - denies
193///     let context = PermissionContext {
194///         request: &request,
195///         is_authenticated: true,
196///         is_admin: false,
197///         is_active: true,
198///         user: None,
199///     };
200///     assert!(!permission.has_permission(&context).await);
201/// }
202/// ```
203pub struct NotPermission<P> {
204	inner: P,
205}
206
207impl<P> NotPermission<P> {
208	/// Create a new NOT permission
209	///
210	/// # Examples
211	///
212	/// ```
213	/// use reinhardt_auth::permission_operators::NotPermission;
214	/// use reinhardt_auth::IsAdminUser;
215	///
216	/// let permission = NotPermission::new(IsAdminUser);
217	/// ```
218	pub fn new(inner: P) -> Self {
219		Self { inner }
220	}
221}
222
223#[async_trait]
224impl<P> Permission for NotPermission<P>
225where
226	P: Permission + Send + Sync,
227{
228	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
229		!self.inner.has_permission(context).await
230	}
231}
232
233// Operator overloading implementations using macros
234//
235// Due to Rust's orphan rules, we need to implement operators for each concrete permission type.
236// This macro makes it easy to add operator support for new permission types.
237
238/// Macro to implement BitAnd, BitOr, and Not operators for permission types.
239///
240/// This allows natural composition syntax like:
241/// ```rust,ignore
242/// # use reinhardt_auth::core::permission::Permission;
243/// # struct IsAuthenticated;
244/// # struct IsActiveUser;
245/// # struct IsAdminUser;
246/// # impl Permission for IsAuthenticated { fn check(&self, _user: Option<&dyn std::any::Any>) -> bool { false } }
247/// # impl Permission for IsActiveUser { fn check(&self, _user: Option<&dyn std::any::Any>) -> bool { false } }
248/// # impl Permission for IsAdminUser { fn check(&self, _user: Option<&dyn std::any::Any>) -> bool { false } }
249/// let permission = IsAuthenticated & IsActiveUser;
250/// let permission = IsAdminUser | IsActiveUser;
251/// let permission = !IsAuthenticated;
252/// ```
253///
254/// The macro generates implementations for `&`, `|`, and `!` operators
255/// that create the appropriate `AndPermission`, `OrPermission`, and `NotPermission` types.
256macro_rules! impl_permission_operators {
257	($type:ty) => {
258		impl<B: Permission> BitAnd<B> for $type {
259			type Output = AndPermission<Self, B>;
260
261			fn bitand(self, rhs: B) -> Self::Output {
262				AndPermission::new(self, rhs)
263			}
264		}
265
266		impl<B: Permission> BitOr<B> for $type {
267			type Output = OrPermission<Self, B>;
268
269			fn bitor(self, rhs: B) -> Self::Output {
270				OrPermission::new(self, rhs)
271			}
272		}
273
274		impl Not for $type {
275			type Output = NotPermission<Self>;
276
277			fn not(self) -> Self::Output {
278				NotPermission::new(self)
279			}
280		}
281	};
282}
283
284// Apply operators to all built-in permission types
285impl_permission_operators!(crate::AllowAny);
286impl_permission_operators!(crate::IsAuthenticated);
287impl_permission_operators!(crate::IsAdminUser);
288impl_permission_operators!(crate::IsActiveUser);
289impl_permission_operators!(crate::IsAuthenticatedOrReadOnly);
290
291// Apply operators to composite permission types to allow chaining
292impl<A, B, C> BitAnd<C> for AndPermission<A, B>
293where
294	A: Permission,
295	B: Permission,
296	C: Permission,
297{
298	type Output = AndPermission<Self, C>;
299
300	fn bitand(self, rhs: C) -> Self::Output {
301		AndPermission::new(self, rhs)
302	}
303}
304
305impl<A, B, C> BitOr<C> for AndPermission<A, B>
306where
307	A: Permission,
308	B: Permission,
309	C: Permission,
310{
311	type Output = OrPermission<Self, C>;
312
313	fn bitor(self, rhs: C) -> Self::Output {
314		OrPermission::new(self, rhs)
315	}
316}
317
318impl<A, B> Not for AndPermission<A, B>
319where
320	A: Permission,
321	B: Permission,
322{
323	type Output = NotPermission<Self>;
324
325	fn not(self) -> Self::Output {
326		NotPermission::new(self)
327	}
328}
329
330impl<A, B, C> BitAnd<C> for OrPermission<A, B>
331where
332	A: Permission,
333	B: Permission,
334	C: Permission,
335{
336	type Output = AndPermission<Self, C>;
337
338	fn bitand(self, rhs: C) -> Self::Output {
339		AndPermission::new(self, rhs)
340	}
341}
342
343impl<A, B, C> BitOr<C> for OrPermission<A, B>
344where
345	A: Permission,
346	B: Permission,
347	C: Permission,
348{
349	type Output = OrPermission<Self, C>;
350
351	fn bitor(self, rhs: C) -> Self::Output {
352		OrPermission::new(self, rhs)
353	}
354}
355
356impl<A, B> Not for OrPermission<A, B>
357where
358	A: Permission,
359	B: Permission,
360{
361	type Output = NotPermission<Self>;
362
363	fn not(self) -> Self::Output {
364		NotPermission::new(self)
365	}
366}
367
368impl<P, B> BitAnd<B> for NotPermission<P>
369where
370	P: Permission,
371	B: Permission,
372{
373	type Output = AndPermission<Self, B>;
374
375	fn bitand(self, rhs: B) -> Self::Output {
376		AndPermission::new(self, rhs)
377	}
378}
379
380impl<P, B> BitOr<B> for NotPermission<P>
381where
382	P: Permission,
383	B: Permission,
384{
385	type Output = OrPermission<Self, B>;
386
387	fn bitor(self, rhs: B) -> Self::Output {
388		OrPermission::new(self, rhs)
389	}
390}
391
392impl<P> Not for NotPermission<P>
393where
394	P: Permission,
395{
396	type Output = NotPermission<Self>;
397
398	fn not(self) -> Self::Output {
399		NotPermission::new(self)
400	}
401}
402
403#[cfg(test)]
404mod tests {
405	use super::*;
406	use crate::core::{AllowAny, IsAdminUser, IsAuthenticated};
407	use bytes::Bytes;
408	use hyper::{HeaderMap, Method, Version};
409	use reinhardt_http::Request;
410
411	#[tokio::test]
412	async fn test_and_permission_both_true() {
413		let permission = AndPermission::new(IsAuthenticated, IsAdminUser);
414		let request = Request::builder()
415			.method(Method::GET)
416			.uri("/")
417			.version(Version::HTTP_11)
418			.headers(HeaderMap::new())
419			.body(Bytes::new())
420			.build()
421			.unwrap();
422
423		let context = PermissionContext {
424			request: &request,
425			is_authenticated: true,
426			is_admin: true,
427			is_active: true,
428			user: None,
429		};
430
431		assert!(permission.has_permission(&context).await);
432	}
433
434	#[tokio::test]
435	async fn test_and_permission_left_false() {
436		let permission = AndPermission::new(IsAuthenticated, IsAdminUser);
437		let request = Request::builder()
438			.method(Method::GET)
439			.uri("/")
440			.version(Version::HTTP_11)
441			.headers(HeaderMap::new())
442			.body(Bytes::new())
443			.build()
444			.unwrap();
445
446		let context = PermissionContext {
447			request: &request,
448			is_authenticated: false,
449			is_admin: true,
450			is_active: false,
451			user: None,
452		};
453
454		assert!(!permission.has_permission(&context).await);
455	}
456
457	#[tokio::test]
458	async fn test_and_permission_right_false() {
459		let permission = AndPermission::new(IsAuthenticated, IsAdminUser);
460		let request = Request::builder()
461			.method(Method::GET)
462			.uri("/")
463			.version(Version::HTTP_11)
464			.headers(HeaderMap::new())
465			.body(Bytes::new())
466			.build()
467			.unwrap();
468
469		let context = PermissionContext {
470			request: &request,
471			is_authenticated: true,
472			is_admin: false,
473			is_active: true,
474			user: None,
475		};
476
477		assert!(!permission.has_permission(&context).await);
478	}
479
480	#[tokio::test]
481	async fn test_or_permission_both_true() {
482		let permission = OrPermission::new(IsAuthenticated, AllowAny);
483		let request = Request::builder()
484			.method(Method::GET)
485			.uri("/")
486			.version(Version::HTTP_11)
487			.headers(HeaderMap::new())
488			.body(Bytes::new())
489			.build()
490			.unwrap();
491
492		let context = PermissionContext {
493			request: &request,
494			is_authenticated: true,
495			is_admin: false,
496			is_active: true,
497			user: None,
498		};
499
500		assert!(permission.has_permission(&context).await);
501	}
502
503	#[tokio::test]
504	async fn test_or_permission_left_true() {
505		let permission = OrPermission::new(IsAuthenticated, IsAdminUser);
506		let request = Request::builder()
507			.method(Method::GET)
508			.uri("/")
509			.version(Version::HTTP_11)
510			.headers(HeaderMap::new())
511			.body(Bytes::new())
512			.build()
513			.unwrap();
514
515		let context = PermissionContext {
516			request: &request,
517			is_authenticated: true,
518			is_admin: false,
519			is_active: true,
520			user: None,
521		};
522
523		assert!(permission.has_permission(&context).await);
524	}
525
526	#[tokio::test]
527	async fn test_or_permission_right_true() {
528		let permission = OrPermission::new(IsAuthenticated, AllowAny);
529		let request = Request::builder()
530			.method(Method::GET)
531			.uri("/")
532			.version(Version::HTTP_11)
533			.headers(HeaderMap::new())
534			.body(Bytes::new())
535			.build()
536			.unwrap();
537
538		let context = PermissionContext {
539			request: &request,
540			is_authenticated: false,
541			is_admin: false,
542			is_active: false,
543			user: None,
544		};
545
546		assert!(permission.has_permission(&context).await);
547	}
548
549	#[tokio::test]
550	async fn test_or_permission_both_false() {
551		let permission = OrPermission::new(IsAuthenticated, IsAdminUser);
552		let request = Request::builder()
553			.method(Method::GET)
554			.uri("/")
555			.version(Version::HTTP_11)
556			.headers(HeaderMap::new())
557			.body(Bytes::new())
558			.build()
559			.unwrap();
560
561		let context = PermissionContext {
562			request: &request,
563			is_authenticated: false,
564			is_admin: false,
565			is_active: false,
566			user: None,
567		};
568
569		assert!(!permission.has_permission(&context).await);
570	}
571
572	#[tokio::test]
573	async fn test_not_permission_true() {
574		let permission = NotPermission::new(IsAuthenticated);
575		let request = Request::builder()
576			.method(Method::GET)
577			.uri("/")
578			.version(Version::HTTP_11)
579			.headers(HeaderMap::new())
580			.body(Bytes::new())
581			.build()
582			.unwrap();
583
584		let context = PermissionContext {
585			request: &request,
586			is_authenticated: false,
587			is_admin: false,
588			is_active: false,
589			user: None,
590		};
591
592		assert!(permission.has_permission(&context).await);
593	}
594
595	#[tokio::test]
596	async fn test_not_permission_false() {
597		let permission = NotPermission::new(IsAuthenticated);
598		let request = Request::builder()
599			.method(Method::GET)
600			.uri("/")
601			.version(Version::HTTP_11)
602			.headers(HeaderMap::new())
603			.body(Bytes::new())
604			.build()
605			.unwrap();
606
607		let context = PermissionContext {
608			request: &request,
609			is_authenticated: true,
610			is_admin: false,
611			is_active: true,
612			user: None,
613		};
614
615		assert!(!permission.has_permission(&context).await);
616	}
617
618	#[tokio::test]
619	async fn test_complex_permission_combination() {
620		let permission =
621			OrPermission::new(AndPermission::new(IsAuthenticated, IsAdminUser), AllowAny);
622
623		let request = Request::builder()
624			.method(Method::GET)
625			.uri("/")
626			.version(Version::HTTP_11)
627			.headers(HeaderMap::new())
628			.body(Bytes::new())
629			.build()
630			.unwrap();
631
632		let context = PermissionContext {
633			request: &request,
634			is_authenticated: false,
635			is_admin: false,
636			is_active: false,
637			user: None,
638		};
639
640		assert!(permission.has_permission(&context).await);
641	}
642
643	// Tests for operator overloading
644
645	#[tokio::test]
646	async fn test_bitand_operator() {
647		let permission = IsAuthenticated & IsAdminUser;
648
649		let request = Request::builder()
650			.method(Method::GET)
651			.uri("/")
652			.version(Version::HTTP_11)
653			.headers(HeaderMap::new())
654			.body(Bytes::new())
655			.build()
656			.unwrap();
657
658		// Both conditions met
659		let context = PermissionContext {
660			request: &request,
661			is_authenticated: true,
662			is_admin: true,
663			is_active: true,
664			user: None,
665		};
666		assert!(permission.has_permission(&context).await);
667
668		// Only authenticated, not admin
669		let context = PermissionContext {
670			request: &request,
671			is_authenticated: true,
672			is_admin: false,
673			is_active: true,
674			user: None,
675		};
676		assert!(!permission.has_permission(&context).await);
677	}
678
679	#[tokio::test]
680	async fn test_bitor_operator() {
681		let permission = IsAuthenticated | AllowAny;
682
683		let request = Request::builder()
684			.method(Method::GET)
685			.uri("/")
686			.version(Version::HTTP_11)
687			.headers(HeaderMap::new())
688			.body(Bytes::new())
689			.build()
690			.unwrap();
691
692		// Not authenticated, but AllowAny should allow
693		let context = PermissionContext {
694			request: &request,
695			is_authenticated: false,
696			is_admin: false,
697			is_active: false,
698			user: None,
699		};
700		assert!(permission.has_permission(&context).await);
701	}
702
703	#[tokio::test]
704	async fn test_not_operator() {
705		let permission = !IsAuthenticated;
706
707		let request = Request::builder()
708			.method(Method::GET)
709			.uri("/")
710			.version(Version::HTTP_11)
711			.headers(HeaderMap::new())
712			.body(Bytes::new())
713			.build()
714			.unwrap();
715
716		// Not authenticated - should be allowed
717		let context = PermissionContext {
718			request: &request,
719			is_authenticated: false,
720			is_admin: false,
721			is_active: false,
722			user: None,
723		};
724		assert!(permission.has_permission(&context).await);
725
726		// Authenticated - should be denied
727		let context = PermissionContext {
728			request: &request,
729			is_authenticated: true,
730			is_admin: false,
731			is_active: true,
732			user: None,
733		};
734		assert!(!permission.has_permission(&context).await);
735	}
736
737	#[tokio::test]
738	async fn test_complex_operator_combination() {
739		// (IsAuthenticated & IsAdminUser) | AllowAny
740		let permission = (IsAuthenticated & IsAdminUser) | AllowAny;
741
742		let request = Request::builder()
743			.method(Method::GET)
744			.uri("/")
745			.version(Version::HTTP_11)
746			.headers(HeaderMap::new())
747			.body(Bytes::new())
748			.build()
749			.unwrap();
750
751		// Not authenticated, but AllowAny allows
752		let context = PermissionContext {
753			request: &request,
754			is_authenticated: false,
755			is_admin: false,
756			is_active: false,
757			user: None,
758		};
759		assert!(permission.has_permission(&context).await);
760	}
761}