use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::sync::Arc; use tokio::sync::RwLock; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum Permission { Read, Write, Delete, Admin, Execute, Share, Export, Import, Manage, Configure, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum ResourceType { Organization, Bot, KnowledgeBase, Form, Site, Document, Conversation, User, Role, Group, Channel, Workflow, Project, Meeting, Report, ApiKey, Webhook, Integration, Billing, Audit, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Resource { pub resource_type: ResourceType, pub resource_id: Uuid, pub organization_id: Uuid, pub parent_resource: Option>, pub owner_id: Option, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PermissionGrant { pub id: Uuid, pub permission: Permission, pub resource_type: ResourceType, pub resource_id: Option, pub conditions: Vec, pub granted_at: DateTime, pub granted_by: Uuid, pub expires_at: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PermissionCondition { pub condition_type: ConditionType, pub operator: ConditionOperator, pub value: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ConditionType { TimeOfDay, DayOfWeek, IpAddress, Location, DeviceType, MfaVerified, SessionAge, ResourceOwner, ResourceTag, Custom, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ConditionOperator { Equals, NotEquals, Contains, StartsWith, EndsWith, GreaterThan, LessThan, In, NotIn, Matches, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrganizationRole { pub id: Uuid, pub organization_id: Uuid, pub name: String, pub description: Option, pub is_system_role: bool, pub hierarchy_level: u32, pub permissions: Vec, pub inherits_from: Vec, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrganizationGroup { pub id: Uuid, pub organization_id: Uuid, pub name: String, pub description: Option, pub is_system_group: bool, pub roles: Vec, pub members: Vec, pub permissions: Vec, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserPermissionContext { pub user_id: Uuid, pub organization_id: Uuid, pub roles: Vec, pub groups: Vec, pub direct_permissions: Vec, pub is_organization_owner: bool, pub is_organization_admin: bool, pub mfa_verified: bool, pub session_created_at: DateTime, pub ip_address: Option, pub device_type: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccessCheckRequest { pub user_context: UserPermissionContext, pub permission: Permission, pub resource: Resource, pub action_context: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccessCheckResult { pub allowed: bool, pub reason: AccessReason, pub matching_grants: Vec, pub evaluated_policies: Vec, pub audit_id: Uuid, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum AccessReason { Allowed, OrganizationOwner, OrganizationAdmin, RoleGrant, GroupGrant, DirectGrant, ResourceOwner, InheritedPermission, DeniedNoPermission, DeniedConditionFailed, DeniedExpiredGrant, DeniedResourceNotFound, DeniedOrganizationMismatch, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyEvaluation { pub policy_id: Uuid, pub policy_name: String, pub result: PolicyResult, pub conditions_evaluated: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum PolicyResult { Allow, Deny, NotApplicable, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConditionEvaluation { pub condition: PermissionCondition, pub passed: bool, pub actual_value: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResourcePolicy { pub id: Uuid, pub name: String, pub description: Option, pub organization_id: Uuid, pub resource_type: ResourceType, pub resource_pattern: Option, pub effect: PolicyEffect, pub principals: PolicyPrincipals, pub permissions: Vec, pub conditions: Vec, pub priority: i32, pub enabled: bool, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum PolicyEffect { Allow, Deny, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PolicyPrincipals { pub users: Vec, pub roles: Vec, pub groups: Vec, pub all_authenticated: bool, pub resource_owner: bool, } type UserRolesMap = HashMap<(Uuid, Uuid), Vec>; pub struct OrganizationRbacService { roles: Arc>>, groups: Arc>>, policies: Arc>>, user_roles: Arc>, audit_log: Arc>>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccessAuditEntry { pub id: Uuid, pub timestamp: DateTime, pub user_id: Uuid, pub organization_id: Uuid, pub permission: Permission, pub resource_type: ResourceType, pub resource_id: Option, pub result: AccessReason, pub ip_address: Option, pub user_agent: Option, } impl Default for OrganizationRbacService { fn default() -> Self { Self::new() } } impl OrganizationRbacService { pub fn new() -> Self { Self { roles: Arc::new(RwLock::new(HashMap::new())), groups: Arc::new(RwLock::new(HashMap::new())), policies: Arc::new(RwLock::new(HashMap::new())), user_roles: Arc::new(RwLock::new(HashMap::new())), audit_log: Arc::new(RwLock::new(Vec::new())), } } pub async fn check_access(&self, request: AccessCheckRequest) -> AccessCheckResult { let audit_id = Uuid::new_v4(); let mut matching_grants = Vec::new(); let mut evaluated_policies = Vec::new(); if request.resource.organization_id != request.user_context.organization_id { return self .create_denied_result( audit_id, AccessReason::DeniedOrganizationMismatch, &request, ) .await; } if request.user_context.is_organization_owner { return self .create_allowed_result( audit_id, AccessReason::OrganizationOwner, matching_grants, evaluated_policies, &request, ) .await; } if request.user_context.is_organization_admin && self.is_admin_allowed_permission(&request.permission) { return self .create_allowed_result( audit_id, AccessReason::OrganizationAdmin, matching_grants, evaluated_policies, &request, ) .await; } if let Some(owner_id) = &request.resource.owner_id { if *owner_id == request.user_context.user_id && self.is_owner_implied_permission(&request.permission) { return self .create_allowed_result( audit_id, AccessReason::ResourceOwner, matching_grants, evaluated_policies, &request, ) .await; } } let policy_result = self.evaluate_policies(&request, &mut evaluated_policies).await; if let Some(result) = policy_result { if result == PolicyEffect::Deny { return self .create_denied_result(audit_id, AccessReason::DeniedConditionFailed, &request) .await; } } if let Some(grant) = self.check_direct_permissions(&request, &mut matching_grants).await { if self.evaluate_conditions(&grant.conditions, &request).await { return self .create_allowed_result( audit_id, AccessReason::DirectGrant, matching_grants, evaluated_policies, &request, ) .await; } } if let Some(grant) = self.check_role_permissions(&request, &mut matching_grants).await { if self.evaluate_conditions(&grant.conditions, &request).await { return self .create_allowed_result( audit_id, AccessReason::RoleGrant, matching_grants, evaluated_policies, &request, ) .await; } } if let Some(grant) = self.check_group_permissions(&request, &mut matching_grants).await { if self.evaluate_conditions(&grant.conditions, &request).await { return self .create_allowed_result( audit_id, AccessReason::GroupGrant, matching_grants, evaluated_policies, &request, ) .await; } } if let Some(grant) = self .check_inherited_permissions(&request, &mut matching_grants) .await { if self.evaluate_conditions(&grant.conditions, &request).await { return self .create_allowed_result( audit_id, AccessReason::InheritedPermission, matching_grants, evaluated_policies, &request, ) .await; } } self.create_denied_result(audit_id, AccessReason::DeniedNoPermission, &request) .await } async fn evaluate_policies( &self, request: &AccessCheckRequest, evaluated: &mut Vec, ) -> Option { let policies = self.policies.read().await; let mut applicable_policies: Vec<&ResourcePolicy> = policies .values() .filter(|p| { p.enabled && p.organization_id == request.user_context.organization_id && p.resource_type == request.resource.resource_type && p.permissions.contains(&request.permission) }) .collect(); applicable_policies.sort_by(|a, b| b.priority.cmp(&a.priority)); for policy in applicable_policies { let principal_matches = self.check_principal_match(policy, request); if !principal_matches { evaluated.push(PolicyEvaluation { policy_id: policy.id, policy_name: policy.name.clone(), result: PolicyResult::NotApplicable, conditions_evaluated: Vec::new(), }); continue; } let mut condition_evals = Vec::new(); let conditions_pass = self .evaluate_policy_conditions(&policy.conditions, request, &mut condition_evals) .await; let result = if conditions_pass { match policy.effect { PolicyEffect::Allow => PolicyResult::Allow, PolicyEffect::Deny => PolicyResult::Deny, } } else { PolicyResult::NotApplicable }; evaluated.push(PolicyEvaluation { policy_id: policy.id, policy_name: policy.name.clone(), result: result.clone(), conditions_evaluated: condition_evals, }); if result == PolicyResult::Deny { return Some(PolicyEffect::Deny); } if result == PolicyResult::Allow { return Some(PolicyEffect::Allow); } } None } fn check_principal_match(&self, policy: &ResourcePolicy, request: &AccessCheckRequest) -> bool { if policy.principals.all_authenticated { return true; } if policy.principals.resource_owner { if let Some(owner_id) = &request.resource.owner_id { if *owner_id == request.user_context.user_id { return true; } } } if policy.principals.users.contains(&request.user_context.user_id) { return true; } for role_id in &request.user_context.roles { if policy.principals.roles.contains(role_id) { return true; } } for group_id in &request.user_context.groups { if policy.principals.groups.contains(group_id) { return true; } } false } async fn evaluate_policy_conditions( &self, conditions: &[PermissionCondition], request: &AccessCheckRequest, evals: &mut Vec, ) -> bool { for condition in conditions { let (passed, actual_value) = self.evaluate_single_condition(condition, request); evals.push(ConditionEvaluation { condition: condition.clone(), passed, actual_value, }); if !passed { return false; } } true } fn evaluate_single_condition( &self, condition: &PermissionCondition, request: &AccessCheckRequest, ) -> (bool, Option) { match condition.condition_type { ConditionType::MfaVerified => { let actual = request.user_context.mfa_verified; let expected = condition.value == "true"; (actual == expected, Some(actual.to_string())) } ConditionType::IpAddress => { if let Some(ip) = &request.user_context.ip_address { let passed = self.match_condition_value(&condition.operator, ip, &condition.value); (passed, Some(ip.clone())) } else { (false, None) } } ConditionType::DeviceType => { if let Some(device) = &request.user_context.device_type { let passed = self.match_condition_value(&condition.operator, device, &condition.value); (passed, Some(device.clone())) } else { (false, None) } } ConditionType::SessionAge => { let age_seconds = (Utc::now() - request.user_context.session_created_at).num_seconds(); let max_age: i64 = condition.value.parse().unwrap_or(3600); let passed = match condition.operator { ConditionOperator::LessThan => age_seconds < max_age, ConditionOperator::GreaterThan => age_seconds > max_age, _ => age_seconds <= max_age, }; (passed, Some(age_seconds.to_string())) } ConditionType::ResourceOwner => { if let Some(owner_id) = &request.resource.owner_id { let is_owner = *owner_id == request.user_context.user_id; let expected = condition.value == "true"; (is_owner == expected, Some(is_owner.to_string())) } else { (condition.value == "false", None) } } ConditionType::TimeOfDay | ConditionType::DayOfWeek | ConditionType::Location => { (true, None) } ConditionType::ResourceTag => { if let Some(tag_value) = request.action_context.get(&condition.value) { (true, Some(tag_value.clone())) } else { (false, None) } } ConditionType::Custom => { if let Some(custom_value) = request.action_context.get(&condition.value) { (true, Some(custom_value.clone())) } else { (false, None) } } } } fn match_condition_value(&self, operator: &ConditionOperator, actual: &str, expected: &str) -> bool { match operator { ConditionOperator::Equals => actual == expected, ConditionOperator::NotEquals => actual != expected, ConditionOperator::Contains => actual.contains(expected), ConditionOperator::StartsWith => actual.starts_with(expected), ConditionOperator::EndsWith => actual.ends_with(expected), ConditionOperator::In => { expected.split(',').any(|v| v.trim() == actual) } ConditionOperator::NotIn => { !expected.split(',').any(|v| v.trim() == actual) } ConditionOperator::Matches => { regex::Regex::new(expected) .map(|re| re.is_match(actual)) .unwrap_or(false) } ConditionOperator::GreaterThan | ConditionOperator::LessThan => { if let (Ok(a), Ok(e)) = (actual.parse::(), expected.parse::()) { match operator { ConditionOperator::GreaterThan => a > e, ConditionOperator::LessThan => a < e, _ => false, } } else { false } } } } async fn check_direct_permissions( &self, request: &AccessCheckRequest, matching: &mut Vec, ) -> Option { for grant in &request.user_context.direct_permissions { if self.grant_matches(grant, request) { matching.push(grant.clone()); return Some(grant.clone()); } } None } async fn check_role_permissions( &self, request: &AccessCheckRequest, matching: &mut Vec, ) -> Option { let roles = self.roles.read().await; for role_id in &request.user_context.roles { if let Some(role) = roles.get(role_id) { for grant in &role.permissions { if self.grant_matches(grant, request) { matching.push(grant.clone()); return Some(grant.clone()); } } } } None } async fn check_group_permissions( &self, request: &AccessCheckRequest, matching: &mut Vec, ) -> Option { let groups = self.groups.read().await; for group_id in &request.user_context.groups { if let Some(group) = groups.get(group_id) { for grant in &group.permissions { if self.grant_matches(grant, request) { matching.push(grant.clone()); return Some(grant.clone()); } } let roles_guard = self.roles.read().await; for role_id in &group.roles { if let Some(role) = roles_guard.get(role_id) { for grant in &role.permissions { if self.grant_matches(grant, request) { matching.push(grant.clone()); return Some(grant.clone()); } } } } } } None } async fn check_inherited_permissions( &self, request: &AccessCheckRequest, matching: &mut Vec, ) -> Option { let roles = self.roles.read().await; let mut visited_roles = HashSet::new(); let mut roles_to_check: Vec = request.user_context.roles.clone(); while let Some(role_id) = roles_to_check.pop() { if visited_roles.contains(&role_id) { continue; } visited_roles.insert(role_id); if let Some(role) = roles.get(&role_id) { for parent_role_id in &role.inherits_from { if let Some(parent_role) = roles.get(parent_role_id) { for grant in &parent_role.permissions { if self.grant_matches(grant, request) { matching.push(grant.clone()); return Some(grant.clone()); } } roles_to_check.push(*parent_role_id); } } } } None } fn grant_matches(&self, grant: &PermissionGrant, request: &AccessCheckRequest) -> bool { if grant.permission != request.permission { return false; } if grant.resource_type != request.resource.resource_type { return false; } if let Some(resource_id) = grant.resource_id { if resource_id != request.resource.resource_id { return false; } } if let Some(expires_at) = grant.expires_at { if expires_at < Utc::now() { return false; } } true } async fn evaluate_conditions( &self, conditions: &[PermissionCondition], request: &AccessCheckRequest, ) -> bool { for condition in conditions { let (passed, _) = self.evaluate_single_condition(condition, request); if !passed { return false; } } true } fn is_admin_allowed_permission(&self, permission: &Permission) -> bool { matches!( permission, Permission::Read | Permission::Write | Permission::Delete | Permission::Execute | Permission::Share | Permission::Export | Permission::Import | Permission::Manage | Permission::Configure ) } fn is_owner_implied_permission(&self, permission: &Permission) -> bool { matches!( permission, Permission::Read | Permission::Write | Permission::Delete | Permission::Share | Permission::Export ) } async fn create_allowed_result( &self, audit_id: Uuid, reason: AccessReason, matching_grants: Vec, evaluated_policies: Vec, request: &AccessCheckRequest, ) -> AccessCheckResult { self.log_access(audit_id, request, &reason).await; AccessCheckResult { allowed: true, reason, matching_grants, evaluated_policies, audit_id, } } async fn create_denied_result( &self, audit_id: Uuid, reason: AccessReason, request: &AccessCheckRequest, ) -> AccessCheckResult { self.log_access(audit_id, request, &reason).await; AccessCheckResult { allowed: false, reason, matching_grants: Vec::new(), evaluated_policies: Vec::new(), audit_id, } } async fn log_access(&self, audit_id: Uuid, request: &AccessCheckRequest, reason: &AccessReason) { let entry = AccessAuditEntry { id: audit_id, timestamp: Utc::now(), user_id: request.user_context.user_id, organization_id: request.user_context.organization_id, permission: request.permission.clone(), resource_type: request.resource.resource_type.clone(), resource_id: Some(request.resource.resource_id), result: reason.clone(), ip_address: request.user_context.ip_address.clone(), user_agent: request.action_context.get("user_agent").cloned(), }; let mut log = self.audit_log.write().await; log.push(entry); if log.len() > 10000 { log.drain(0..1000); } } pub async fn create_role(&self, role: OrganizationRole) -> Result { let mut roles = self.roles.write().await; if roles.values().any(|r| { r.organization_id == role.organization_id && r.name == role.name && r.id != role.id }) { return Err("Role with this name already exists".to_string()); } roles.insert(role.id, role.clone()); Ok(role) } pub async fn update_role(&self, role: OrganizationRole) -> Result { let mut roles = self.roles.write().await; if !roles.contains_key(&role.id) { return Err("Role not found".to_string()); } roles.insert(role.id, role.clone()); Ok(role) } pub async fn delete_role(&self, role_id: Uuid) -> Result<(), String> { let mut roles = self.roles.write().await; if let Some(role) = roles.get(&role_id) { if role.is_system_role { return Err("Cannot delete system role".to_string()); } } roles.remove(&role_id); Ok(()) } pub async fn get_role(&self, role_id: Uuid) -> Option { let roles = self.roles.read().await; roles.get(&role_id).cloned() } pub async fn get_organization_roles(&self, organization_id: Uuid) -> Vec { let roles = self.roles.read().await; roles .values() .filter(|r| r.organization_id == organization_id) .cloned() .collect() } pub async fn create_group(&self, group: OrganizationGroup) -> Result { let mut groups = self.groups.write().await; if groups.values().any(|g| { g.organization_id == group.organization_id && g.name == group.name && g.id != group.id }) { return Err("Group with this name already exists".to_string()); } groups.insert(group.id, group.clone()); Ok(group) } pub async fn update_group(&self, group: OrganizationGroup) -> Result { let mut groups = self.groups.write().await; if !groups.contains_key(&group.id) { return Err("Group not found".to_string()); } groups.insert(group.id, group.clone()); Ok(group) } pub async fn delete_group(&self, group_id: Uuid) -> Result<(), String> { let mut groups = self.groups.write().await; if let Some(group) = groups.get(&group_id) { if group.is_system_group { return Err("Cannot delete system group".to_string()); } } groups.remove(&group_id); Ok(()) } pub async fn add_user_to_role( &self, user_id: Uuid, organization_id: Uuid, role_id: Uuid, ) -> Result<(), String> { let roles = self.roles.read().await; if !roles.contains_key(&role_id) { return Err("Role not found".to_string()); } drop(roles); let mut user_roles = self.user_roles.write().await; let entry = user_roles .entry((organization_id, user_id)) .or_default(); if !entry.contains(&role_id) { entry.push(role_id); } Ok(()) } pub async fn remove_user_from_role( &self, user_id: Uuid, organization_id: Uuid, role_id: Uuid, ) -> Result<(), String> { let mut user_roles = self.user_roles.write().await; if let Some(roles) = user_roles.get_mut(&(organization_id, user_id)) { roles.retain(|&r| r != role_id); } Ok(()) } pub async fn get_user_roles( &self, user_id: Uuid, organization_id: Uuid, ) -> Vec { let user_roles = self.user_roles.read().await; let roles = self.roles.read().await; user_roles .get(&(organization_id, user_id)) .map(|role_ids| { role_ids .iter() .filter_map(|id| roles.get(id).cloned()) .collect() }) .unwrap_or_default() } }