Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/auth/types/
permissions.rs

1//! Permission system for authentication
2//!
3//! This module defines the permission levels and operations for authentication.
4
5use serde::{Deserialize, Serialize};
6
7use super::super::permission::clamp_permission;
8use crate::crdt::{CRDTError, Doc, doc::Value};
9
10/// Permission levels for authenticated operations
11#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
12pub enum Permission {
13    /// Full administrative access including settings and key management
14    /// Priority may be used for conflict resolution, lower number = higher priority
15    /// Admin keys always have priority over Write keys
16    Admin(u32),
17    /// Read and write access to data (excludes settings modifications)
18    /// Priority may be used for conflict resolution, lower number = higher priority
19    Write(u32),
20    /// Read-only access to data
21    Read,
22}
23
24impl PartialOrd for Permission {
25    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
26        Some(self.cmp(other))
27    }
28}
29
30impl Ord for Permission {
31    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
32        self.ordering_value().cmp(&other.ordering_value())
33    }
34}
35
36impl Permission {
37    /// Calculate ordering value for mathematical comparison
38    /// Read = 0, Write(p) = 1 + u32::MAX - p, Admin(p) = 2 + (2 * u32::MAX) - p
39    /// This ensures Admin permissions are always > Write permissions
40    fn ordering_value(&self) -> u64 {
41        match self {
42            Permission::Read => 0,
43            Permission::Write(p) => 1 + (u32::MAX as u64) - (*p as u64),
44            Permission::Admin(p) => 2 + (2 * u32::MAX as u64) - (*p as u64),
45        }
46    }
47
48    /// Get the priority level for permissions that have one
49    pub fn priority(&self) -> Option<u32> {
50        match self {
51            Permission::Read => None,
52            Permission::Write(priority) => Some(*priority),
53            Permission::Admin(priority) => Some(*priority),
54        }
55    }
56
57    /// Check if this permission allows writing data
58    pub fn can_write(&self) -> bool {
59        matches!(self, Permission::Write(_) | Permission::Admin(_))
60    }
61
62    /// Check if this permission allows administrative operations
63    pub fn can_admin(&self) -> bool {
64        matches!(self, Permission::Admin(_))
65    }
66
67    /// Clamp permissions to a maximum level
68    ///
69    /// Used for delegated tree delegation to ensure users cannot escalate
70    /// their permissions beyond what was granted in the main tree.
71    /// Returns the minimum of self and max_permission.
72    pub fn clamp_to(&self, max_permission: &Permission) -> Permission {
73        use std::cmp::min;
74        min(*self, *max_permission)
75    }
76
77    /// Clamp permissions within bounds (for delegated trees)
78    ///
79    /// Applies both minimum and maximum bounds from PermissionBounds.
80    /// If min is specified and self is below min, raises to min.
81    /// If self is above max, lowers to max.
82    pub fn clamp_to_bounds(&self, bounds: &PermissionBounds) -> Permission {
83        clamp_permission(*self, bounds)
84    }
85}
86
87/// Status of an authentication key
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
89pub enum KeyStatus {
90    /// Key is active and can create new entries
91    Active,
92    /// Key is revoked - cannot create new entries, but historical entries are preserved
93    /// Content of revoked entries is preserved during merges, but cannot be parents of new entries
94    Revoked,
95}
96
97/// Permission bounds for delegated trees
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
99pub struct PermissionBounds {
100    /// Maximum permission level (required)
101    pub max: Permission,
102    /// Minimum permission level (optional)
103    pub min: Option<Permission>,
104}
105
106impl Default for PermissionBounds {
107    fn default() -> Self {
108        Self {
109            max: Permission::Read,
110            min: None,
111        }
112    }
113}
114
115// ==================== Doc Conversions ====================
116
117impl From<Permission> for Value {
118    fn from(perm: Permission) -> Value {
119        Value::Doc(Doc::from(perm))
120    }
121}
122
123impl From<Permission> for Doc {
124    fn from(perm: Permission) -> Doc {
125        let mut doc = Doc::new();
126        match perm {
127            Permission::Admin(p) => {
128                doc.set("type", "Admin");
129                doc.set("priority", p);
130            }
131            Permission::Write(p) => {
132                doc.set("type", "Write");
133                doc.set("priority", p);
134            }
135            Permission::Read => {
136                doc.set("type", "Read");
137            }
138        }
139        doc
140    }
141}
142
143impl TryFrom<&Doc> for Permission {
144    type Error = crate::Error;
145
146    fn try_from(doc: &Doc) -> crate::Result<Self> {
147        let ptype = doc
148            .get_as::<&str>("type")
149            .ok_or_else(|| CRDTError::ElementNotFound {
150                key: "type".to_string(),
151            })?;
152        match ptype {
153            "Admin" => {
154                let p =
155                    doc.get_as::<i64>("priority")
156                        .ok_or_else(|| CRDTError::ElementNotFound {
157                            key: "priority".to_string(),
158                        })?;
159                Ok(Permission::Admin(p as u32))
160            }
161            "Write" => {
162                let p =
163                    doc.get_as::<i64>("priority")
164                        .ok_or_else(|| CRDTError::ElementNotFound {
165                            key: "priority".to_string(),
166                        })?;
167                Ok(Permission::Write(p as u32))
168            }
169            "Read" => Ok(Permission::Read),
170            other => Err(CRDTError::DeserializationFailed {
171                reason: format!("unknown Permission type: {other}"),
172            }
173            .into()),
174        }
175    }
176}
177
178impl From<PermissionBounds> for Value {
179    fn from(bounds: PermissionBounds) -> Value {
180        Value::Doc(Doc::from(bounds))
181    }
182}
183
184impl From<PermissionBounds> for Doc {
185    fn from(bounds: PermissionBounds) -> Doc {
186        let mut doc = Doc::new();
187        doc.set("max", bounds.max);
188        if let Some(min) = bounds.min {
189            doc.set("min", min);
190        }
191        doc
192    }
193}
194
195impl TryFrom<&Doc> for PermissionBounds {
196    type Error = crate::Error;
197
198    fn try_from(doc: &Doc) -> crate::Result<Self> {
199        let max_doc = match doc.get("max") {
200            Some(Value::Doc(d)) => d,
201            _ => {
202                return Err(CRDTError::ElementNotFound {
203                    key: "max".to_string(),
204                }
205                .into());
206            }
207        };
208        let max = Permission::try_from(max_doc)?;
209
210        let min = match doc.get("min") {
211            Some(Value::Doc(d)) => Some(Permission::try_from(d)?),
212            _ => None,
213        };
214
215        Ok(PermissionBounds { max, min })
216    }
217}
218
219/// Operation types for permission checking
220#[derive(Debug, Clone, PartialEq, Eq)]
221pub enum Operation {
222    /// Writing data to non-settings subtrees
223    WriteData,
224    /// Writing to _settings subtree (includes authentication modifications)
225    WriteSettings,
226}