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

eidetica/auth/
permission.rs

1//! Permission clamping logic for delegated trees
2//!
3//! This module provides functions for enforcing permission boundaries when
4//! keys are delegated through other trees. The clamping ensures that delegated
5//! keys cannot exceed their allowed permission levels.
6
7use crate::auth::types::{Permission, PermissionBounds};
8
9/// Clamp a delegated permission to fit within the specified bounds
10///
11/// This function enforces permission boundaries by:
12/// 1. Applying the maximum bound (required) - reduces permission if it exceeds max
13/// 2. Applying the minimum bound (optional) - increases permission if it's below min
14///
15/// # Arguments
16/// * `delegated_permission` - The permission level from the delegated tree
17/// * `bounds` - The permission bounds configured for this delegation
18///
19/// # Returns
20/// The effective permission after applying bounds
21///
22/// # Examples
23/// ```
24/// use eidetica::auth::permission::clamp_permission;
25/// use eidetica::auth::types::{Permission, PermissionBounds};
26///
27/// let bounds = PermissionBounds {
28///     max: Permission::Write(10),
29///     min: Some(Permission::Read),
30/// };
31///
32/// // Admin permission gets clamped down to Write(10)
33/// let clamped = clamp_permission(Permission::Admin(5), &bounds);
34/// assert_eq!(clamped, Permission::Write(10));
35///
36/// // Read permission stays as Read (meets minimum)
37/// let clamped = clamp_permission(Permission::Read, &bounds);
38/// assert_eq!(clamped, Permission::Read);
39/// ```
40pub fn clamp_permission(delegated_permission: Permission, bounds: &PermissionBounds) -> Permission {
41    // Validate bounds first
42    if !validate_permission_bounds(bounds) {
43        tracing::warn!(
44            "Invalid permission bounds detected (min > max). Applying only max bound as fallback."
45        );
46        // For invalid bounds, apply only the max bound (safer fallback)
47        if delegated_permission > bounds.max {
48            return bounds.max;
49        }
50        return delegated_permission;
51    }
52
53    // Apply maximum bound (always enforced) - if permission exceeds max, clamp to max
54    if delegated_permission > bounds.max {
55        return bounds.max;
56    }
57
58    // Apply minimum bound if specified - only if permission is naturally below minimum
59    if let Some(min) = &bounds.min
60        && delegated_permission < *min
61    {
62        return *min;
63    }
64
65    delegated_permission
66}
67
68/// Validate that permission bounds are correctly configured
69///
70/// Ensures that:
71/// - If minimum is specified, it's not greater than maximum
72///
73/// # Arguments
74/// * `bounds` - The permission bounds to validate
75///
76/// # Returns
77/// `true` if bounds are valid, `false` otherwise
78pub fn validate_permission_bounds(bounds: &PermissionBounds) -> bool {
79    // If minimum is specified, it must not exceed maximum
80    if let Some(min) = &bounds.min {
81        min <= &bounds.max
82    } else {
83        true // No minimum means bounds are valid
84    }
85}
86
87/// Check if a delegating key has sufficient permission to set the given bounds
88///
89/// A key can only delegate permissions that are at or below its own level.
90///
91/// # Arguments
92/// * `delegating_permission` - Permission level of the key setting up delegation
93/// * `bounds` - The permission bounds being configured
94///
95/// # Returns
96/// `true` if the delegating key can set these bounds, `false` otherwise
97pub fn can_delegate_with_bounds(
98    delegating_permission: &Permission,
99    bounds: &PermissionBounds,
100) -> bool {
101    // Maximum bound cannot exceed delegating key's permission
102    if bounds.max > *delegating_permission {
103        return false;
104    }
105    validate_permission_bounds(bounds)
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_clamp_permission_max_bound() {
114        let bounds = PermissionBounds {
115            max: Permission::Write(10),
116            min: None,
117        };
118
119        // Admin should be clamped down to Write(10)
120        let result = clamp_permission(Permission::Admin(5), &bounds);
121        assert_eq!(result, Permission::Write(10));
122
123        // Write(20) should remain Write(20) (lower privilege than max)
124        let result = clamp_permission(Permission::Write(20), &bounds);
125        assert_eq!(result, Permission::Write(20));
126
127        // Write(5) should be clamped to Write(10) (higher privilege than max)
128        let result = clamp_permission(Permission::Write(5), &bounds);
129        assert_eq!(result, Permission::Write(10));
130
131        // Read should remain Read
132        let result = clamp_permission(Permission::Read, &bounds);
133        assert_eq!(result, Permission::Read);
134    }
135
136    #[test]
137    fn test_clamp_permission_min_bound() {
138        let bounds = PermissionBounds {
139            max: Permission::Admin(5),
140            min: Some(Permission::Write(10)),
141        };
142
143        // Admin(5) should remain Admin(5) (above minimum)
144        let result = clamp_permission(Permission::Admin(5), &bounds);
145        assert_eq!(result, Permission::Admin(5));
146
147        // Write(15) should be raised to Write(10) (below minimum privilege)
148        let result = clamp_permission(Permission::Write(15), &bounds);
149        assert_eq!(result, Permission::Write(10));
150
151        // Write(5) should remain Write(5) (above minimum, below max)
152        let result = clamp_permission(Permission::Write(5), &bounds);
153        assert_eq!(result, Permission::Write(5));
154
155        // Read should be raised to Write(10) (minimum)
156        let result = clamp_permission(Permission::Read, &bounds);
157        assert_eq!(result, Permission::Write(10));
158    }
159
160    #[test]
161    fn test_clamp_permission_both_bounds() {
162        let bounds = PermissionBounds {
163            max: Permission::Write(5),        // Higher privilege (lower number)
164            min: Some(Permission::Write(15)), // Lower privilege (higher number)
165        };
166
167        // Admin should be clamped down to Write(5) (max allowed privilege)
168        let result = clamp_permission(Permission::Admin(1), &bounds);
169        assert_eq!(result, Permission::Write(5));
170
171        // Write(20) should be raised to Write(15) (below minimum privilege)
172        let result = clamp_permission(Permission::Write(20), &bounds);
173        assert_eq!(result, Permission::Write(15));
174
175        // Write(12) should remain Write(12) (within bounds)
176        let result = clamp_permission(Permission::Write(12), &bounds);
177        assert_eq!(result, Permission::Write(12));
178
179        // Write(3) should be clamped to Write(5) (exceeds max, gets clamped to max)
180        let result = clamp_permission(Permission::Write(3), &bounds);
181        assert_eq!(result, Permission::Write(5));
182
183        // Read should be raised to Write(15) (minimum)
184        let result = clamp_permission(Permission::Read, &bounds);
185        assert_eq!(result, Permission::Write(15));
186    }
187
188    #[test]
189    fn test_validate_permission_bounds_valid() {
190        // No minimum is always valid
191        let bounds = PermissionBounds {
192            max: Permission::Write(10),
193            min: None,
194        };
195        assert!(validate_permission_bounds(&bounds));
196
197        // Minimum equal to maximum is valid
198        let bounds = PermissionBounds {
199            max: Permission::Write(10),
200            min: Some(Permission::Write(10)),
201        };
202        assert!(validate_permission_bounds(&bounds));
203
204        // Minimum less than maximum is valid
205        let bounds = PermissionBounds {
206            max: Permission::Admin(5),
207            min: Some(Permission::Write(10)),
208        };
209        assert!(validate_permission_bounds(&bounds));
210    }
211
212    #[test]
213    fn test_validate_permission_bounds_invalid() {
214        // Minimum greater than maximum is invalid
215        let bounds = PermissionBounds {
216            max: Permission::Write(10),
217            min: Some(Permission::Admin(5)),
218        };
219        assert!(!validate_permission_bounds(&bounds));
220
221        // Read minimum with Write max is invalid (Read > Write in permission hierarchy)
222        let bounds = PermissionBounds {
223            max: Permission::Read,
224            min: Some(Permission::Write(10)),
225        };
226        assert!(!validate_permission_bounds(&bounds));
227    }
228
229    #[test]
230    fn test_can_delegate_with_bounds_valid() {
231        let delegating_permission = Permission::Admin(5);
232
233        // Can delegate any bounds within admin permission
234        let bounds = PermissionBounds {
235            max: Permission::Write(10),
236            min: Some(Permission::Read),
237        };
238        assert!(can_delegate_with_bounds(&delegating_permission, &bounds));
239
240        // Can delegate up to admin level
241        let bounds = PermissionBounds {
242            max: Permission::Admin(5),
243            min: None,
244        };
245        assert!(can_delegate_with_bounds(&delegating_permission, &bounds));
246    }
247
248    #[test]
249    fn test_can_delegate_with_bounds_invalid() {
250        let delegating_permission = Permission::Write(10);
251
252        // Cannot delegate admin permission
253        let bounds = PermissionBounds {
254            max: Permission::Admin(5),
255            min: None,
256        };
257        assert!(!can_delegate_with_bounds(&delegating_permission, &bounds));
258
259        // Cannot set minimum above own permission
260        let bounds = PermissionBounds {
261            max: Permission::Write(20),
262            min: Some(Permission::Admin(1)),
263        };
264        assert!(!can_delegate_with_bounds(&delegating_permission, &bounds));
265
266        // Cannot set maximum above own permission
267        let bounds = PermissionBounds {
268            max: Permission::Write(5), // Write(5) > Write(10), so this should fail
269            min: Some(Permission::Read),
270        };
271        assert!(!can_delegate_with_bounds(&delegating_permission, &bounds));
272    }
273
274    #[test]
275    fn test_permission_clamping_edge_cases() {
276        // Test with identical permissions
277        let bounds = PermissionBounds {
278            max: Permission::Write(10),
279            min: Some(Permission::Write(10)),
280        };
281
282        let result = clamp_permission(Permission::Write(10), &bounds);
283        assert_eq!(result, Permission::Write(10));
284
285        let result = clamp_permission(Permission::Admin(5), &bounds);
286        assert_eq!(result, Permission::Write(10));
287
288        let result = clamp_permission(Permission::Read, &bounds);
289        assert_eq!(result, Permission::Write(10));
290    }
291
292    #[test]
293    fn test_permission_priority_clamping() {
294        // Test that priority values are preserved when clamping within same permission level
295        let bounds = PermissionBounds {
296            max: Permission::Write(5),
297            min: Some(Permission::Write(15)),
298        };
299
300        // Priority should be preserved when within bounds
301        let result = clamp_permission(Permission::Write(12), &bounds);
302        assert_eq!(result, Permission::Write(12));
303
304        // Priority should be adjusted to min when below minimum
305        let result = clamp_permission(Permission::Write(20), &bounds);
306        assert_eq!(result, Permission::Write(15));
307
308        // Priority should be adjusted to max when exceeding max
309        let result = clamp_permission(Permission::Write(3), &bounds);
310        assert_eq!(result, Permission::Write(5));
311    }
312}