Skip to main content

mnemo_core/model/
acl.rs

1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5pub struct Acl {
6    pub id: Uuid,
7    pub memory_id: Uuid,
8    pub principal_type: PrincipalType,
9    pub principal_id: String,
10    pub permission: Permission,
11    pub granted_by: String,
12    pub created_at: String,
13    pub expires_at: Option<String>,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum Permission {
19    Read,
20    Write,
21    Delete,
22    Share,
23    Delegate,
24    Admin,
25}
26
27impl std::fmt::Display for Permission {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Permission::Read => write!(f, "read"),
31            Permission::Write => write!(f, "write"),
32            Permission::Delete => write!(f, "delete"),
33            Permission::Share => write!(f, "share"),
34            Permission::Delegate => write!(f, "delegate"),
35            Permission::Admin => write!(f, "admin"),
36        }
37    }
38}
39
40impl std::str::FromStr for Permission {
41    type Err = crate::error::Error;
42    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
43        match s {
44            "read" => Ok(Permission::Read),
45            "write" => Ok(Permission::Write),
46            "delete" => Ok(Permission::Delete),
47            "share" => Ok(Permission::Share),
48            "delegate" => Ok(Permission::Delegate),
49            "admin" => Ok(Permission::Admin),
50            _ => Err(crate::error::Error::Validation(format!(
51                "invalid permission: {s}"
52            ))),
53        }
54    }
55}
56
57impl Permission {
58    pub fn satisfies(&self, required: Permission) -> bool {
59        // Hierarchy: Admin > Delegate > Share > Delete > Write > Read
60        let level = |p: &Permission| -> u8 {
61            match p {
62                Permission::Read => 0,
63                Permission::Write => 1,
64                Permission::Delete => 2,
65                Permission::Share => 3,
66                Permission::Delegate => 4,
67                Permission::Admin => 5,
68            }
69        };
70        level(self) >= level(&required)
71    }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(rename_all = "snake_case")]
76pub enum PrincipalType {
77    Agent,
78    Org,
79    Public,
80    User,
81    Role,
82}
83
84impl std::fmt::Display for PrincipalType {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        match self {
87            PrincipalType::Agent => write!(f, "agent"),
88            PrincipalType::Org => write!(f, "org"),
89            PrincipalType::Public => write!(f, "public"),
90            PrincipalType::User => write!(f, "user"),
91            PrincipalType::Role => write!(f, "role"),
92        }
93    }
94}
95
96impl std::str::FromStr for PrincipalType {
97    type Err = crate::error::Error;
98    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
99        match s {
100            "agent" => Ok(PrincipalType::Agent),
101            "org" => Ok(PrincipalType::Org),
102            "public" => Ok(PrincipalType::Public),
103            "user" => Ok(PrincipalType::User),
104            "role" => Ok(PrincipalType::Role),
105            _ => Err(crate::error::Error::Validation(format!(
106                "invalid principal type: {s}"
107            ))),
108        }
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_acl_serde_roundtrip() {
118        let acl = Acl {
119            id: Uuid::now_v7(),
120            memory_id: Uuid::now_v7(),
121            principal_type: PrincipalType::Agent,
122            principal_id: "agent-2".to_string(),
123            permission: Permission::Read,
124            granted_by: "agent-1".to_string(),
125            created_at: "2025-01-01T00:00:00Z".to_string(),
126            expires_at: None,
127        };
128        let json = serde_json::to_string(&acl).unwrap();
129        let deserialized: Acl = serde_json::from_str(&json).unwrap();
130        assert_eq!(acl, deserialized);
131    }
132
133    #[test]
134    fn test_permission_satisfies() {
135        // Admin satisfies everything
136        assert!(Permission::Admin.satisfies(Permission::Read));
137        assert!(Permission::Admin.satisfies(Permission::Write));
138        assert!(Permission::Admin.satisfies(Permission::Delete));
139        assert!(Permission::Admin.satisfies(Permission::Share));
140        assert!(Permission::Admin.satisfies(Permission::Delegate));
141        assert!(Permission::Admin.satisfies(Permission::Admin));
142        // Write satisfies Read and Write but not higher
143        assert!(Permission::Write.satisfies(Permission::Read));
144        assert!(Permission::Write.satisfies(Permission::Write));
145        assert!(!Permission::Write.satisfies(Permission::Admin));
146        assert!(!Permission::Write.satisfies(Permission::Delete));
147        // Read only satisfies Read
148        assert!(Permission::Read.satisfies(Permission::Read));
149        assert!(!Permission::Read.satisfies(Permission::Write));
150        // Delegate satisfies Share and below
151        assert!(Permission::Delegate.satisfies(Permission::Share));
152        assert!(Permission::Delegate.satisfies(Permission::Delete));
153        assert!(!Permission::Delegate.satisfies(Permission::Admin));
154    }
155}