oxify_authz/
delegation.rs

1//! Permission Delegation System
2//!
3//! Allows users to delegate their permissions to other users with optional time limits
4//! and specific scopes.
5//!
6//! ## Features
7//!
8//! - **Time-Limited Delegations**: Set expiration times for delegated permissions
9//! - **Scoped Delegations**: Delegate specific permissions, not all
10//! - **Revocable**: Delegations can be revoked at any time
11//! - **Audit Trail**: All delegations are tracked for compliance
12//!
13//! ## Example
14//!
15//! ```rust
16//! use oxify_authz::delegation::{Delegation, DelegationManager};
17//! use oxify_authz::Subject;
18//! use chrono::{Duration, Utc};
19//!
20//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
21//! let manager = DelegationManager::new();
22//!
23//! // Alice delegates "editor" permission on "document:123" to Bob for 24 hours
24//! let delegation = Delegation::new(
25//!     Subject::User("alice".to_string()),
26//!     Subject::User("bob".to_string()),
27//!     "document",
28//!     "123",
29//!     "editor",
30//! )
31//! .with_expiration(Utc::now() + Duration::hours(24));
32//!
33//! manager.create_delegation(delegation).await?;
34//!
35//! // Check if Bob can act as editor through delegation
36//! let has_delegation = manager.check_delegation(
37//!     &Subject::User("bob".to_string()),
38//!     "document",
39//!     "123",
40//!     "editor",
41//! ).await?;
42//! assert!(has_delegation);
43//! # Ok(())
44//! # }
45//! ```
46
47use crate::{AuthzError, RelationTuple, Result, Subject};
48use chrono::{DateTime, Utc};
49use serde::{Deserialize, Serialize};
50use std::collections::HashMap;
51use std::sync::Arc;
52use tokio::sync::RwLock;
53use uuid::Uuid;
54
55/// Delegation of permissions from one subject to another
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
57pub struct Delegation {
58    /// Unique delegation ID
59    pub id: String,
60
61    /// Subject who is delegating (delegator)
62    pub delegator: Subject,
63
64    /// Subject receiving the delegation (delegate)
65    pub delegate: Subject,
66
67    /// Namespace of the resource
68    pub namespace: String,
69
70    /// Object ID of the resource
71    pub object_id: String,
72
73    /// Relation being delegated
74    pub relation: String,
75
76    /// When the delegation was created
77    pub created_at: DateTime<Utc>,
78
79    /// When the delegation expires (None = never expires)
80    pub expires_at: Option<DateTime<Utc>>,
81
82    /// Whether the delegation has been revoked
83    pub revoked: bool,
84
85    /// When the delegation was revoked (if applicable)
86    pub revoked_at: Option<DateTime<Utc>>,
87}
88
89impl Delegation {
90    /// Create a new delegation
91    pub fn new(
92        delegator: Subject,
93        delegate: Subject,
94        namespace: impl Into<String>,
95        object_id: impl Into<String>,
96        relation: impl Into<String>,
97    ) -> Self {
98        Self {
99            id: Uuid::new_v4().to_string(),
100            delegator,
101            delegate,
102            namespace: namespace.into(),
103            object_id: object_id.into(),
104            relation: relation.into(),
105            created_at: Utc::now(),
106            expires_at: None,
107            revoked: false,
108            revoked_at: None,
109        }
110    }
111
112    /// Set expiration time for the delegation
113    pub fn with_expiration(mut self, expires_at: DateTime<Utc>) -> Self {
114        self.expires_at = Some(expires_at);
115        self
116    }
117
118    /// Check if this delegation is currently active
119    pub fn is_active(&self) -> bool {
120        if self.revoked {
121            return false;
122        }
123
124        if let Some(expires_at) = self.expires_at {
125            Utc::now() < expires_at
126        } else {
127            true
128        }
129    }
130
131    /// Revoke this delegation
132    pub fn revoke(&mut self) {
133        self.revoked = true;
134        self.revoked_at = Some(Utc::now());
135    }
136
137    /// Convert to a relation tuple (for compatibility with existing authorization)
138    pub fn to_relation_tuple(&self) -> RelationTuple {
139        RelationTuple::new(
140            &self.namespace,
141            &self.relation,
142            &self.object_id,
143            self.delegate.clone(),
144        )
145    }
146}
147
148/// Manager for permission delegations
149pub struct DelegationManager {
150    /// Active delegations by ID
151    delegations: Arc<RwLock<HashMap<String, Delegation>>>,
152
153    /// Index: delegate -> list of delegation IDs
154    by_delegate: Arc<RwLock<HashMap<String, Vec<String>>>>,
155
156    /// Index: delegator -> list of delegation IDs
157    by_delegator: Arc<RwLock<HashMap<String, Vec<String>>>>,
158}
159
160impl DelegationManager {
161    /// Create a new delegation manager
162    pub fn new() -> Self {
163        Self {
164            delegations: Arc::new(RwLock::new(HashMap::new())),
165            by_delegate: Arc::new(RwLock::new(HashMap::new())),
166            by_delegator: Arc::new(RwLock::new(HashMap::new())),
167        }
168    }
169
170    /// Create a new delegation
171    pub async fn create_delegation(&self, delegation: Delegation) -> Result<String> {
172        let id = delegation.id.clone();
173        let delegate_key = delegation.delegate.to_string();
174        let delegator_key = delegation.delegator.to_string();
175
176        // Store the delegation
177        let mut delegations = self.delegations.write().await;
178        delegations.insert(id.clone(), delegation.clone());
179
180        // Index by delegate
181        let mut by_delegate = self.by_delegate.write().await;
182        by_delegate
183            .entry(delegate_key)
184            .or_insert_with(Vec::new)
185            .push(id.clone());
186
187        // Index by delegator
188        let mut by_delegator = self.by_delegator.write().await;
189        by_delegator
190            .entry(delegator_key)
191            .or_insert_with(Vec::new)
192            .push(id.clone());
193
194        Ok(id)
195    }
196
197    /// Revoke a delegation by ID
198    pub async fn revoke_delegation(&self, delegation_id: &str) -> Result<()> {
199        let mut delegations = self.delegations.write().await;
200        if let Some(delegation) = delegations.get_mut(delegation_id) {
201            delegation.revoke();
202            Ok(())
203        } else {
204            Err(AuthzError::InvalidTuple(format!(
205                "Delegation not found: {}",
206                delegation_id
207            )))
208        }
209    }
210
211    /// Get a delegation by ID
212    pub async fn get_delegation(&self, delegation_id: &str) -> Result<Option<Delegation>> {
213        let delegations = self.delegations.read().await;
214        Ok(delegations.get(delegation_id).cloned())
215    }
216
217    /// Check if a subject has a delegated permission
218    pub async fn check_delegation(
219        &self,
220        delegate: &Subject,
221        namespace: &str,
222        object_id: &str,
223        relation: &str,
224    ) -> Result<bool> {
225        let delegate_key = delegate.to_string();
226
227        // Get all delegations for this delegate
228        let by_delegate = self.by_delegate.read().await;
229        if let Some(delegation_ids) = by_delegate.get(&delegate_key) {
230            let delegations = self.delegations.read().await;
231
232            for id in delegation_ids {
233                if let Some(delegation) = delegations.get(id) {
234                    // Check if this delegation matches and is active
235                    if delegation.namespace == namespace
236                        && delegation.object_id == object_id
237                        && delegation.relation == relation
238                        && delegation.is_active()
239                    {
240                        return Ok(true);
241                    }
242                }
243            }
244        }
245
246        Ok(false)
247    }
248
249    /// List all active delegations for a delegate
250    pub async fn list_delegations_for_delegate(
251        &self,
252        delegate: &Subject,
253    ) -> Result<Vec<Delegation>> {
254        let delegate_key = delegate.to_string();
255        let by_delegate = self.by_delegate.read().await;
256        let delegations_guard = self.delegations.read().await;
257
258        let mut result = Vec::new();
259
260        if let Some(delegation_ids) = by_delegate.get(&delegate_key) {
261            for id in delegation_ids {
262                if let Some(delegation) = delegations_guard.get(id) {
263                    if delegation.is_active() {
264                        result.push(delegation.clone());
265                    }
266                }
267            }
268        }
269
270        Ok(result)
271    }
272
273    /// List all delegations created by a delegator
274    pub async fn list_delegations_by_delegator(
275        &self,
276        delegator: &Subject,
277    ) -> Result<Vec<Delegation>> {
278        let delegator_key = delegator.to_string();
279        let by_delegator = self.by_delegator.read().await;
280        let delegations_guard = self.delegations.read().await;
281
282        let mut result = Vec::new();
283
284        if let Some(delegation_ids) = by_delegator.get(&delegator_key) {
285            for id in delegation_ids {
286                if let Some(delegation) = delegations_guard.get(id) {
287                    result.push(delegation.clone());
288                }
289            }
290        }
291
292        Ok(result)
293    }
294
295    /// Clean up expired and revoked delegations
296    pub async fn cleanup_expired(&self) -> Result<usize> {
297        let mut delegations = self.delegations.write().await;
298        let expired_ids: Vec<String> = delegations
299            .iter()
300            .filter(|(_, d)| !d.is_active())
301            .map(|(id, _)| id.clone())
302            .collect();
303
304        for id in &expired_ids {
305            delegations.remove(id);
306        }
307
308        // Clean up indices
309        let mut by_delegate = self.by_delegate.write().await;
310        let mut by_delegator = self.by_delegator.write().await;
311
312        for entries in by_delegate.values_mut() {
313            entries.retain(|id| !expired_ids.contains(id));
314        }
315
316        for entries in by_delegator.values_mut() {
317            entries.retain(|id| !expired_ids.contains(id));
318        }
319
320        Ok(expired_ids.len())
321    }
322}
323
324impl Default for DelegationManager {
325    fn default() -> Self {
326        Self::new()
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::*;
333    use chrono::Duration;
334
335    #[tokio::test]
336    async fn test_delegation_creation() {
337        let manager = DelegationManager::new();
338
339        let delegation = Delegation::new(
340            Subject::User("alice".to_string()),
341            Subject::User("bob".to_string()),
342            "document",
343            "123",
344            "editor",
345        );
346
347        let id = manager.create_delegation(delegation).await.unwrap();
348        assert!(!id.is_empty());
349
350        let retrieved = manager.get_delegation(&id).await.unwrap();
351        assert!(retrieved.is_some());
352    }
353
354    #[tokio::test]
355    async fn test_delegation_check() {
356        let manager = DelegationManager::new();
357
358        let delegation = Delegation::new(
359            Subject::User("alice".to_string()),
360            Subject::User("bob".to_string()),
361            "document",
362            "123",
363            "editor",
364        );
365
366        manager.create_delegation(delegation).await.unwrap();
367
368        let has_delegation = manager
369            .check_delegation(
370                &Subject::User("bob".to_string()),
371                "document",
372                "123",
373                "editor",
374            )
375            .await
376            .unwrap();
377
378        assert!(has_delegation);
379
380        let no_delegation = manager
381            .check_delegation(
382                &Subject::User("charlie".to_string()),
383                "document",
384                "123",
385                "editor",
386            )
387            .await
388            .unwrap();
389
390        assert!(!no_delegation);
391    }
392
393    #[tokio::test]
394    async fn test_delegation_expiration() {
395        let manager = DelegationManager::new();
396
397        // Create delegation that expired 1 hour ago
398        let delegation = Delegation::new(
399            Subject::User("alice".to_string()),
400            Subject::User("bob".to_string()),
401            "document",
402            "123",
403            "editor",
404        )
405        .with_expiration(Utc::now() - Duration::hours(1));
406
407        manager.create_delegation(delegation).await.unwrap();
408
409        let has_delegation = manager
410            .check_delegation(
411                &Subject::User("bob".to_string()),
412                "document",
413                "123",
414                "editor",
415            )
416            .await
417            .unwrap();
418
419        assert!(!has_delegation);
420    }
421
422    #[tokio::test]
423    async fn test_delegation_revocation() {
424        let manager = DelegationManager::new();
425
426        let delegation = Delegation::new(
427            Subject::User("alice".to_string()),
428            Subject::User("bob".to_string()),
429            "document",
430            "123",
431            "editor",
432        );
433
434        let id = manager.create_delegation(delegation).await.unwrap();
435
436        // Should have delegation
437        let has_delegation = manager
438            .check_delegation(
439                &Subject::User("bob".to_string()),
440                "document",
441                "123",
442                "editor",
443            )
444            .await
445            .unwrap();
446        assert!(has_delegation);
447
448        // Revoke delegation
449        manager.revoke_delegation(&id).await.unwrap();
450
451        // Should not have delegation anymore
452        let has_delegation = manager
453            .check_delegation(
454                &Subject::User("bob".to_string()),
455                "document",
456                "123",
457                "editor",
458            )
459            .await
460            .unwrap();
461        assert!(!has_delegation);
462    }
463
464    #[tokio::test]
465    async fn test_list_delegations() {
466        let manager = DelegationManager::new();
467
468        let delegation1 = Delegation::new(
469            Subject::User("alice".to_string()),
470            Subject::User("bob".to_string()),
471            "document",
472            "123",
473            "editor",
474        );
475
476        let delegation2 = Delegation::new(
477            Subject::User("alice".to_string()),
478            Subject::User("bob".to_string()),
479            "document",
480            "456",
481            "viewer",
482        );
483
484        manager.create_delegation(delegation1).await.unwrap();
485        manager.create_delegation(delegation2).await.unwrap();
486
487        let delegations = manager
488            .list_delegations_for_delegate(&Subject::User("bob".to_string()))
489            .await
490            .unwrap();
491
492        assert_eq!(delegations.len(), 2);
493    }
494
495    #[test]
496    fn test_delegation_active_status() {
497        let mut delegation = Delegation::new(
498            Subject::User("alice".to_string()),
499            Subject::User("bob".to_string()),
500            "document",
501            "123",
502            "editor",
503        );
504
505        assert!(delegation.is_active());
506
507        delegation.revoke();
508        assert!(!delegation.is_active());
509    }
510}