use crate::capabilities::{Capability, CapabilitySet, ResourceType, Action};
use crate::error::{SecurityError, Result};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
pub type RoleId = String;
pub type PrincipalId = String;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Role {
pub id: RoleId,
pub name: String,
pub description: Option<String>,
pub parent_roles: HashSet<RoleId>,
pub capabilities: CapabilitySet,
pub attributes: HashMap<String, serde_json::Value>,
pub active: bool,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
}
impl Role {
pub fn new(id: RoleId, name: String) -> Self {
let now = chrono::Utc::now();
Self {
id,
name,
description: None,
parent_roles: HashSet::new(),
capabilities: CapabilitySet::new(),
attributes: HashMap::new(),
active: true,
created_at: now,
updated_at: now,
}
}
pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
pub fn add_parent_role(mut self, parent_role_id: RoleId) -> Self {
self.parent_roles.insert(parent_role_id);
self.updated_at = chrono::Utc::now();
self
}
pub fn add_capability(mut self, capability: Capability) -> Self {
self.capabilities.add_capability(capability);
self.updated_at = chrono::Utc::now();
self
}
pub fn add_capabilities(mut self, capabilities: Vec<Capability>) -> Self {
for cap in capabilities {
self.capabilities.add_capability(cap);
}
self.updated_at = chrono::Utc::now();
self
}
pub fn deactivate(mut self) -> Self {
self.active = false;
self.updated_at = chrono::Utc::now();
self
}
pub fn is_active(&self) -> bool {
self.active
}
pub fn get_all_capabilities(&self, role_registry: &RoleRegistry) -> Result<CapabilitySet> {
let mut all_caps = self.capabilities.clone();
for parent_id in &self.parent_roles {
if let Some(parent_role) = role_registry.get_role(parent_id) {
if parent_role.is_active() {
let parent_caps = parent_role.get_all_capabilities(role_registry)?;
all_caps = all_caps.union(&parent_caps);
}
} else {
return Err(SecurityError::Configuration(
format!("Parent role '{}' not found", parent_id)
));
}
}
Ok(all_caps)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoleRegistry {
roles: HashMap<RoleId, Role>,
}
impl RoleRegistry {
pub fn new() -> Self {
Self {
roles: HashMap::new(),
}
}
pub fn add_role(&mut self, role: Role) -> Result<()> {
if self.roles.contains_key(&role.id) {
return Err(SecurityError::Configuration(
format!("Role '{}' already exists", role.id)
));
}
for parent_id in &role.parent_roles {
if !self.roles.contains_key(parent_id) {
return Err(SecurityError::Configuration(
format!("Parent role '{}' does not exist", parent_id)
));
}
}
self.roles.insert(role.id.clone(), role);
Ok(())
}
pub fn update_role(&mut self, role: Role) -> Result<()> {
if !self.roles.contains_key(&role.id) {
return Err(SecurityError::Configuration(
format!("Role '{}' does not exist", role.id)
));
}
for parent_id in &role.parent_roles {
if !self.roles.contains_key(parent_id) {
return Err(SecurityError::Configuration(
format!("Parent role '{}' does not exist", parent_id)
));
}
}
self.roles.insert(role.id.clone(), role);
Ok(())
}
pub fn remove_role(&mut self, role_id: &RoleId) -> Result<()> {
if let Some(role) = self.roles.get(role_id) {
for (id, r) in &self.roles {
if id != role_id && r.parent_roles.contains(role_id) {
return Err(SecurityError::Configuration(
format!("Cannot remove role '{}' as it is referenced by role '{}'", role_id, id)
));
}
}
}
self.roles.remove(role_id);
Ok(())
}
pub fn get_role(&self, role_id: &RoleId) -> Option<&Role> {
self.roles.get(role_id)
}
pub fn get_all_roles(&self) -> Vec<&Role> {
self.roles.values().collect()
}
pub fn get_active_roles(&self) -> Vec<&Role> {
self.roles.values().filter(|r| r.is_active()).collect()
}
pub fn role_exists(&self, role_id: &RoleId) -> bool {
self.roles.contains_key(role_id)
}
pub fn get_child_roles(&self, role_id: &RoleId) -> Vec<&Role> {
self.roles.values()
.filter(|r| r.parent_roles.contains(role_id))
.collect()
}
pub fn get_role_hierarchy(&self, role_id: &RoleId) -> Result<Vec<RoleId>> {
let mut path = Vec::new();
let mut visited = HashSet::new();
let mut current_id = role_id.clone();
while !visited.contains(¤t_id) {
visited.insert(current_id.clone());
if let Some(role) = self.get_role(¤t_id) {
path.push(current_id.clone());
if let Some(parent_id) = role.parent_roles.iter().next() {
current_id = parent_id.clone();
} else {
break; }
} else {
return Err(SecurityError::Configuration(
format!("Role '{}' not found in hierarchy", current_id)
));
}
if path.len() > 100 {
return Err(SecurityError::Configuration(
"Circular role inheritance detected".to_string()
));
}
}
Ok(path)
}
}
impl Default for RoleRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoleAssignment {
pub principal_id: PrincipalId,
pub role_id: RoleId,
pub scope: Option<String>,
pub conditions: Option<HashMap<String, serde_json::Value>>,
pub assigned_at: chrono::DateTime<chrono::Utc>,
pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
pub active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoleAssignmentManager {
assignments: HashMap<(PrincipalId, RoleId), RoleAssignment>,
}
impl RoleAssignmentManager {
pub fn new() -> Self {
Self {
assignments: HashMap::new(),
}
}
pub fn assign_role(&mut self, assignment: RoleAssignment) -> Result<()> {
let key = (assignment.principal_id.clone(), assignment.role_id.clone());
if self.assignments.contains_key(&key) {
return Err(SecurityError::Configuration(
format!("Role '{}' is already assigned to principal '{}'",
assignment.role_id, assignment.principal_id)
));
}
self.assignments.insert(key, assignment);
Ok(())
}
pub fn revoke_role(&mut self, principal_id: &PrincipalId, role_id: &RoleId) -> Result<()> {
let key = (principal_id.clone(), role_id.clone());
if self.assignments.remove(&key).is_none() {
return Err(SecurityError::Configuration(
format!("Role '{}' is not assigned to principal '{}'",
role_id, principal_id)
));
}
Ok(())
}
pub fn get_principal_roles(&self, principal_id: &PrincipalId) -> Vec<&RoleAssignment> {
self.assignments.values()
.filter(|assignment| {
&assignment.principal_id == principal_id &&
assignment.active &&
assignment.expires_at.map_or(true, |exp| chrono::Utc::now() < exp)
})
.collect()
}
pub fn get_role_principals(&self, role_id: &RoleId) -> Vec<&RoleAssignment> {
self.assignments.values()
.filter(|assignment| {
&assignment.role_id == role_id &&
assignment.active &&
assignment.expires_at.map_or(true, |exp| chrono::Utc::now() < exp)
})
.collect()
}
pub fn has_role(&self, principal_id: &PrincipalId, role_id: &RoleId) -> bool {
self.get_principal_roles(principal_id)
.iter()
.any(|assignment| &assignment.role_id == role_id)
}
pub fn get_all_assignments(&self) -> Vec<&RoleAssignment> {
self.assignments.values()
.filter(|assignment| {
assignment.active &&
assignment.expires_at.map_or(true, |exp| chrono::Utc::now() < exp)
})
.collect()
}
}
impl Default for RoleAssignmentManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct RBACService {
role_registry: RoleRegistry,
assignment_manager: RoleAssignmentManager,
}
impl RBACService {
pub fn new() -> Self {
Self {
role_registry: RoleRegistry::new(),
assignment_manager: RoleAssignmentManager::new(),
}
}
pub fn with_data(role_registry: RoleRegistry, assignment_manager: RoleAssignmentManager) -> Self {
Self {
role_registry,
assignment_manager,
}
}
pub fn add_role(&mut self, role: Role) -> Result<()> {
self.role_registry.add_role(role)
}
pub fn assign_role(&mut self, assignment: RoleAssignment) -> Result<()> {
if !self.role_registry.role_exists(&assignment.role_id) {
return Err(SecurityError::Configuration(
format!("Role '{}' does not exist", assignment.role_id)
));
}
self.assignment_manager.assign_role(assignment)
}
pub fn revoke_role(&mut self, principal_id: &PrincipalId, role_id: &RoleId) -> Result<()> {
self.assignment_manager.revoke_role(principal_id, role_id)
}
pub fn check_permission(
&self,
principal_id: &PrincipalId,
resource_type: &ResourceType,
action: &Action,
scope: Option<&str>,
) -> Result<bool> {
let principal_assignments = self.assignment_manager.get_principal_roles(principal_id);
for assignment in principal_assignments {
if let Some(role) = self.role_registry.get_role(&assignment.role_id) {
if !role.is_active() {
continue;
}
let all_capabilities = role.get_all_capabilities(&self.role_registry)?;
if all_capabilities.allows(resource_type, action, scope) {
return Ok(true);
}
}
}
Ok(false)
}
pub fn get_principal_capabilities(&self, principal_id: &PrincipalId) -> Result<CapabilitySet> {
let principal_assignments = self.assignment_manager.get_principal_roles(principal_id);
let mut all_capabilities = CapabilitySet::new();
for assignment in principal_assignments {
if let Some(role) = self.role_registry.get_role(&assignment.role_id) {
if role.is_active() {
let role_capabilities = role.get_all_capabilities(&self.role_registry)?;
all_capabilities = all_capabilities.union(&role_capabilities);
}
}
}
Ok(all_capabilities)
}
pub fn get_principal_roles(&self, principal_id: &PrincipalId) -> Vec<&Role> {
let assignments = self.assignment_manager.get_principal_roles(principal_id);
assignments.iter()
.filter_map(|assignment| self.role_registry.get_role(&assignment.role_id))
.collect()
}
pub fn list_roles(&self) -> Vec<&Role> {
self.role_registry.get_all_roles()
}
pub fn list_assignments(&self) -> Vec<&RoleAssignment> {
self.assignment_manager.get_all_assignments()
}
pub fn create_common_roles(&mut self) -> Result<()> {
let admin_role = Role::new("admin".to_string(), "Administrator".to_string())
.with_description("Full system access".to_string())
.add_capability(Capability::new(ResourceType::Admin, Action::Admin, None));
let user_manager_role = Role::new("user_manager".to_string(), "User Manager".to_string())
.with_description("Manage user accounts".to_string())
.add_capability(Capability::new(ResourceType::User, Action::Read, None))
.add_capability(Capability::new(ResourceType::User, Action::Create, None))
.add_capability(Capability::new(ResourceType::User, Action::Update, None));
let content_editor_role = Role::new("content_editor".to_string(), "Content Editor".to_string())
.with_description("Edit content and data".to_string())
.add_capability(Capability::new(ResourceType::Graph, Action::Read, None))
.add_capability(Capability::new(ResourceType::Graph, Action::Create, None))
.add_capability(Capability::new(ResourceType::Graph, Action::Update, None));
let content_viewer_role = Role::new("content_viewer".to_string(), "Content Viewer".to_string())
.with_description("View content and data".to_string())
.add_capability(Capability::new(ResourceType::Graph, Action::Read, None));
self.add_role(admin_role)?;
self.add_role(user_manager_role)?;
self.add_role(content_editor_role)?;
self.add_role(content_viewer_role)?;
Ok(())
}
}
impl Default for RBACService {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_role_creation() {
let role = Role::new("test_role".to_string(), "Test Role".to_string())
.with_description("A test role".to_string());
assert_eq!(role.id, "test_role");
assert_eq!(role.name, "Test Role");
assert_eq!(role.description, Some("A test role".to_string()));
assert!(role.is_active());
}
#[test]
fn test_role_registry() {
let mut registry = RoleRegistry::new();
let role = Role::new("test_role".to_string(), "Test Role".to_string());
registry.add_role(role.clone()).unwrap();
assert!(registry.role_exists(&"test_role".to_string()));
assert_eq!(registry.get_role(&"test_role".to_string()).unwrap().name, "Test Role");
}
#[test]
fn test_role_assignment() {
let mut manager = RoleAssignmentManager::new();
let assignment = RoleAssignment {
principal_id: "user1".to_string(),
role_id: "role1".to_string(),
scope: None,
conditions: None,
assigned_at: chrono::Utc::now(),
expires_at: None,
active: true,
};
manager.assign_role(assignment).unwrap();
let user_roles = manager.get_principal_roles(&"user1".to_string());
assert_eq!(user_roles.len(), 1);
assert_eq!(user_roles[0].role_id, "role1");
}
#[test]
fn test_rbac_permission_check() {
let mut rbac = RBACService::new();
let role = Role::new("reader".to_string(), "Reader".to_string())
.add_capability(Capability::new(ResourceType::Graph, Action::Read, None));
rbac.add_role(role).unwrap();
let assignment = RoleAssignment {
principal_id: "user1".to_string(),
role_id: "reader".to_string(),
scope: None,
conditions: None,
assigned_at: chrono::Utc::now(),
expires_at: None,
active: true,
};
rbac.assign_role(assignment).unwrap();
assert!(rbac.check_permission(&"user1".to_string(), &ResourceType::Graph, &Action::Read, None).unwrap());
assert!(!rbac.check_permission(&"user1".to_string(), &ResourceType::Graph, &Action::Write, None).unwrap());
}
#[test]
fn test_role_hierarchy() {
let mut registry = RoleRegistry::new();
let parent_role = Role::new("parent".to_string(), "Parent Role".to_string())
.add_capability(Capability::new(ResourceType::Graph, Action::Read, None));
let child_role = Role::new("child".to_string(), "Child Role".to_string())
.add_parent_role("parent".to_string())
.add_capability(Capability::new(ResourceType::Graph, Action::Write, None));
registry.add_role(parent_role).unwrap();
registry.add_role(child_role).unwrap();
let child = registry.get_role(&"child".to_string()).unwrap();
let all_caps = child.get_all_capabilities(®istry).unwrap();
assert!(all_caps.has_capability(&ResourceType::Graph, &Action::Read, None));
assert!(all_caps.has_capability(&ResourceType::Graph, &Action::Write, None));
}
}