1use crate::errors::{AuthError, Result};
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, HashSet};
10use std::time::SystemTime;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct Permission {
15 pub resource: String,
17 pub action: String,
19 pub conditions: Option<AccessCondition>,
21 pub attributes: Vec<(String, String)>,
23}
24
25impl Permission {
26 pub fn new(resource: impl Into<String>, action: impl Into<String>) -> Self {
28 Self {
29 resource: resource.into(),
30 action: action.into(),
31 conditions: None,
32 attributes: Vec::new(),
33 }
34 }
35
36 pub fn with_condition(mut self, condition: AccessCondition) -> Self {
38 self.conditions = Some(condition);
39 self
40 }
41
42 pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
44 self.attributes.push((key.into(), value.into()));
45 self
46 }
47
48 pub fn matches(&self, requested: &Permission, context: &AccessContext) -> bool {
50 if self.resource != requested.resource || self.action != requested.action {
52 return false;
53 }
54
55 if let Some(condition) = &self.conditions {
57 return condition.evaluate(context);
58 }
59
60 true
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub enum AccessCondition {
67 TimeRange {
69 start_hour: u8,
70 end_hour: u8,
71 timezone: String,
72 },
73 IpWhitelist(Vec<String>),
75 UserAttribute {
77 attribute: String,
78 value: String,
79 operator: ComparisonOperator,
80 },
81 ResourceAttribute {
83 attribute: String,
84 value: String,
85 operator: ComparisonOperator,
86 },
87 And(Vec<AccessCondition>),
89 Or(Vec<AccessCondition>),
90 Not(Box<AccessCondition>),
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
94pub enum ComparisonOperator {
95 Equals,
96 NotEquals,
97 GreaterThan,
98 LessThan,
99 Contains,
100 StartsWith,
101 EndsWith,
102}
103
104impl AccessCondition {
105 pub fn evaluate(&self, context: &AccessContext) -> bool {
107 match self {
108 AccessCondition::TimeRange {
109 start_hour,
110 end_hour,
111 timezone: _,
112 } => {
113 let now = SystemTime::now()
115 .duration_since(SystemTime::UNIX_EPOCH)
116 .unwrap()
117 .as_secs();
118 let hour = ((now / 3600) % 24) as u8;
119 hour >= *start_hour && hour <= *end_hour
120 }
121 AccessCondition::IpWhitelist(ips) => context
122 .ip_address
123 .as_ref()
124 .map(|ip| ips.contains(ip))
125 .unwrap_or(false),
126 AccessCondition::UserAttribute {
127 attribute,
128 value,
129 operator,
130 } => context
131 .user_attributes
132 .get(attribute)
133 .map(|attr_value| compare_values(attr_value, value, operator))
134 .unwrap_or(false),
135 AccessCondition::ResourceAttribute {
136 attribute,
137 value,
138 operator,
139 } => context
140 .resource_attributes
141 .get(attribute)
142 .map(|attr_value| compare_values(attr_value, value, operator))
143 .unwrap_or(false),
144 AccessCondition::And(conditions) => conditions.iter().all(|c| c.evaluate(context)),
145 AccessCondition::Or(conditions) => conditions.iter().any(|c| c.evaluate(context)),
146 AccessCondition::Not(condition) => !condition.evaluate(context),
147 }
148 }
149}
150
151fn compare_values(left: &str, right: &str, operator: &ComparisonOperator) -> bool {
152 match operator {
153 ComparisonOperator::Equals => left == right,
154 ComparisonOperator::NotEquals => left != right,
155 ComparisonOperator::GreaterThan => left > right,
156 ComparisonOperator::LessThan => left < right,
157 ComparisonOperator::Contains => left.contains(right),
158 ComparisonOperator::StartsWith => left.starts_with(right),
159 ComparisonOperator::EndsWith => left.ends_with(right),
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct Role {
166 pub id: String,
168 pub name: String,
170 pub description: String,
172 pub permissions: HashSet<Permission>,
174 pub parent_roles: HashSet<String>,
176 pub metadata: HashMap<String, String>,
178 pub created_at: SystemTime,
180 pub updated_at: SystemTime,
182}
183
184impl Role {
185 pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
187 let now = SystemTime::now();
188 Self {
189 id: id.into(),
190 name: name.into(),
191 description: String::new(),
192 permissions: HashSet::new(),
193 parent_roles: HashSet::new(),
194 metadata: HashMap::new(),
195 created_at: now,
196 updated_at: now,
197 }
198 }
199
200 pub fn add_permission(&mut self, permission: Permission) {
202 self.permissions.insert(permission);
203 self.updated_at = SystemTime::now();
204 }
205
206 pub fn remove_permission(&mut self, permission: &Permission) {
208 self.permissions.remove(permission);
209 self.updated_at = SystemTime::now();
210 }
211
212 pub fn add_parent_role(&mut self, role_id: impl Into<String>) {
214 self.parent_roles.insert(role_id.into());
215 self.updated_at = SystemTime::now();
216 }
217
218 pub fn has_permission(&self, permission: &Permission, context: &AccessContext) -> bool {
220 self.permissions
221 .iter()
222 .any(|p| p.matches(permission, context))
223 }
224}
225
226#[derive(Debug, Clone)]
228pub struct AccessContext {
229 pub user_id: String,
231 pub user_attributes: HashMap<String, String>,
233 pub resource_id: Option<String>,
235 pub resource_attributes: HashMap<String, String>,
237 pub ip_address: Option<String>,
239 pub timestamp: SystemTime,
241 pub metadata: HashMap<String, String>,
243}
244
245impl AccessContext {
246 pub fn new(user_id: impl Into<String>) -> Self {
248 Self {
249 user_id: user_id.into(),
250 user_attributes: HashMap::new(),
251 resource_id: None,
252 resource_attributes: HashMap::new(),
253 ip_address: None,
254 timestamp: SystemTime::now(),
255 metadata: HashMap::new(),
256 }
257 }
258
259 pub fn with_user_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
261 self.user_attributes.insert(key.into(), value.into());
262 self
263 }
264
265 pub fn with_resource(mut self, resource_id: impl Into<String>) -> Self {
267 self.resource_id = Some(resource_id.into());
268 self
269 }
270
271 pub fn with_resource_attribute(
273 mut self,
274 key: impl Into<String>,
275 value: impl Into<String>,
276 ) -> Self {
277 self.resource_attributes.insert(key.into(), value.into());
278 self
279 }
280
281 pub fn with_ip_address(mut self, ip: impl Into<String>) -> Self {
283 self.ip_address = Some(ip.into());
284 self
285 }
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct UserRole {
291 pub user_id: String,
293 pub role_id: String,
295 pub assigned_at: SystemTime,
297 pub expires_at: Option<SystemTime>,
299 pub assigned_by: String,
301}
302
303#[derive(Debug, Clone)]
305pub struct AuthorizationResult {
306 pub granted: bool,
308 pub reason: String,
310 pub permissions: Vec<Permission>,
312 pub evaluation_time: std::time::Duration,
314}
315
316#[async_trait]
318pub trait AuthorizationStorage: Send + Sync {
319 async fn store_role(&self, role: &Role) -> Result<()>;
321
322 async fn get_role(&self, role_id: &str) -> Result<Option<Role>>;
324
325 async fn update_role(&self, role: &Role) -> Result<()>;
327
328 async fn delete_role(&self, role_id: &str) -> Result<()>;
330
331 async fn list_roles(&self) -> Result<Vec<Role>>;
333
334 async fn assign_role(&self, user_role: &UserRole) -> Result<()>;
336
337 async fn remove_role(&self, user_id: &str, role_id: &str) -> Result<()>;
339
340 async fn get_user_roles(&self, user_id: &str) -> Result<Vec<UserRole>>;
342
343 async fn get_role_users(&self, role_id: &str) -> Result<Vec<UserRole>>;
345}
346
347pub struct AuthorizationEngine<S: AuthorizationStorage> {
349 storage: S,
350 role_cache: std::sync::RwLock<HashMap<String, Role>>,
351}
352
353impl<S: AuthorizationStorage> AuthorizationEngine<S> {
354 pub fn new(storage: S) -> Self {
356 Self {
357 storage,
358 role_cache: std::sync::RwLock::new(HashMap::new()),
359 }
360 }
361
362 pub async fn check_permission(
364 &self,
365 user_id: &str,
366 permission: &Permission,
367 context: &AccessContext,
368 ) -> Result<AuthorizationResult> {
369 let start_time = std::time::Instant::now();
370
371 let user_roles = self.storage.get_user_roles(user_id).await?;
373
374 let mut applicable_permissions = Vec::new();
375 let mut granted = false;
376 let mut reason = "No matching permissions found".to_string();
377
378 for user_role in user_roles {
379 if let Some(expires_at) = user_role.expires_at
381 && SystemTime::now() > expires_at
382 {
383 continue;
384 }
385
386 let role_permissions = self.get_role_permissions(&user_role.role_id).await?;
388
389 for role_permission in role_permissions {
390 if role_permission.matches(permission, context) {
391 applicable_permissions.push(role_permission);
392 granted = true;
393 reason = format!("Permission granted via role: {}", user_role.role_id);
394 break;
395 }
396 }
397
398 if granted {
399 break;
400 }
401 }
402
403 let evaluation_time = start_time.elapsed();
404
405 Ok(AuthorizationResult {
406 granted,
407 reason,
408 permissions: applicable_permissions,
409 evaluation_time,
410 })
411 }
412
413 async fn get_role_permissions(&self, role_id: &str) -> Result<Vec<Permission>> {
415 let mut all_permissions = Vec::new();
416 let mut visited_roles = HashSet::new();
417
418 self.collect_role_permissions(role_id, &mut all_permissions, &mut visited_roles)
419 .await?;
420
421 Ok(all_permissions)
422 }
423
424 fn collect_role_permissions<'a>(
426 &'a self,
427 role_id: &'a str,
428 permissions: &'a mut Vec<Permission>,
429 visited: &'a mut HashSet<String>,
430 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>> {
431 Box::pin(async move {
432 if visited.contains(role_id) {
434 return Ok(());
435 }
436 visited.insert(role_id.to_string());
437
438 let role = match self.get_cached_role(role_id).await? {
440 Some(role) => role,
441 None => return Ok(()),
442 };
443
444 permissions.extend(role.permissions.iter().cloned());
446
447 for parent_role_id in &role.parent_roles {
449 self.collect_role_permissions(parent_role_id, permissions, visited)
450 .await?;
451 }
452
453 Ok(())
454 })
455 }
456
457 async fn get_cached_role(&self, role_id: &str) -> Result<Option<Role>> {
459 {
461 let cache = self
462 .role_cache
463 .read()
464 .map_err(|_| AuthError::internal("Failed to acquire role cache lock"))?;
465 if let Some(role) = cache.get(role_id) {
466 return Ok(Some(role.clone()));
467 }
468 }
469
470 if let Some(role) = self.storage.get_role(role_id).await? {
472 {
474 let mut cache = self
475 .role_cache
476 .write()
477 .map_err(|_| AuthError::internal("Failed to acquire role cache lock"))?;
478 cache.insert(role_id.to_string(), role.clone());
479 }
480 Ok(Some(role))
481 } else {
482 Ok(None)
483 }
484 }
485
486 pub fn invalidate_role_cache(&self, role_id: &str) -> Result<()> {
488 let mut cache = self
489 .role_cache
490 .write()
491 .map_err(|_| AuthError::internal("Failed to acquire role cache lock"))?;
492 cache.remove(role_id);
493 Ok(())
494 }
495
496 pub async fn create_role(&self, role: Role) -> Result<()> {
498 self.storage.store_role(&role).await?;
499 self.invalidate_role_cache(&role.id)?;
500 Ok(())
501 }
502
503 pub async fn assign_role(&self, user_id: &str, role_id: &str, assigned_by: &str) -> Result<()> {
505 if self.storage.get_role(role_id).await?.is_none() {
507 return Err(AuthError::validation(format!(
508 "Role '{}' does not exist",
509 role_id
510 )));
511 }
512
513 let user_role = UserRole {
514 user_id: user_id.to_string(),
515 role_id: role_id.to_string(),
516 assigned_at: SystemTime::now(),
517 expires_at: None,
518 assigned_by: assigned_by.to_string(),
519 };
520
521 self.storage.assign_role(&user_role).await
522 }
523
524 pub async fn has_any_role(&self, user_id: &str, role_ids: &[String]) -> Result<bool> {
526 let user_roles = self.storage.get_user_roles(user_id).await?;
527 Ok(user_roles.iter().any(|ur| role_ids.contains(&ur.role_id)))
528 }
529}
530
531pub struct CommonPermissions;
533
534impl CommonPermissions {
535 pub fn user_read() -> Permission {
537 Permission::new("users", "read")
538 }
539
540 pub fn user_write() -> Permission {
541 Permission::new("users", "write")
542 }
543
544 pub fn user_delete() -> Permission {
545 Permission::new("users", "delete")
546 }
547
548 pub fn user_admin() -> Permission {
549 Permission::new("users", "admin")
550 }
551
552 pub fn document_read() -> Permission {
554 Permission::new("documents", "read")
555 }
556
557 pub fn document_write() -> Permission {
558 Permission::new("documents", "write")
559 }
560
561 pub fn document_delete() -> Permission {
562 Permission::new("documents", "delete")
563 }
564
565 pub fn api_read() -> Permission {
567 Permission::new("api", "read")
568 }
569
570 pub fn api_write() -> Permission {
571 Permission::new("api", "write")
572 }
573
574 pub fn system_admin() -> Permission {
576 Permission::new("system", "admin")
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 #[test]
585 fn test_permission_matching() {
586 let context = AccessContext::new("user123");
587
588 let permission = Permission::new("users", "read");
589 let requested = Permission::new("users", "read");
590
591 assert!(permission.matches(&requested, &context));
592
593 let different_action = Permission::new("users", "write");
594 assert!(!permission.matches(&different_action, &context));
595 }
596
597 #[test]
598 fn test_access_condition_evaluation() {
599 let mut context = AccessContext::new("user123");
600 context
601 .user_attributes
602 .insert("department".to_string(), "engineering".to_string());
603
604 let condition = AccessCondition::UserAttribute {
605 attribute: "department".to_string(),
606 value: "engineering".to_string(),
607 operator: ComparisonOperator::Equals,
608 };
609
610 assert!(condition.evaluate(&context));
611
612 let wrong_condition = AccessCondition::UserAttribute {
613 attribute: "department".to_string(),
614 value: "sales".to_string(),
615 operator: ComparisonOperator::Equals,
616 };
617
618 assert!(!wrong_condition.evaluate(&context));
619 }
620
621 #[test]
622 fn test_role_hierarchy() {
623 let mut admin_role = Role::new("admin", "Administrator");
624 admin_role.add_permission(CommonPermissions::system_admin());
625
626 let mut manager_role = Role::new("manager", "Manager");
627 manager_role.add_permission(CommonPermissions::user_write());
628 manager_role.add_parent_role("admin");
629
630 let context = AccessContext::new("user123");
631
632 assert!(manager_role.has_permission(&CommonPermissions::user_write(), &context));
634
635 assert!(!manager_role.has_permission(&CommonPermissions::system_admin(), &context));
637 }
638}
639
640