Skip to main content

reinhardt_auth/core/
permission.rs

1use async_trait::async_trait;
2
3use crate::core::user::User;
4
5/// Permission context - contains request information for permission checking
6///
7/// This struct provides the context needed for permission classes to make
8/// authorization decisions.
9///
10/// # Examples
11///
12/// ```
13/// use reinhardt_auth::{PermissionContext, AnonymousUser, User};
14/// use reinhardt_http::Request;
15/// use hyper::{Method, Uri, Version, header::HeaderMap};
16/// use bytes::Bytes;
17///
18/// let request = Request::builder()
19///     .method(Method::GET)
20///     .uri("/")
21///     .version(Version::HTTP_11)
22///     .headers(HeaderMap::new())
23///     .body(Bytes::new())
24///     .build()
25///     .unwrap();
26/// let context = PermissionContext {
27///     request: &request,
28///     is_authenticated: false,
29///     is_admin: false,
30///     is_active: false,
31///     user: None,
32/// };
33///
34/// assert!(!context.is_authenticated);
35/// ```
36pub struct PermissionContext<'a> {
37	/// The HTTP request
38	pub request: &'a reinhardt_http::Request,
39	/// Whether the user is authenticated
40	pub is_authenticated: bool,
41	/// Whether the user is an admin
42	pub is_admin: bool,
43	/// Whether the user account is active
44	pub is_active: bool,
45	/// The authenticated user, if any
46	pub user: Option<Box<dyn User>>,
47}
48
49/// Permission trait - defines permission checking interface
50///
51/// Implement this trait to create custom permission classes for your API.
52///
53/// # Examples
54///
55/// Custom permission class:
56///
57/// ```
58/// use reinhardt_auth::{Permission, PermissionContext};
59/// use async_trait::async_trait;
60///
61/// struct IsOwner;
62///
63/// #[async_trait]
64/// impl Permission for IsOwner {
65///     async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
66///         // Check if user is authenticated and owns the resource
67///         if !context.is_authenticated {
68///             return false;
69///         }
70///
71///         // Extract owner_id from request and compare with user.id()
72///         // This is a simplified example
73///         true
74///     }
75/// }
76/// ```
77#[async_trait]
78pub trait Permission: Send + Sync {
79	/// Checks if the user has permission to perform the action
80	///
81	/// Returns `true` if permission is granted, `false` otherwise.
82	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool;
83}
84
85/// AllowAny - grants permission to all requests
86///
87/// This permission class allows unrestricted access. Use with caution.
88///
89/// # Examples
90///
91/// ```
92/// use reinhardt_auth::{Permission, AllowAny, PermissionContext};
93/// use reinhardt_http::Request;
94/// use hyper::{Method, Uri, Version, header::HeaderMap};
95/// use bytes::Bytes;
96///
97/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
98/// let permission = AllowAny;
99/// let request = Request::builder()
100///     .method(Method::GET)
101///     .uri("/")
102///     .version(Version::HTTP_11)
103///     .headers(HeaderMap::new())
104///     .body(Bytes::new())
105///     .build()
106///     .unwrap();
107/// let context = PermissionContext {
108///     request: &request,
109///     is_authenticated: false,
110///     is_admin: false,
111///     is_active: false,
112///     user: None,
113/// };
114///
115/// assert!(permission.has_permission(&context).await);
116/// # });
117/// ```
118#[derive(Clone, Copy, Default)]
119pub struct AllowAny;
120
121#[async_trait]
122impl Permission for AllowAny {
123	async fn has_permission(&self, _context: &PermissionContext<'_>) -> bool {
124		true
125	}
126}
127
128/// IsAuthenticated - requires the user to be authenticated
129///
130/// # Examples
131///
132/// ```
133/// use reinhardt_auth::{Permission, IsAuthenticated, PermissionContext, SimpleUser, User};
134/// use reinhardt_http::Request;
135/// use hyper::{Method, Uri, Version, header::HeaderMap};
136/// use bytes::Bytes;
137/// use uuid::Uuid;
138///
139/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
140/// let permission = IsAuthenticated;
141/// let request = Request::builder()
142///     .method(Method::GET)
143///     .uri("/")
144///     .version(Version::HTTP_11)
145///     .headers(HeaderMap::new())
146///     .body(Bytes::new())
147///     .build()
148///     .unwrap();
149///
150/// // Anonymous user - permission denied
151/// let context = PermissionContext {
152///     request: &request,
153///     is_authenticated: false,
154///     is_admin: false,
155///     is_active: false,
156///     user: None,
157/// };
158/// assert!(!permission.has_permission(&context).await);
159///
160/// // Authenticated user - permission granted
161/// let user = SimpleUser {
162///     id: Uuid::new_v4(),
163///     username: "alice".to_string(),
164///     email: "alice@example.com".to_string(),
165///     is_active: true,
166///     is_admin: false,
167///     is_staff: false,
168///     is_superuser: false,
169/// };
170/// let context = PermissionContext {
171///     request: &request,
172///     is_authenticated: true,
173///     is_admin: false,
174///     is_active: true,
175///     user: Some(Box::new(user)),
176/// };
177/// assert!(permission.has_permission(&context).await);
178/// # });
179/// ```
180#[derive(Clone, Copy, Default)]
181pub struct IsAuthenticated;
182
183#[async_trait]
184impl Permission for IsAuthenticated {
185	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
186		context.is_authenticated
187	}
188}
189
190/// IsAdminUser - requires the user to be an admin
191///
192/// # Examples
193///
194/// ```
195/// use reinhardt_auth::{Permission, IsAdminUser, PermissionContext, SimpleUser, User};
196/// use reinhardt_http::Request;
197/// use hyper::{Method, Uri, Version, header::HeaderMap};
198/// use bytes::Bytes;
199/// use uuid::Uuid;
200///
201/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
202/// let permission = IsAdminUser;
203/// let request = Request::builder()
204///     .method(Method::GET)
205///     .uri("/")
206///     .version(Version::HTTP_11)
207///     .headers(HeaderMap::new())
208///     .body(Bytes::new())
209///     .build()
210///     .unwrap();
211///
212/// // Non-admin user - permission denied
213/// let user = SimpleUser {
214///     id: Uuid::new_v4(),
215///     username: "alice".to_string(),
216///     email: "alice@example.com".to_string(),
217///     is_active: true,
218///     is_admin: false,
219///     is_staff: false,
220///     is_superuser: false,
221/// };
222/// let context = PermissionContext {
223///     request: &request,
224///     is_authenticated: true,
225///     is_admin: false,
226///     is_active: true,
227///     user: Some(Box::new(user)),
228/// };
229/// assert!(!permission.has_permission(&context).await);
230///
231/// // Admin user - permission granted
232/// let admin = SimpleUser {
233///     id: Uuid::new_v4(),
234///     username: "admin".to_string(),
235///     email: "admin@example.com".to_string(),
236///     is_active: true,
237///     is_admin: true,
238///     is_staff: true,
239///     is_superuser: true,
240/// };
241/// let context = PermissionContext {
242///     request: &request,
243///     is_authenticated: true,
244///     is_admin: true,
245///     is_active: true,
246///     user: Some(Box::new(admin)),
247/// };
248/// assert!(permission.has_permission(&context).await);
249/// # });
250/// ```
251pub struct IsAdminUser;
252
253#[async_trait]
254impl Permission for IsAdminUser {
255	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
256		context.is_authenticated && context.is_admin
257	}
258}
259
260/// IsActiveUser - requires the user to be active
261///
262/// # Examples
263///
264/// ```
265/// use reinhardt_auth::{Permission, IsActiveUser, PermissionContext};
266/// use reinhardt_http::Request;
267/// use hyper::{Method, Uri, Version, header::HeaderMap};
268/// use bytes::Bytes;
269///
270/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
271/// let permission = IsActiveUser;
272/// let request = Request::builder()
273///     .method(Method::GET)
274///     .uri("/")
275///     .version(Version::HTTP_11)
276///     .headers(HeaderMap::new())
277///     .body(Bytes::new())
278///     .build()
279///     .unwrap();
280///
281/// // Inactive user - permission denied
282/// let context = PermissionContext {
283///     request: &request,
284///     is_authenticated: true,
285///     is_admin: false,
286///     is_active: false,
287///     user: None,
288/// };
289/// assert!(!permission.has_permission(&context).await);
290///
291/// // Active user - permission granted
292/// let context = PermissionContext {
293///     request: &request,
294///     is_authenticated: true,
295///     is_admin: false,
296///     is_active: true,
297///     user: None,
298///     };
299/// assert!(permission.has_permission(&context).await);
300/// # });
301/// ```
302pub struct IsActiveUser;
303
304#[async_trait]
305impl Permission for IsActiveUser {
306	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
307		context.is_authenticated && context.is_active
308	}
309}
310
311/// IsAuthenticatedOrReadOnly - authenticated for writes, allow reads
312///
313/// This permission allows read-only access (GET, HEAD, OPTIONS) to all users,
314/// but requires authentication for write operations (POST, PUT, PATCH, DELETE).
315///
316/// # Examples
317///
318/// ```
319/// use reinhardt_auth::{Permission, IsAuthenticatedOrReadOnly, PermissionContext};
320/// use reinhardt_http::Request;
321/// use hyper::{Method, Uri, Version, header::HeaderMap};
322/// use bytes::Bytes;
323///
324/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
325/// let permission = IsAuthenticatedOrReadOnly;
326///
327/// // GET request from anonymous user - allowed
328/// let mut request = Request::builder()
329///     .method(Method::GET)
330///     .uri("/")
331///     .version(Version::HTTP_11)
332///     .headers(HeaderMap::new())
333///     .body(Bytes::new())
334///     .build()
335///     .unwrap();
336/// request.method = Method::GET;
337/// let context = PermissionContext {
338///     request: &request,
339///     is_authenticated: false,
340///     is_admin: false,
341///     is_active: false,
342///     user: None,
343/// };
344/// assert!(permission.has_permission(&context).await);
345///
346/// // POST request from anonymous user - denied
347/// request.method = Method::POST;
348/// let context = PermissionContext {
349///     request: &request,
350///     is_authenticated: false,
351///     is_admin: false,
352///     is_active: false,
353///     user: None,
354/// };
355/// assert!(!permission.has_permission(&context).await);
356///
357/// // POST request from authenticated user - allowed
358/// let context = PermissionContext {
359///     request: &request,
360///     is_authenticated: true,
361///     is_admin: false,
362///     is_active: true,
363///     user: None,
364/// };
365/// assert!(permission.has_permission(&context).await);
366/// # });
367/// ```
368#[derive(Clone, Copy, Default)]
369pub struct IsAuthenticatedOrReadOnly;
370
371#[async_trait]
372impl Permission for IsAuthenticatedOrReadOnly {
373	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
374		use hyper::Method;
375
376		// Allow read-only methods
377		matches!(
378			context.request.method,
379			Method::GET | Method::HEAD | Method::OPTIONS
380		) || context.is_authenticated
381	}
382}