1use 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
57pub struct Delegation {
58 pub id: String,
60
61 pub delegator: Subject,
63
64 pub delegate: Subject,
66
67 pub namespace: String,
69
70 pub object_id: String,
72
73 pub relation: String,
75
76 pub created_at: DateTime<Utc>,
78
79 pub expires_at: Option<DateTime<Utc>>,
81
82 pub revoked: bool,
84
85 pub revoked_at: Option<DateTime<Utc>>,
87}
88
89impl Delegation {
90 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 pub fn with_expiration(mut self, expires_at: DateTime<Utc>) -> Self {
114 self.expires_at = Some(expires_at);
115 self
116 }
117
118 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 pub fn revoke(&mut self) {
133 self.revoked = true;
134 self.revoked_at = Some(Utc::now());
135 }
136
137 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
148pub struct DelegationManager {
150 delegations: Arc<RwLock<HashMap<String, Delegation>>>,
152
153 by_delegate: Arc<RwLock<HashMap<String, Vec<String>>>>,
155
156 by_delegator: Arc<RwLock<HashMap<String, Vec<String>>>>,
158}
159
160impl DelegationManager {
161 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 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 let mut delegations = self.delegations.write().await;
178 delegations.insert(id.clone(), delegation.clone());
179
180 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 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 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 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 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 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 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 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 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 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 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 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 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 manager.revoke_delegation(&id).await.unwrap();
450
451 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}