1use std::collections::{HashMap, HashSet};
6use std::sync::{Arc, OnceLock};
7use tokio::sync::RwLock;
8use uuid::Uuid;
9
10static GLOBAL_GROUP_MANAGER: OnceLock<Arc<GroupManager>> = OnceLock::new();
15
16pub fn register_group_manager(manager: Arc<GroupManager>) {
25 GLOBAL_GROUP_MANAGER
26 .set(manager)
27 .expect("GroupManager has already been registered");
28}
29
30pub fn get_group_manager() -> Option<&'static Arc<GroupManager>> {
32 GLOBAL_GROUP_MANAGER.get()
33}
34
35#[non_exhaustive]
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum GroupManagementError {
39 GroupNotFound,
41 GroupAlreadyExists,
43 InvalidGroupName,
45 UserNotFound,
47 Other(String),
49}
50
51impl std::fmt::Display for GroupManagementError {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 GroupManagementError::GroupNotFound => write!(f, "Group not found"),
55 GroupManagementError::GroupAlreadyExists => write!(f, "Group already exists"),
56 GroupManagementError::InvalidGroupName => write!(f, "Invalid group name"),
57 GroupManagementError::UserNotFound => write!(f, "User not found"),
58 GroupManagementError::Other(msg) => write!(f, "Error: {}", msg),
59 }
60 }
61}
62
63impl std::error::Error for GroupManagementError {}
64
65pub type GroupManagementResult<T> = Result<T, GroupManagementError>;
67
68#[cfg_attr(
88 feature = "database",
89 reinhardt_core::macros::model(app_label = "auth", table_name = "auth_group")
90)]
91#[derive(Debug, Clone, PartialEq, Eq)]
92#[cfg_attr(feature = "database", derive(serde::Serialize, serde::Deserialize))]
93pub struct Group {
94 #[cfg_attr(feature = "database", field(primary_key = true))]
96 pub id: Uuid,
97 #[cfg_attr(feature = "database", field(max_length = 150, unique = true))]
99 pub name: String,
100 #[cfg_attr(feature = "database", field(max_length = 500, null = true))]
102 pub description: Option<String>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct CreateGroupData {
121 pub name: String,
123 pub description: Option<String>,
125}
126
127#[derive(Debug)]
162pub struct GroupManager {
163 groups: Arc<RwLock<HashMap<Uuid, Group>>>,
164 group_index: Arc<RwLock<HashMap<String, Uuid>>>,
165 group_permissions: Arc<RwLock<HashMap<Uuid, HashSet<String>>>>,
166 user_groups: Arc<RwLock<HashMap<String, HashSet<Uuid>>>>,
167}
168
169impl GroupManager {
170 pub fn new() -> Self {
180 Self {
181 groups: Arc::new(RwLock::new(HashMap::new())),
182 group_index: Arc::new(RwLock::new(HashMap::new())),
183 group_permissions: Arc::new(RwLock::new(HashMap::new())),
184 user_groups: Arc::new(RwLock::new(HashMap::new())),
185 }
186 }
187
188 pub async fn create_group(&mut self, data: CreateGroupData) -> GroupManagementResult<Group> {
209 if data.name.is_empty() || data.name.len() < 2 {
211 return Err(GroupManagementError::InvalidGroupName);
212 }
213
214 let group_index = self.group_index.read().await;
216 if group_index.contains_key(&data.name) {
217 return Err(GroupManagementError::GroupAlreadyExists);
218 }
219 drop(group_index);
220
221 let group = Group {
223 id: Uuid::new_v4(),
224 name: data.name.clone(),
225 description: data.description,
226 };
227
228 let mut groups = self.groups.write().await;
230 let mut group_index = self.group_index.write().await;
231
232 groups.insert(group.id, group.clone());
233 group_index.insert(data.name, group.id);
234
235 Ok(group)
236 }
237
238 pub async fn get_group(&self, group_id: &str) -> GroupManagementResult<Group> {
260 let uuid = Uuid::parse_str(group_id)
261 .map_err(|_| GroupManagementError::Other("Invalid UUID".to_string()))?;
262
263 let groups = self.groups.read().await;
264 groups
265 .get(&uuid)
266 .cloned()
267 .ok_or(GroupManagementError::GroupNotFound)
268 }
269
270 pub async fn get_group_by_name(&self, name: &str) -> GroupManagementResult<Group> {
292 let group_index = self.group_index.read().await;
293 let group_id = group_index
294 .get(name)
295 .ok_or(GroupManagementError::GroupNotFound)?;
296
297 let groups = self.groups.read().await;
298 groups
299 .get(group_id)
300 .cloned()
301 .ok_or(GroupManagementError::GroupNotFound)
302 }
303
304 pub async fn delete_group(&mut self, group_id: &str) -> GroupManagementResult<()> {
326 let uuid = Uuid::parse_str(group_id)
327 .map_err(|_| GroupManagementError::Other("Invalid UUID".to_string()))?;
328
329 let mut groups = self.groups.write().await;
330 let group = groups
331 .get(&uuid)
332 .ok_or(GroupManagementError::GroupNotFound)?
333 .clone();
334
335 let mut group_index = self.group_index.write().await;
336 let mut group_permissions = self.group_permissions.write().await;
337 let mut user_groups = self.user_groups.write().await;
338
339 groups.remove(&uuid);
340 group_index.remove(&group.name);
341 group_permissions.remove(&uuid);
342
343 for user_group_set in user_groups.values_mut() {
345 user_group_set.remove(&uuid);
346 }
347
348 Ok(())
349 }
350
351 pub async fn add_group_permission(
375 &mut self,
376 group_id: &str,
377 permission: &str,
378 ) -> GroupManagementResult<()> {
379 let uuid = Uuid::parse_str(group_id)
380 .map_err(|_| GroupManagementError::Other("Invalid UUID".to_string()))?;
381
382 let groups = self.groups.read().await;
384 if !groups.contains_key(&uuid) {
385 return Err(GroupManagementError::GroupNotFound);
386 }
387 drop(groups);
388
389 let mut group_permissions = self.group_permissions.write().await;
390 group_permissions
391 .entry(uuid)
392 .or_default()
393 .insert(permission.to_string());
394
395 Ok(())
396 }
397
398 pub async fn remove_group_permission(
423 &mut self,
424 group_id: &str,
425 permission: &str,
426 ) -> GroupManagementResult<()> {
427 let uuid = Uuid::parse_str(group_id)
428 .map_err(|_| GroupManagementError::Other("Invalid UUID".to_string()))?;
429
430 let mut group_permissions = self.group_permissions.write().await;
431 if let Some(perms) = group_permissions.get_mut(&uuid) {
432 perms.remove(permission);
433 }
434
435 Ok(())
436 }
437
438 pub async fn get_group_permissions(
463 &self,
464 group_id: &str,
465 ) -> GroupManagementResult<Vec<String>> {
466 let uuid = Uuid::parse_str(group_id)
467 .map_err(|_| GroupManagementError::Other("Invalid UUID".to_string()))?;
468
469 let group_permissions = self.group_permissions.read().await;
470 Ok(group_permissions
471 .get(&uuid)
472 .map(|perms| perms.iter().cloned().collect())
473 .unwrap_or_default())
474 }
475
476 pub async fn add_user_to_group(
500 &mut self,
501 username: &str,
502 group_id: &str,
503 ) -> GroupManagementResult<()> {
504 let uuid = Uuid::parse_str(group_id)
505 .map_err(|_| GroupManagementError::Other("Invalid UUID".to_string()))?;
506
507 let groups = self.groups.read().await;
509 if !groups.contains_key(&uuid) {
510 return Err(GroupManagementError::GroupNotFound);
511 }
512 drop(groups);
513
514 let mut user_groups = self.user_groups.write().await;
515 user_groups
516 .entry(username.to_string())
517 .or_default()
518 .insert(uuid);
519
520 Ok(())
521 }
522
523 pub async fn remove_user_from_group(
548 &mut self,
549 username: &str,
550 group_id: &str,
551 ) -> GroupManagementResult<()> {
552 let uuid = Uuid::parse_str(group_id)
553 .map_err(|_| GroupManagementError::Other("Invalid UUID".to_string()))?;
554
555 let mut user_groups = self.user_groups.write().await;
556 if let Some(groups) = user_groups.get_mut(username) {
557 groups.remove(&uuid);
558 }
559
560 Ok(())
561 }
562
563 pub async fn get_user_groups(&self, username: &str) -> GroupManagementResult<Vec<Group>> {
594 let user_groups = self.user_groups.read().await;
595 let group_ids = user_groups.get(username).cloned().unwrap_or_default();
596
597 let groups = self.groups.read().await;
598 Ok(group_ids
599 .iter()
600 .filter_map(|id| groups.get(id).cloned())
601 .collect())
602 }
603
604 pub async fn get_user_permissions(&self, username: &str) -> GroupManagementResult<Vec<String>> {
631 let user_groups = self.user_groups.read().await;
632 let group_ids = user_groups.get(username).cloned().unwrap_or_default();
633
634 let group_permissions = self.group_permissions.read().await;
635 let mut all_permissions = HashSet::new();
636
637 for group_id in group_ids {
638 if let Some(perms) = group_permissions.get(&group_id) {
639 all_permissions.extend(perms.clone());
640 }
641 }
642
643 Ok(all_permissions.into_iter().collect())
644 }
645
646 pub async fn list_groups(&self) -> Vec<Group> {
674 let groups = self.groups.read().await;
675 groups.values().cloned().collect()
676 }
677
678 pub fn get_permissions_for_groups_sync(
686 &self,
687 group_names: &[String],
688 ) -> std::collections::HashSet<String> {
689 let index = match self.group_index.try_read() {
690 Ok(guard) => guard,
691 Err(_) => return std::collections::HashSet::new(),
692 };
693 let perms = match self.group_permissions.try_read() {
694 Ok(guard) => guard,
695 Err(_) => return std::collections::HashSet::new(),
696 };
697
698 let mut result = std::collections::HashSet::new();
699 for name in group_names {
700 if let Some(group_id) = index.get(name)
701 && let Some(group_perms) = perms.get(group_id)
702 {
703 result.extend(group_perms.iter().cloned());
704 }
705 }
706 result
707 }
708}
709
710impl Default for GroupManager {
711 fn default() -> Self {
712 Self::new()
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719
720 #[tokio::test]
721 async fn test_create_group() {
722 let mut manager = GroupManager::new();
723
724 let group_data = CreateGroupData {
725 name: "Editors".to_string(),
726 description: Some("Content editors".to_string()),
727 };
728
729 let group = manager.create_group(group_data).await.unwrap();
730 assert_eq!(group.name, "Editors");
731 assert_eq!(group.description, Some("Content editors".to_string()));
732 }
733
734 #[tokio::test]
735 async fn test_get_group() {
736 let mut manager = GroupManager::new();
737
738 let group_data = CreateGroupData {
739 name: "Moderators".to_string(),
740 description: None,
741 };
742
743 let group = manager.create_group(group_data).await.unwrap();
744 let retrieved = manager.get_group(&group.id.to_string()).await.unwrap();
745 assert_eq!(retrieved.name, "Moderators");
746 }
747
748 #[tokio::test]
749 async fn test_get_group_by_name() {
750 let mut manager = GroupManager::new();
751
752 let group_data = CreateGroupData {
753 name: "Viewers".to_string(),
754 description: None,
755 };
756
757 manager.create_group(group_data).await.unwrap();
758 let retrieved = manager.get_group_by_name("Viewers").await.unwrap();
759 assert_eq!(retrieved.name, "Viewers");
760 }
761
762 #[tokio::test]
763 async fn test_delete_group() {
764 let mut manager = GroupManager::new();
765
766 let group_data = CreateGroupData {
767 name: "TempGroup".to_string(),
768 description: None,
769 };
770
771 let group = manager.create_group(group_data).await.unwrap();
772 manager.delete_group(&group.id.to_string()).await.unwrap();
773 let result = manager.get_group(&group.id.to_string()).await;
774 assert!(result.is_err());
775 }
776
777 #[tokio::test]
778 async fn test_group_permissions() {
779 let mut manager = GroupManager::new();
780
781 let group_data = CreateGroupData {
782 name: "Writers".to_string(),
783 description: None,
784 };
785
786 let group = manager.create_group(group_data).await.unwrap();
787 manager
788 .add_group_permission(&group.id.to_string(), "blog.add_article")
789 .await
790 .unwrap();
791 manager
792 .add_group_permission(&group.id.to_string(), "blog.change_article")
793 .await
794 .unwrap();
795
796 let perms = manager
797 .get_group_permissions(&group.id.to_string())
798 .await
799 .unwrap();
800 assert_eq!(perms.len(), 2);
801 assert!(perms.contains(&"blog.add_article".to_string()));
802 assert!(perms.contains(&"blog.change_article".to_string()));
803 }
804
805 #[tokio::test]
806 async fn test_user_groups() {
807 let mut manager = GroupManager::new();
808
809 let group_data1 = CreateGroupData {
810 name: "Team1".to_string(),
811 description: None,
812 };
813 let group_data2 = CreateGroupData {
814 name: "Team2".to_string(),
815 description: None,
816 };
817
818 let group1 = manager.create_group(group_data1).await.unwrap();
819 let group2 = manager.create_group(group_data2).await.unwrap();
820
821 manager
822 .add_user_to_group("alice", &group1.id.to_string())
823 .await
824 .unwrap();
825 manager
826 .add_user_to_group("alice", &group2.id.to_string())
827 .await
828 .unwrap();
829
830 let groups = manager.get_user_groups("alice").await.unwrap();
831 assert_eq!(groups.len(), 2);
832 }
833
834 #[tokio::test]
835 async fn test_user_permissions() {
836 let mut manager = GroupManager::new();
837
838 let group_data = CreateGroupData {
839 name: "Developers".to_string(),
840 description: None,
841 };
842
843 let group = manager.create_group(group_data).await.unwrap();
844 manager
845 .add_group_permission(&group.id.to_string(), "code.commit")
846 .await
847 .unwrap();
848 manager
849 .add_group_permission(&group.id.to_string(), "code.review")
850 .await
851 .unwrap();
852 manager
853 .add_user_to_group("bob", &group.id.to_string())
854 .await
855 .unwrap();
856
857 let perms = manager.get_user_permissions("bob").await.unwrap();
858 assert_eq!(perms.len(), 2);
859 assert!(perms.contains(&"code.commit".to_string()));
860 assert!(perms.contains(&"code.review".to_string()));
861 }
862
863 #[tokio::test]
864 async fn test_remove_user_from_group() {
865 let mut manager = GroupManager::new();
866
867 let group_data = CreateGroupData {
868 name: "Staff".to_string(),
869 description: None,
870 };
871
872 let group = manager.create_group(group_data).await.unwrap();
873 manager
874 .add_user_to_group("charlie", &group.id.to_string())
875 .await
876 .unwrap();
877 manager
878 .remove_user_from_group("charlie", &group.id.to_string())
879 .await
880 .unwrap();
881
882 let groups = manager.get_user_groups("charlie").await.unwrap();
883 assert_eq!(groups.len(), 0);
884 }
885
886 #[tokio::test]
887 async fn test_list_groups() {
888 let mut manager = GroupManager::new();
889
890 let group_data1 = CreateGroupData {
891 name: "Group1".to_string(),
892 description: None,
893 };
894 let group_data2 = CreateGroupData {
895 name: "Group2".to_string(),
896 description: None,
897 };
898
899 manager.create_group(group_data1).await.unwrap();
900 manager.create_group(group_data2).await.unwrap();
901
902 let groups = manager.list_groups().await;
903 assert_eq!(groups.len(), 2);
904 }
905}