Skip to main content

reinhardt_auth/
object_permissions.rs

1//! Object-Level Permissions
2//!
3//! Provides permission checking on individual object instances.
4
5// This module uses the deprecated User trait for backward compatibility.
6// ObjectPermission accepts &dyn User to preserve existing permission APIs.
7#![allow(deprecated)]
8use crate::User;
9use crate::{Permission, PermissionContext};
10use async_trait::async_trait;
11use std::collections::HashMap;
12use std::sync::Arc;
13use tokio::sync::RwLock;
14
15// Type alias to simplify complex permission map type
16/// Permission map keyed by (username, object_id) tuple, storing a list of permission strings
17pub type PermissionMap = Arc<RwLock<HashMap<(String, String), Vec<String>>>>;
18
19/// Object permission checker trait
20///
21/// Allows for custom permission logic on specific object instances.
22///
23/// # Examples
24///
25/// ```
26/// use reinhardt_auth::object_permissions::ObjectPermissionChecker;
27/// use reinhardt_auth::{User, SimpleUser};
28/// use async_trait::async_trait;
29/// use uuid::Uuid;
30///
31/// struct ArticlePermissionChecker;
32///
33/// #[async_trait]
34/// impl ObjectPermissionChecker for ArticlePermissionChecker {
35///     async fn has_object_permission(
36///         &self,
37///         user: &dyn User,
38///         object_id: &str,
39///         permission: &str,
40///     ) -> bool {
41///         // Example: Check if user is the owner
42///         user.username() == "alice" && permission == "change"
43///     }
44/// }
45///
46/// #[tokio::main]
47/// async fn main() {
48///     let checker = ArticlePermissionChecker;
49///     let user = SimpleUser {
50///         id: Uuid::new_v4(),
51///         username: "alice".to_string(),
52///         email: "alice@example.com".to_string(),
53///         is_active: true,
54///         is_admin: false,
55///         is_staff: false,
56///         is_superuser: false,
57///     };
58///
59///     assert!(checker.has_object_permission(&user, "article:123", "change").await);
60///     assert!(!checker.has_object_permission(&user, "article:123", "delete").await);
61/// }
62/// ```
63#[async_trait]
64pub trait ObjectPermissionChecker: Send + Sync {
65	/// Check if user has permission for specific object
66	///
67	/// # Arguments
68	///
69	/// * `user` - The user to check permissions for
70	/// * `object_id` - Identifier for the object
71	/// * `permission` - Permission to check (e.g., "view", "change", "delete")
72	///
73	/// # Returns
74	///
75	/// `true` if user has permission, `false` otherwise
76	async fn has_object_permission(
77		&self,
78		user: &dyn User,
79		object_id: &str,
80		permission: &str,
81	) -> bool;
82}
83
84/// Object permission manager
85///
86/// Manages object-level permissions using a permission map.
87///
88/// # Examples
89///
90/// ```
91/// use reinhardt_auth::object_permissions::{ObjectPermissionManager, ObjectPermissionChecker};
92/// use reinhardt_auth::{SimpleUser, User};
93/// use uuid::Uuid;
94///
95/// #[tokio::main]
96/// async fn main() {
97///     let mut manager = ObjectPermissionManager::new();
98///
99///     // Grant alice permission to change article:123
100///     manager.grant_permission("alice", "article:123", "change").await;
101///
102///     let user = SimpleUser {
103///         id: Uuid::new_v4(),
104///         username: "alice".to_string(),
105///         email: "alice@example.com".to_string(),
106///         is_active: true,
107///         is_admin: false,
108///         is_staff: false,
109///         is_superuser: false,
110///     };
111///
112///     assert!(manager.has_object_permission(&user, "article:123", "change").await);
113///     assert!(!manager.has_object_permission(&user, "article:123", "delete").await);
114///
115///     // Revoke permission
116///     manager.revoke_permission("alice", "article:123", "change").await;
117///     assert!(!manager.has_object_permission(&user, "article:123", "change").await);
118/// }
119/// ```
120pub struct ObjectPermissionManager {
121	/// Permissions map: (username, object_id) -> list of permissions
122	permissions: PermissionMap,
123}
124
125impl ObjectPermissionManager {
126	/// Create a new object permission manager
127	///
128	/// # Examples
129	///
130	/// ```
131	/// use reinhardt_auth::object_permissions::ObjectPermissionManager;
132	///
133	/// let manager = ObjectPermissionManager::new();
134	/// ```
135	pub fn new() -> Self {
136		Self {
137			permissions: Arc::new(RwLock::new(HashMap::new())),
138		}
139	}
140
141	/// Grant permission to user for specific object
142	///
143	/// # Examples
144	///
145	/// ```
146	/// use reinhardt_auth::object_permissions::ObjectPermissionManager;
147	///
148	/// #[tokio::main]
149	/// async fn main() {
150	///     let mut manager = ObjectPermissionManager::new();
151	///     manager.grant_permission("alice", "article:123", "change").await;
152	///     manager.grant_permission("alice", "article:123", "view").await;
153	/// }
154	/// ```
155	pub async fn grant_permission(&mut self, username: &str, object_id: &str, permission: &str) {
156		let mut perms = self.permissions.write().await;
157		let key = (username.to_string(), object_id.to_string());
158		perms.entry(key).or_default().push(permission.to_string());
159	}
160
161	/// Revoke permission from user for specific object
162	///
163	/// # Examples
164	///
165	/// ```
166	/// use reinhardt_auth::object_permissions::ObjectPermissionManager;
167	///
168	/// #[tokio::main]
169	/// async fn main() {
170	///     let mut manager = ObjectPermissionManager::new();
171	///     manager.grant_permission("alice", "article:123", "change").await;
172	///     manager.revoke_permission("alice", "article:123", "change").await;
173	/// }
174	/// ```
175	pub async fn revoke_permission(&mut self, username: &str, object_id: &str, permission: &str) {
176		let mut perms = self.permissions.write().await;
177		let key = (username.to_string(), object_id.to_string());
178		if let Some(user_perms) = perms.get_mut(&key) {
179			user_perms.retain(|p| p != permission);
180			if user_perms.is_empty() {
181				perms.remove(&key);
182			}
183		}
184	}
185
186	/// Revoke all permissions for user on specific object
187	///
188	/// # Examples
189	///
190	/// ```
191	/// use reinhardt_auth::object_permissions::ObjectPermissionManager;
192	///
193	/// #[tokio::main]
194	/// async fn main() {
195	///     let mut manager = ObjectPermissionManager::new();
196	///     manager.grant_permission("alice", "article:123", "change").await;
197	///     manager.grant_permission("alice", "article:123", "view").await;
198	///     manager.revoke_all_permissions("alice", "article:123").await;
199	/// }
200	/// ```
201	pub async fn revoke_all_permissions(&mut self, username: &str, object_id: &str) {
202		let mut perms = self.permissions.write().await;
203		let key = (username.to_string(), object_id.to_string());
204		perms.remove(&key);
205	}
206
207	/// List all permissions for user on specific object
208	///
209	/// # Examples
210	///
211	/// ```
212	/// use reinhardt_auth::object_permissions::ObjectPermissionManager;
213	///
214	/// #[tokio::main]
215	/// async fn main() {
216	///     let mut manager = ObjectPermissionManager::new();
217	///     manager.grant_permission("alice", "article:123", "change").await;
218	///     manager.grant_permission("alice", "article:123", "view").await;
219	///
220	///     let perms = manager.list_permissions("alice", "article:123").await;
221	///     assert_eq!(perms.len(), 2);
222	/// }
223	/// ```
224	pub async fn list_permissions(&self, username: &str, object_id: &str) -> Vec<String> {
225		let perms = self.permissions.read().await;
226		let key = (username.to_string(), object_id.to_string());
227		perms.get(&key).cloned().unwrap_or_default()
228	}
229}
230
231impl Default for ObjectPermissionManager {
232	fn default() -> Self {
233		Self::new()
234	}
235}
236
237#[async_trait]
238impl ObjectPermissionChecker for ObjectPermissionManager {
239	async fn has_object_permission(
240		&self,
241		user: &dyn User,
242		object_id: &str,
243		permission: &str,
244	) -> bool {
245		let perms = self.permissions.read().await;
246		let key = (user.username().to_string(), object_id.to_string());
247		if let Some(user_perms) = perms.get(&key) {
248			return user_perms.iter().any(|p| p == permission);
249		}
250		false
251	}
252}
253
254/// Object permission with Permission trait support
255///
256/// Wraps an `ObjectPermissionChecker` for use with the `Permission` trait.
257///
258/// # Examples
259///
260/// ```
261/// use reinhardt_auth::object_permissions::{ObjectPermission, ObjectPermissionManager};
262/// use reinhardt_auth::{Permission, PermissionContext};
263/// use reinhardt_auth::{SimpleUser, User};
264/// use bytes::Bytes;
265/// use hyper::{Method};
266/// use reinhardt_http::Request;
267/// use uuid::Uuid;
268///
269/// #[tokio::main]
270/// async fn main() {
271///     let mut manager = ObjectPermissionManager::new();
272///     manager.grant_permission("alice", "article:123", "view").await;
273///
274///     let perm = ObjectPermission::new(manager, "article:123", "view");
275///
276///     let user = SimpleUser {
277///         id: Uuid::new_v4(),
278///         username: "alice".to_string(),
279///         email: "alice@example.com".to_string(),
280///         is_active: true,
281///         is_admin: false,
282///         is_staff: false,
283///         is_superuser: false,
284///     };
285///
286///     let request = Request::builder()
287///         .method(Method::GET)
288///         .uri("/")
289///         .body(Bytes::new())
290///         .build()
291///         .unwrap();
292///
293///     let context = PermissionContext {
294///         request: &request,
295///         is_authenticated: true,
296///         is_admin: false,
297///         is_active: true,
298///         user: Some(Box::new(user)),
299///     };
300///
301///     assert!(perm.has_permission(&context).await);
302/// }
303/// ```
304pub struct ObjectPermission<T: ObjectPermissionChecker + Send + Sync> {
305	checker: T,
306	object_id: String,
307	permission: String,
308}
309
310impl<T: ObjectPermissionChecker + Send + Sync> ObjectPermission<T> {
311	/// Create a new object permission
312	///
313	/// # Examples
314	///
315	/// ```
316	/// use reinhardt_auth::object_permissions::{ObjectPermission, ObjectPermissionManager};
317	///
318	/// let manager = ObjectPermissionManager::new();
319	/// let perm = ObjectPermission::new(manager, "article:123", "view");
320	/// ```
321	pub fn new(checker: T, object_id: impl Into<String>, permission: impl Into<String>) -> Self {
322		Self {
323			checker,
324			object_id: object_id.into(),
325			permission: permission.into(),
326		}
327	}
328}
329
330#[async_trait]
331impl<T: ObjectPermissionChecker + Send + Sync> Permission for ObjectPermission<T> {
332	async fn has_permission(&self, context: &PermissionContext<'_>) -> bool {
333		if !context.is_authenticated {
334			return false;
335		}
336
337		if let Some(ref user) = context.user {
338			return self
339				.checker
340				.has_object_permission(user.as_ref(), &self.object_id, &self.permission)
341				.await;
342		}
343
344		false
345	}
346}
347
348#[cfg(test)]
349mod tests {
350	use super::*;
351	use crate::SimpleUser;
352	use bytes::Bytes;
353	use hyper::Method;
354	use reinhardt_http::Request;
355	use rstest::rstest;
356	use uuid::Uuid;
357
358	#[tokio::test]
359	async fn test_object_permission_manager_grant() {
360		let mut manager = ObjectPermissionManager::new();
361		manager
362			.grant_permission("alice", "article:123", "view")
363			.await;
364		manager
365			.grant_permission("alice", "article:123", "change")
366			.await;
367
368		let user = SimpleUser {
369			id: Uuid::new_v4(),
370			username: "alice".to_string(),
371			email: "alice@example.com".to_string(),
372			is_active: true,
373			is_admin: false,
374			is_staff: false,
375			is_superuser: false,
376		};
377
378		assert!(
379			manager
380				.has_object_permission(&user, "article:123", "view")
381				.await
382		);
383		assert!(
384			manager
385				.has_object_permission(&user, "article:123", "change")
386				.await
387		);
388		assert!(
389			!manager
390				.has_object_permission(&user, "article:123", "delete")
391				.await
392		);
393	}
394
395	#[tokio::test]
396	async fn test_object_permission_manager_revoke() {
397		let mut manager = ObjectPermissionManager::new();
398		manager
399			.grant_permission("alice", "article:123", "view")
400			.await;
401		manager
402			.grant_permission("alice", "article:123", "change")
403			.await;
404
405		let user = SimpleUser {
406			id: Uuid::new_v4(),
407			username: "alice".to_string(),
408			email: "alice@example.com".to_string(),
409			is_active: true,
410			is_admin: false,
411			is_staff: false,
412			is_superuser: false,
413		};
414
415		manager
416			.revoke_permission("alice", "article:123", "view")
417			.await;
418
419		assert!(
420			!manager
421				.has_object_permission(&user, "article:123", "view")
422				.await
423		);
424		assert!(
425			manager
426				.has_object_permission(&user, "article:123", "change")
427				.await
428		);
429	}
430
431	#[tokio::test]
432	async fn test_object_permission_manager_revoke_all() {
433		let mut manager = ObjectPermissionManager::new();
434		manager
435			.grant_permission("alice", "article:123", "view")
436			.await;
437		manager
438			.grant_permission("alice", "article:123", "change")
439			.await;
440
441		let user = SimpleUser {
442			id: Uuid::new_v4(),
443			username: "alice".to_string(),
444			email: "alice@example.com".to_string(),
445			is_active: true,
446			is_admin: false,
447			is_staff: false,
448			is_superuser: false,
449		};
450
451		manager.revoke_all_permissions("alice", "article:123").await;
452
453		assert!(
454			!manager
455				.has_object_permission(&user, "article:123", "view")
456				.await
457		);
458		assert!(
459			!manager
460				.has_object_permission(&user, "article:123", "change")
461				.await
462		);
463	}
464
465	#[tokio::test]
466	async fn test_object_permission_manager_list() {
467		let mut manager = ObjectPermissionManager::new();
468		manager
469			.grant_permission("alice", "article:123", "view")
470			.await;
471		manager
472			.grant_permission("alice", "article:123", "change")
473			.await;
474
475		let perms = manager.list_permissions("alice", "article:123").await;
476		assert_eq!(perms.len(), 2);
477		assert!(perms.contains(&"view".to_string()));
478		assert!(perms.contains(&"change".to_string()));
479	}
480
481	#[tokio::test]
482	async fn test_object_permission_manager_different_objects() {
483		let mut manager = ObjectPermissionManager::new();
484		manager
485			.grant_permission("alice", "article:123", "view")
486			.await;
487		manager
488			.grant_permission("alice", "article:456", "change")
489			.await;
490
491		let user = SimpleUser {
492			id: Uuid::new_v4(),
493			username: "alice".to_string(),
494			email: "alice@example.com".to_string(),
495			is_active: true,
496			is_admin: false,
497			is_staff: false,
498			is_superuser: false,
499		};
500
501		assert!(
502			manager
503				.has_object_permission(&user, "article:123", "view")
504				.await
505		);
506		assert!(
507			!manager
508				.has_object_permission(&user, "article:123", "change")
509				.await
510		);
511		assert!(
512			!manager
513				.has_object_permission(&user, "article:456", "view")
514				.await
515		);
516		assert!(
517			manager
518				.has_object_permission(&user, "article:456", "change")
519				.await
520		);
521	}
522
523	#[tokio::test]
524	async fn test_object_permission_trait_authenticated() {
525		let mut manager = ObjectPermissionManager::new();
526		manager
527			.grant_permission("alice", "article:123", "view")
528			.await;
529
530		let perm = ObjectPermission::new(manager, "article:123", "view");
531
532		let user = SimpleUser {
533			id: Uuid::new_v4(),
534			username: "alice".to_string(),
535			email: "alice@example.com".to_string(),
536			is_active: true,
537			is_admin: false,
538			is_staff: false,
539			is_superuser: false,
540		};
541
542		let request = Request::builder()
543			.method(Method::GET)
544			.uri("/")
545			.body(Bytes::new())
546			.build()
547			.unwrap();
548
549		let context = PermissionContext {
550			request: &request,
551			is_authenticated: true,
552			is_admin: false,
553			is_active: true,
554			user: Some(Box::new(user)),
555		};
556
557		assert!(perm.has_permission(&context).await);
558	}
559
560	#[tokio::test]
561	async fn test_object_permission_trait_unauthenticated() {
562		let manager = ObjectPermissionManager::new();
563		let perm = ObjectPermission::new(manager, "article:123", "view");
564
565		let request = Request::builder()
566			.method(Method::GET)
567			.uri("/")
568			.body(Bytes::new())
569			.build()
570			.unwrap();
571
572		let context = PermissionContext {
573			request: &request,
574			is_authenticated: false,
575			is_admin: false,
576			is_active: false,
577			user: None,
578		};
579
580		assert!(!perm.has_permission(&context).await);
581	}
582
583	#[tokio::test]
584	async fn test_object_permission_trait_no_permission() {
585		let manager = ObjectPermissionManager::new();
586		let perm = ObjectPermission::new(manager, "article:123", "delete");
587
588		let user = SimpleUser {
589			id: Uuid::new_v4(),
590			username: "alice".to_string(),
591			email: "alice@example.com".to_string(),
592			is_active: true,
593			is_admin: false,
594			is_staff: false,
595			is_superuser: false,
596		};
597
598		let request = Request::builder()
599			.method(Method::DELETE)
600			.uri("/")
601			.body(Bytes::new())
602			.build()
603			.unwrap();
604
605		let context = PermissionContext {
606			request: &request,
607			is_authenticated: true,
608			is_admin: false,
609			is_active: true,
610			user: Some(Box::new(user)),
611		};
612
613		assert!(!perm.has_permission(&context).await);
614	}
615
616	#[rstest]
617	#[tokio::test]
618	async fn test_bulk_check_multiple_objects() {
619		// Arrange
620		let mut manager = ObjectPermissionManager::new();
621		manager.grant_permission("alice", "article:1", "view").await;
622		manager
623			.grant_permission("alice", "article:2", "change")
624			.await;
625		// article:3 has no permissions granted for alice
626
627		let user = SimpleUser {
628			id: Uuid::new_v4(),
629			username: "alice".to_string(),
630			email: "alice@example.com".to_string(),
631			is_active: true,
632			is_admin: false,
633			is_staff: false,
634			is_superuser: false,
635		};
636
637		// Act
638		let result_obj1 = manager
639			.has_object_permission(&user, "article:1", "view")
640			.await;
641		let result_obj2 = manager
642			.has_object_permission(&user, "article:2", "change")
643			.await;
644		let result_obj3 = manager
645			.has_object_permission(&user, "article:3", "view")
646			.await;
647
648		// Assert
649		assert!(result_obj1);
650		assert!(result_obj2);
651		assert!(!result_obj3);
652	}
653
654	#[rstest]
655	#[tokio::test]
656	async fn test_different_users_same_object() {
657		// Arrange
658		let mut manager = ObjectPermissionManager::new();
659		manager
660			.grant_permission("alice", "article:42", "view")
661			.await;
662
663		let user_a = SimpleUser {
664			id: Uuid::new_v4(),
665			username: "alice".to_string(),
666			email: "alice@example.com".to_string(),
667			is_active: true,
668			is_admin: false,
669			is_staff: false,
670			is_superuser: false,
671		};
672		let user_b = SimpleUser {
673			id: Uuid::new_v4(),
674			username: "bob".to_string(),
675			email: "bob@example.com".to_string(),
676			is_active: true,
677			is_admin: false,
678			is_staff: false,
679			is_superuser: false,
680		};
681
682		// Act
683		let result_a = manager
684			.has_object_permission(&user_a, "article:42", "view")
685			.await;
686		let result_b = manager
687			.has_object_permission(&user_b, "article:42", "view")
688			.await;
689
690		// Assert
691		assert!(result_a);
692		assert!(!result_b);
693	}
694}