1use crate::errors::{AuthError, Result};
7use async_trait::async_trait;
8use chrono::Timelike;
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet};
11use std::time::SystemTime;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct AbacPermission {
16 pub resource: String,
18 pub action: String,
20 pub conditions: Option<AccessCondition>,
22 pub attributes: HashMap<String, String>,
24}
25
26impl std::hash::Hash for AbacPermission {
27 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
28 self.resource.hash(state);
29 self.action.hash(state);
30 self.conditions.hash(state);
31 }
32}
33
34impl AbacPermission {
35 pub fn new(resource: impl Into<String>, action: impl Into<String>) -> Self {
37 Self {
38 resource: resource.into(),
39 action: action.into(),
40 conditions: None,
41 attributes: HashMap::new(),
42 }
43 }
44
45 pub fn with_condition(mut self, condition: AccessCondition) -> Self {
47 self.conditions = Some(condition);
48 self
49 }
50
51 pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
53 self.attributes.insert(key.into(), value.into());
54 self
55 }
56
57 pub fn matches(&self, requested: &AbacPermission, context: &AccessContext) -> bool {
59 if self.resource != requested.resource || self.action != requested.action {
61 return false;
62 }
63
64 if let Some(condition) = &self.conditions {
66 return condition.evaluate(context);
67 }
68
69 true
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
75pub enum AccessCondition {
76 TimeRange {
78 start_hour: u8,
79 end_hour: u8,
80 timezone: String,
81 },
82 IpWhitelist(crate::types::IpList),
84 UserAttribute {
86 attribute: String,
87 value: String,
88 operator: ComparisonOperator,
89 },
90 ResourceAttribute {
92 attribute: String,
93 value: String,
94 operator: ComparisonOperator,
95 },
96 And(Vec<AccessCondition>),
98 Or(Vec<AccessCondition>),
99 Not(Box<AccessCondition>),
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
103pub enum ComparisonOperator {
104 Equals,
105 NotEquals,
106 GreaterThan,
107 LessThan,
108 Contains,
109 StartsWith,
110 EndsWith,
111}
112
113impl AccessCondition {
114 pub fn evaluate(&self, context: &AccessContext) -> bool {
116 match self {
117 AccessCondition::TimeRange {
118 start_hour,
119 end_hour,
120 timezone: _,
121 } => {
122 let hour = chrono::Utc::now().hour() as u8;
126 hour >= *start_hour && hour <= *end_hour
127 }
128 AccessCondition::IpWhitelist(ips) => context
129 .ip_address
130 .as_ref()
131 .map(|ip| ips.contains(ip))
132 .unwrap_or(false),
133 AccessCondition::UserAttribute {
134 attribute,
135 value,
136 operator,
137 } => context
138 .user_attributes
139 .get(attribute)
140 .map(|attr_value| compare_values(attr_value, value, operator))
141 .unwrap_or(false),
142 AccessCondition::ResourceAttribute {
143 attribute,
144 value,
145 operator,
146 } => context
147 .resource_attributes
148 .get(attribute)
149 .map(|attr_value| compare_values(attr_value, value, operator))
150 .unwrap_or(false),
151 AccessCondition::And(conditions) => conditions.iter().all(|c| c.evaluate(context)),
152 AccessCondition::Or(conditions) => conditions.iter().any(|c| c.evaluate(context)),
153 AccessCondition::Not(condition) => !condition.evaluate(context),
154 }
155 }
156}
157
158fn compare_values(left: &str, right: &str, operator: &ComparisonOperator) -> bool {
159 match operator {
160 ComparisonOperator::Equals => left == right,
161 ComparisonOperator::NotEquals => left != right,
162 ComparisonOperator::GreaterThan => left > right,
163 ComparisonOperator::LessThan => left < right,
164 ComparisonOperator::Contains => left.contains(right),
165 ComparisonOperator::StartsWith => left.starts_with(right),
166 ComparisonOperator::EndsWith => left.ends_with(right),
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct AbacRole {
173 pub id: String,
175 pub name: String,
177 pub description: String,
179 pub permissions: HashSet<AbacPermission>,
181 pub parent_roles: HashSet<String>,
183 pub metadata: HashMap<String, String>,
185 pub created_at: SystemTime,
187 pub updated_at: SystemTime,
189}
190
191impl AbacRole {
192 pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
194 let now = SystemTime::now();
195 Self {
196 id: id.into(),
197 name: name.into(),
198 description: String::new(),
199 permissions: HashSet::new(),
200 parent_roles: HashSet::new(),
201 metadata: HashMap::new(),
202 created_at: now,
203 updated_at: now,
204 }
205 }
206
207 pub fn add_permission(&mut self, permission: AbacPermission) {
209 self.permissions.insert(permission);
210 self.updated_at = SystemTime::now();
211 }
212
213 pub fn remove_permission(&mut self, permission: &AbacPermission) {
215 self.permissions.remove(permission);
216 self.updated_at = SystemTime::now();
217 }
218
219 pub fn add_parent_role(&mut self, role_id: impl Into<String>) {
221 self.parent_roles.insert(role_id.into());
222 self.updated_at = SystemTime::now();
223 }
224
225 pub fn has_permission(&self, permission: &AbacPermission, context: &AccessContext) -> bool {
227 self.permissions
228 .iter()
229 .any(|p| p.matches(permission, context))
230 }
231}
232
233#[derive(Debug, Clone)]
235pub struct AccessContext {
236 pub user_id: String,
238 pub user_attributes: HashMap<String, String>,
240 pub resource_id: Option<String>,
242 pub resource_attributes: HashMap<String, String>,
244 pub ip_address: Option<String>,
246 pub timestamp: SystemTime,
248 pub metadata: HashMap<String, String>,
250}
251
252impl AccessContext {
253 pub fn new(user_id: impl Into<String>) -> Self {
258 Self {
259 user_id: user_id.into(),
260 user_attributes: HashMap::new(),
261 resource_id: None,
262 resource_attributes: HashMap::new(),
263 ip_address: None,
264 timestamp: SystemTime::now(),
265 metadata: HashMap::new(),
266 }
267 }
268
269 pub fn with_user_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
271 self.user_attributes.insert(key.into(), value.into());
272 self
273 }
274
275 pub fn with_resource(mut self, resource_id: impl Into<String>) -> Self {
277 self.resource_id = Some(resource_id.into());
278 self
279 }
280
281 pub fn with_resource_attribute(
283 mut self,
284 key: impl Into<String>,
285 value: impl Into<String>,
286 ) -> Self {
287 self.resource_attributes.insert(key.into(), value.into());
288 self
289 }
290
291 pub fn with_ip_address(mut self, ip: impl Into<String>) -> Self {
293 self.ip_address = Some(ip.into());
294 self
295 }
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct UserRole {
301 pub user_id: String,
303 pub role_id: String,
305 pub assigned_at: SystemTime,
307 pub expires_at: Option<SystemTime>,
309 pub assigned_by: String,
311}
312
313#[derive(Debug, Clone)]
315pub struct AuthorizationResult {
316 pub granted: bool,
318 pub reason: String,
320 pub permissions: Vec<AbacPermission>,
322 pub evaluation_time: std::time::Duration,
324}
325
326#[async_trait]
328pub trait AuthorizationStorage: Send + Sync {
329 async fn store_role(&self, role: &AbacRole) -> Result<()>;
331
332 async fn get_role(&self, role_id: &str) -> Result<Option<AbacRole>>;
334
335 async fn update_role(&self, role: &AbacRole) -> Result<()>;
337
338 async fn delete_role(&self, role_id: &str) -> Result<()>;
340
341 async fn list_roles(&self) -> Result<Vec<AbacRole>>;
343
344 async fn assign_role(&self, user_role: &UserRole) -> Result<()>;
346
347 async fn remove_role(&self, user_id: &str, role_id: &str) -> Result<()>;
349
350 async fn get_user_roles(&self, user_id: &str) -> Result<Vec<UserRole>>;
352
353 async fn get_role_users(&self, role_id: &str) -> Result<Vec<UserRole>>;
355}
356
357pub struct AuthorizationEngine<S: AuthorizationStorage> {
359 storage: S,
360 role_cache: std::sync::RwLock<HashMap<String, AbacRole>>,
361}
362
363impl<S: AuthorizationStorage> AuthorizationEngine<S> {
364 pub fn new(storage: S) -> Self {
366 Self {
367 storage,
368 role_cache: std::sync::RwLock::new(HashMap::new()),
369 }
370 }
371
372 pub async fn check_permission(
374 &self,
375 user_id: &str,
376 permission: &AbacPermission,
377 context: &AccessContext,
378 ) -> Result<AuthorizationResult> {
379 let start_time = std::time::Instant::now();
380
381 let user_roles = self.storage.get_user_roles(user_id).await?;
383
384 let mut applicable_permissions = Vec::new();
385 let mut granted = false;
386 let mut reason = "No matching permissions found".to_string();
387
388 for user_role in user_roles {
389 if let Some(expires_at) = user_role.expires_at
391 && SystemTime::now() > expires_at
392 {
393 continue;
394 }
395
396 let role_permissions = self.get_role_permissions(&user_role.role_id).await?;
398
399 for role_permission in role_permissions {
400 if role_permission.matches(permission, context) {
401 applicable_permissions.push(role_permission);
402 granted = true;
403 reason = format!("AbacPermission granted via AbacRole: {}", user_role.role_id);
404 break;
405 }
406 }
407
408 if granted {
409 break;
410 }
411 }
412
413 let evaluation_time = start_time.elapsed();
414
415 Ok(AuthorizationResult {
416 granted,
417 reason,
418 permissions: applicable_permissions,
419 evaluation_time,
420 })
421 }
422
423 async fn get_role_permissions(&self, role_id: &str) -> Result<Vec<AbacPermission>> {
425 let mut all_permissions = Vec::new();
426 let mut visited_roles = HashSet::new();
427
428 self.collect_role_permissions(role_id, &mut all_permissions, &mut visited_roles)
429 .await?;
430
431 Ok(all_permissions)
432 }
433
434 fn collect_role_permissions<'a>(
436 &'a self,
437 role_id: &'a str,
438 permissions: &'a mut Vec<AbacPermission>,
439 visited: &'a mut HashSet<String>,
440 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>> {
441 Box::pin(async move {
442 if visited.contains(role_id) {
444 return Ok(());
445 }
446 visited.insert(role_id.to_string());
447
448 let role = match self.get_cached_role(role_id).await? {
450 Some(role) => role,
451 None => return Ok(()),
452 };
453
454 permissions.extend(role.permissions.iter().cloned());
456
457 for parent_role_id in &role.parent_roles {
459 self.collect_role_permissions(parent_role_id, permissions, visited)
460 .await?;
461 }
462
463 Ok(())
464 })
465 }
466
467 async fn get_cached_role(&self, role_id: &str) -> Result<Option<AbacRole>> {
469 {
471 let cache = self
472 .role_cache
473 .read()
474 .map_err(|_| AuthError::internal("Failed to acquire AbacRole cache lock"))?;
475 if let Some(role) = cache.get(role_id) {
476 return Ok(Some(role.clone()));
477 }
478 }
479
480 if let Some(role) = self.storage.get_role(role_id).await? {
482 {
484 let mut cache = self
485 .role_cache
486 .write()
487 .map_err(|_| AuthError::internal("Failed to acquire AbacRole cache lock"))?;
488 cache.insert(role_id.to_string(), role.clone());
489 }
490 Ok(Some(role))
491 } else {
492 Ok(None)
493 }
494 }
495
496 pub fn invalidate_role_cache(&self, role_id: &str) -> Result<()> {
498 let mut cache = self
499 .role_cache
500 .write()
501 .map_err(|_| AuthError::internal("Failed to acquire AbacRole cache lock"))?;
502 cache.remove(role_id);
503 Ok(())
504 }
505
506 pub async fn create_role(&self, role: AbacRole) -> Result<()> {
508 self.storage.store_role(&role).await?;
509 self.invalidate_role_cache(&role.id)?;
510 Ok(())
511 }
512
513 pub async fn assign_role(&self, user_id: &str, role_id: &str, assigned_by: &str) -> Result<()> {
515 if self.storage.get_role(role_id).await?.is_none() {
517 return Err(AuthError::validation(format!(
518 "AbacRole '{}' does not exist",
519 role_id
520 )));
521 }
522
523 let user_role = UserRole {
524 user_id: user_id.to_string(),
525 role_id: role_id.to_string(),
526 assigned_at: SystemTime::now(),
527 expires_at: None,
528 assigned_by: assigned_by.to_string(),
529 };
530
531 self.storage.assign_role(&user_role).await
532 }
533
534 pub async fn has_any_role(&self, user_id: &str, role_ids: &[String]) -> Result<bool> {
536 let user_roles = self.storage.get_user_roles(user_id).await?;
537 Ok(user_roles.iter().any(|ur| role_ids.contains(&ur.role_id)))
538 }
539}
540
541pub struct CommonPermissions;
543
544impl CommonPermissions {
545 pub fn user_read() -> AbacPermission {
547 AbacPermission::new("users", "read")
548 }
549
550 pub fn user_write() -> AbacPermission {
551 AbacPermission::new("users", "write")
552 }
553
554 pub fn user_delete() -> AbacPermission {
555 AbacPermission::new("users", "delete")
556 }
557
558 pub fn user_admin() -> AbacPermission {
559 AbacPermission::new("users", "admin")
560 }
561
562 pub fn document_read() -> AbacPermission {
564 AbacPermission::new("documents", "read")
565 }
566
567 pub fn document_write() -> AbacPermission {
568 AbacPermission::new("documents", "write")
569 }
570
571 pub fn document_delete() -> AbacPermission {
572 AbacPermission::new("documents", "delete")
573 }
574
575 pub fn api_read() -> AbacPermission {
577 AbacPermission::new("api", "read")
578 }
579
580 pub fn api_write() -> AbacPermission {
581 AbacPermission::new("api", "write")
582 }
583
584 pub fn system_admin() -> AbacPermission {
586 AbacPermission::new("system", "admin")
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 #[test]
595 fn test_permission_matching() {
596 let context = AccessContext::new("user123");
597
598 let permission = AbacPermission::new("users", "read");
599 let requested = AbacPermission::new("users", "read");
600
601 assert!(permission.matches(&requested, &context));
602
603 let different_action = AbacPermission::new("users", "write");
604 assert!(!permission.matches(&different_action, &context));
605 }
606
607 #[test]
608 fn test_access_condition_evaluation() {
609 let mut context = AccessContext::new("user123");
610 context
611 .user_attributes
612 .insert("department".to_string(), "engineering".to_string());
613
614 let condition = AccessCondition::UserAttribute {
615 attribute: "department".to_string(),
616 value: "engineering".to_string(),
617 operator: ComparisonOperator::Equals,
618 };
619
620 assert!(condition.evaluate(&context));
621
622 let wrong_condition = AccessCondition::UserAttribute {
623 attribute: "department".to_string(),
624 value: "sales".to_string(),
625 operator: ComparisonOperator::Equals,
626 };
627
628 assert!(!wrong_condition.evaluate(&context));
629 }
630
631 #[test]
632 fn test_role_hierarchy() {
633 let mut admin_role = AbacRole::new("admin", "Administrator");
634 admin_role.add_permission(CommonPermissions::system_admin());
635
636 let mut manager_role = AbacRole::new("manager", "Manager");
637 manager_role.add_permission(CommonPermissions::user_write());
638 manager_role.add_parent_role("admin");
639
640 let context = AccessContext::new("user123");
641
642 assert!(manager_role.has_permission(&CommonPermissions::user_write(), &context));
644
645 assert!(!manager_role.has_permission(&CommonPermissions::system_admin(), &context));
647 }
648}