kotoba_security/
rbac.rs

1//! Role-Based Access Control (RBAC) implementation
2//!
3//! This module provides comprehensive RBAC functionality including:
4//! - Role definitions and hierarchies
5//! - Role assignments to users/principals
6//! - Role-based permission evaluation
7//! - Role inheritance and composition
8
9use crate::capabilities::{Capability, CapabilitySet, ResourceType, Action};
10use crate::error::{SecurityError, Result};
11use serde::{Deserialize, Serialize};
12use std::collections::{HashMap, HashSet};
13
14/// Unique identifier for a role
15pub type RoleId = String;
16
17/// Unique identifier for a user/principal
18pub type PrincipalId = String;
19
20/// Represents a role in the RBAC system
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Role {
23    /// Unique identifier for the role
24    pub id: RoleId,
25    /// Human-readable name
26    pub name: String,
27    /// Description of the role
28    pub description: Option<String>,
29    /// Parent roles (for inheritance)
30    pub parent_roles: HashSet<RoleId>,
31    /// Capabilities granted by this role
32    pub capabilities: CapabilitySet,
33    /// Additional attributes/metadata
34    pub attributes: HashMap<String, serde_json::Value>,
35    /// Whether this role is active
36    pub active: bool,
37    /// Creation timestamp
38    pub created_at: chrono::DateTime<chrono::Utc>,
39    /// Last modification timestamp
40    pub updated_at: chrono::DateTime<chrono::Utc>,
41}
42
43impl Role {
44    /// Create a new role
45    pub fn new(id: RoleId, name: String) -> Self {
46        let now = chrono::Utc::now();
47        Self {
48            id,
49            name,
50            description: None,
51            parent_roles: HashSet::new(),
52            capabilities: CapabilitySet::new(),
53            attributes: HashMap::new(),
54            active: true,
55            created_at: now,
56            updated_at: now,
57        }
58    }
59
60    /// Create a role with description
61    pub fn with_description(mut self, description: String) -> Self {
62        self.description = Some(description);
63        self
64    }
65
66    /// Add a parent role for inheritance
67    pub fn add_parent_role(mut self, parent_role_id: RoleId) -> Self {
68        self.parent_roles.insert(parent_role_id);
69        self.updated_at = chrono::Utc::now();
70        self
71    }
72
73    /// Add a capability to this role
74    pub fn add_capability(mut self, capability: Capability) -> Self {
75        self.capabilities.add_capability(capability);
76        self.updated_at = chrono::Utc::now();
77        self
78    }
79
80    /// Add multiple capabilities to this role
81    pub fn add_capabilities(mut self, capabilities: Vec<Capability>) -> Self {
82        for cap in capabilities {
83            self.capabilities.add_capability(cap);
84        }
85        self.updated_at = chrono::Utc::now();
86        self
87    }
88
89    /// Set role as inactive
90    pub fn deactivate(mut self) -> Self {
91        self.active = false;
92        self.updated_at = chrono::Utc::now();
93        self
94    }
95
96    /// Check if role is active
97    pub fn is_active(&self) -> bool {
98        self.active
99    }
100
101    /// Get all capabilities including inherited ones
102    pub fn get_all_capabilities(&self, role_registry: &RoleRegistry) -> Result<CapabilitySet> {
103        let mut all_caps = self.capabilities.clone();
104
105        // Add capabilities from parent roles
106        for parent_id in &self.parent_roles {
107            if let Some(parent_role) = role_registry.get_role(parent_id) {
108                if parent_role.is_active() {
109                    let parent_caps = parent_role.get_all_capabilities(role_registry)?;
110                    all_caps = all_caps.union(&parent_caps);
111                }
112            } else {
113                return Err(SecurityError::Configuration(
114                    format!("Parent role '{}' not found", parent_id)
115                ));
116            }
117        }
118
119        Ok(all_caps)
120    }
121}
122
123/// Registry for managing roles and their relationships
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct RoleRegistry {
126    roles: HashMap<RoleId, Role>,
127}
128
129impl RoleRegistry {
130    /// Create a new role registry
131    pub fn new() -> Self {
132        Self {
133            roles: HashMap::new(),
134        }
135    }
136
137    /// Add a role to the registry
138    pub fn add_role(&mut self, role: Role) -> Result<()> {
139        if self.roles.contains_key(&role.id) {
140            return Err(SecurityError::Configuration(
141                format!("Role '{}' already exists", role.id)
142            ));
143        }
144
145        // Validate parent roles exist
146        for parent_id in &role.parent_roles {
147            if !self.roles.contains_key(parent_id) {
148                return Err(SecurityError::Configuration(
149                    format!("Parent role '{}' does not exist", parent_id)
150                ));
151            }
152        }
153
154        self.roles.insert(role.id.clone(), role);
155        Ok(())
156    }
157
158    /// Update an existing role
159    pub fn update_role(&mut self, role: Role) -> Result<()> {
160        if !self.roles.contains_key(&role.id) {
161            return Err(SecurityError::Configuration(
162                format!("Role '{}' does not exist", role.id)
163            ));
164        }
165
166        // Validate parent roles exist
167        for parent_id in &role.parent_roles {
168            if !self.roles.contains_key(parent_id) {
169                return Err(SecurityError::Configuration(
170                    format!("Parent role '{}' does not exist", parent_id)
171                ));
172            }
173        }
174
175        self.roles.insert(role.id.clone(), role);
176        Ok(())
177    }
178
179    /// Remove a role from the registry
180    pub fn remove_role(&mut self, role_id: &RoleId) -> Result<()> {
181        if let Some(role) = self.roles.get(role_id) {
182            // Check if role is referenced as parent by other roles
183            for (id, r) in &self.roles {
184                if id != role_id && r.parent_roles.contains(role_id) {
185                    return Err(SecurityError::Configuration(
186                        format!("Cannot remove role '{}' as it is referenced by role '{}'", role_id, id)
187                    ));
188                }
189            }
190        }
191
192        self.roles.remove(role_id);
193        Ok(())
194    }
195
196    /// Get a role by ID
197    pub fn get_role(&self, role_id: &RoleId) -> Option<&Role> {
198        self.roles.get(role_id)
199    }
200
201    /// Get all roles
202    pub fn get_all_roles(&self) -> Vec<&Role> {
203        self.roles.values().collect()
204    }
205
206    /// Get active roles only
207    pub fn get_active_roles(&self) -> Vec<&Role> {
208        self.roles.values().filter(|r| r.is_active()).collect()
209    }
210
211    /// Check if a role exists
212    pub fn role_exists(&self, role_id: &RoleId) -> bool {
213        self.roles.contains_key(role_id)
214    }
215
216    /// Get all child roles (roles that inherit from the given role)
217    pub fn get_child_roles(&self, role_id: &RoleId) -> Vec<&Role> {
218        self.roles.values()
219            .filter(|r| r.parent_roles.contains(role_id))
220            .collect()
221    }
222
223    /// Get role hierarchy path (from root to leaf)
224    pub fn get_role_hierarchy(&self, role_id: &RoleId) -> Result<Vec<RoleId>> {
225        let mut path = Vec::new();
226        let mut visited = HashSet::new();
227        let mut current_id = role_id.clone();
228
229        while !visited.contains(&current_id) {
230            visited.insert(current_id.clone());
231
232            if let Some(role) = self.get_role(&current_id) {
233                path.push(current_id.clone());
234
235                // For simplicity, pick the first parent (can be extended for multiple inheritance)
236                if let Some(parent_id) = role.parent_roles.iter().next() {
237                    current_id = parent_id.clone();
238                } else {
239                    break; // No more parents
240                }
241            } else {
242                return Err(SecurityError::Configuration(
243                    format!("Role '{}' not found in hierarchy", current_id)
244                ));
245            }
246
247            // Prevent infinite loops
248            if path.len() > 100 {
249                return Err(SecurityError::Configuration(
250                    "Circular role inheritance detected".to_string()
251                ));
252            }
253        }
254
255        Ok(path)
256    }
257}
258
259impl Default for RoleRegistry {
260    fn default() -> Self {
261        Self::new()
262    }
263}
264
265/// Role assignment mapping users to roles
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct RoleAssignment {
268    /// User/Principal ID
269    pub principal_id: PrincipalId,
270    /// Assigned role ID
271    pub role_id: RoleId,
272    /// Assignment context/scope (optional)
273    pub scope: Option<String>,
274    /// Assignment conditions (optional)
275    pub conditions: Option<HashMap<String, serde_json::Value>>,
276    /// Assignment timestamp
277    pub assigned_at: chrono::DateTime<chrono::Utc>,
278    /// Assignment expiration (optional)
279    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
280    /// Whether assignment is active
281    pub active: bool,
282}
283
284/// Role assignment manager
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct RoleAssignmentManager {
287    assignments: HashMap<(PrincipalId, RoleId), RoleAssignment>,
288}
289
290impl RoleAssignmentManager {
291    /// Create a new role assignment manager
292    pub fn new() -> Self {
293        Self {
294            assignments: HashMap::new(),
295        }
296    }
297
298    /// Assign a role to a principal
299    pub fn assign_role(&mut self, assignment: RoleAssignment) -> Result<()> {
300        let key = (assignment.principal_id.clone(), assignment.role_id.clone());
301
302        if self.assignments.contains_key(&key) {
303            return Err(SecurityError::Configuration(
304                format!("Role '{}' is already assigned to principal '{}'",
305                       assignment.role_id, assignment.principal_id)
306            ));
307        }
308
309        self.assignments.insert(key, assignment);
310        Ok(())
311    }
312
313    /// Revoke a role from a principal
314    pub fn revoke_role(&mut self, principal_id: &PrincipalId, role_id: &RoleId) -> Result<()> {
315        let key = (principal_id.clone(), role_id.clone());
316        if self.assignments.remove(&key).is_none() {
317            return Err(SecurityError::Configuration(
318                format!("Role '{}' is not assigned to principal '{}'",
319                       role_id, principal_id)
320            ));
321        }
322        Ok(())
323    }
324
325    /// Get all roles assigned to a principal
326    pub fn get_principal_roles(&self, principal_id: &PrincipalId) -> Vec<&RoleAssignment> {
327        self.assignments.values()
328            .filter(|assignment| {
329                &assignment.principal_id == principal_id &&
330                assignment.active &&
331                assignment.expires_at.map_or(true, |exp| chrono::Utc::now() < exp)
332            })
333            .collect()
334    }
335
336    /// Get all principals with a specific role
337    pub fn get_role_principals(&self, role_id: &RoleId) -> Vec<&RoleAssignment> {
338        self.assignments.values()
339            .filter(|assignment| {
340                &assignment.role_id == role_id &&
341                assignment.active &&
342                assignment.expires_at.map_or(true, |exp| chrono::Utc::now() < exp)
343            })
344            .collect()
345    }
346
347    /// Check if a principal has a specific role
348    pub fn has_role(&self, principal_id: &PrincipalId, role_id: &RoleId) -> bool {
349        self.get_principal_roles(principal_id)
350            .iter()
351            .any(|assignment| &assignment.role_id == role_id)
352    }
353
354    /// Get all active assignments
355    pub fn get_all_assignments(&self) -> Vec<&RoleAssignment> {
356        self.assignments.values()
357            .filter(|assignment| {
358                assignment.active &&
359                assignment.expires_at.map_or(true, |exp| chrono::Utc::now() < exp)
360            })
361            .collect()
362    }
363}
364
365impl Default for RoleAssignmentManager {
366    fn default() -> Self {
367        Self::new()
368    }
369}
370
371/// RBAC Service combining roles, assignments, and evaluation
372#[derive(Debug)]
373pub struct RBACService {
374    role_registry: RoleRegistry,
375    assignment_manager: RoleAssignmentManager,
376}
377
378impl RBACService {
379    /// Create a new RBAC service
380    pub fn new() -> Self {
381        Self {
382            role_registry: RoleRegistry::new(),
383            assignment_manager: RoleAssignmentManager::new(),
384        }
385    }
386
387    /// Create RBAC service with existing registry and assignments
388    pub fn with_data(role_registry: RoleRegistry, assignment_manager: RoleAssignmentManager) -> Self {
389        Self {
390            role_registry,
391            assignment_manager,
392        }
393    }
394
395    /// Add a role to the system
396    pub fn add_role(&mut self, role: Role) -> Result<()> {
397        self.role_registry.add_role(role)
398    }
399
400    /// Assign a role to a principal
401    pub fn assign_role(&mut self, assignment: RoleAssignment) -> Result<()> {
402        // Validate role exists
403        if !self.role_registry.role_exists(&assignment.role_id) {
404            return Err(SecurityError::Configuration(
405                format!("Role '{}' does not exist", assignment.role_id)
406            ));
407        }
408
409        self.assignment_manager.assign_role(assignment)
410    }
411
412    /// Revoke a role from a principal
413    pub fn revoke_role(&mut self, principal_id: &PrincipalId, role_id: &RoleId) -> Result<()> {
414        self.assignment_manager.revoke_role(principal_id, role_id)
415    }
416
417        /// Check if a principal has permission for a specific action on a resource
418        pub fn check_permission(
419            &self,
420            principal_id: &PrincipalId,
421            resource_type: &ResourceType,
422            action: &Action,
423            scope: Option<&str>,
424        ) -> Result<bool> {
425            let principal_assignments = self.assignment_manager.get_principal_roles(principal_id);
426
427            for assignment in principal_assignments {
428                if let Some(role) = self.role_registry.get_role(&assignment.role_id) {
429                    if !role.is_active() {
430                        continue;
431                    }
432
433                    let all_capabilities = role.get_all_capabilities(&self.role_registry)?;
434                    if all_capabilities.allows(resource_type, action, scope) {
435                        return Ok(true);
436                    }
437                }
438            }
439
440            Ok(false)
441        }
442
443    /// Get all effective capabilities for a principal
444    pub fn get_principal_capabilities(&self, principal_id: &PrincipalId) -> Result<CapabilitySet> {
445        let principal_assignments = self.assignment_manager.get_principal_roles(principal_id);
446        let mut all_capabilities = CapabilitySet::new();
447
448        for assignment in principal_assignments {
449            if let Some(role) = self.role_registry.get_role(&assignment.role_id) {
450                if role.is_active() {
451                    let role_capabilities = role.get_all_capabilities(&self.role_registry)?;
452                    all_capabilities = all_capabilities.union(&role_capabilities);
453                }
454            }
455        }
456
457        Ok(all_capabilities)
458    }
459
460    /// Get all roles assigned to a principal
461    pub fn get_principal_roles(&self, principal_id: &PrincipalId) -> Vec<&Role> {
462        let assignments = self.assignment_manager.get_principal_roles(principal_id);
463        assignments.iter()
464            .filter_map(|assignment| self.role_registry.get_role(&assignment.role_id))
465            .collect()
466    }
467
468    /// List all roles
469    pub fn list_roles(&self) -> Vec<&Role> {
470        self.role_registry.get_all_roles()
471    }
472
473    /// List all active role assignments
474    pub fn list_assignments(&self) -> Vec<&RoleAssignment> {
475        self.assignment_manager.get_all_assignments()
476    }
477
478    /// Create common roles for quick setup
479    pub fn create_common_roles(&mut self) -> Result<()> {
480        // Administrator role
481        let admin_role = Role::new("admin".to_string(), "Administrator".to_string())
482            .with_description("Full system access".to_string())
483            .add_capability(Capability::new(ResourceType::Admin, Action::Admin, None));
484
485        // User manager role
486        let user_manager_role = Role::new("user_manager".to_string(), "User Manager".to_string())
487            .with_description("Manage user accounts".to_string())
488            .add_capability(Capability::new(ResourceType::User, Action::Read, None))
489            .add_capability(Capability::new(ResourceType::User, Action::Create, None))
490            .add_capability(Capability::new(ResourceType::User, Action::Update, None));
491
492        // Content editor role
493        let content_editor_role = Role::new("content_editor".to_string(), "Content Editor".to_string())
494            .with_description("Edit content and data".to_string())
495            .add_capability(Capability::new(ResourceType::Graph, Action::Read, None))
496            .add_capability(Capability::new(ResourceType::Graph, Action::Create, None))
497            .add_capability(Capability::new(ResourceType::Graph, Action::Update, None));
498
499        // Content viewer role
500        let content_viewer_role = Role::new("content_viewer".to_string(), "Content Viewer".to_string())
501            .with_description("View content and data".to_string())
502            .add_capability(Capability::new(ResourceType::Graph, Action::Read, None));
503
504        // Add roles to registry
505        self.add_role(admin_role)?;
506        self.add_role(user_manager_role)?;
507        self.add_role(content_editor_role)?;
508        self.add_role(content_viewer_role)?;
509
510        Ok(())
511    }
512}
513
514impl Default for RBACService {
515    fn default() -> Self {
516        Self::new()
517    }
518}
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523
524    #[test]
525    fn test_role_creation() {
526        let role = Role::new("test_role".to_string(), "Test Role".to_string())
527            .with_description("A test role".to_string());
528
529        assert_eq!(role.id, "test_role");
530        assert_eq!(role.name, "Test Role");
531        assert_eq!(role.description, Some("A test role".to_string()));
532        assert!(role.is_active());
533    }
534
535    #[test]
536    fn test_role_registry() {
537        let mut registry = RoleRegistry::new();
538
539        let role = Role::new("test_role".to_string(), "Test Role".to_string());
540        registry.add_role(role.clone()).unwrap();
541
542        assert!(registry.role_exists(&"test_role".to_string()));
543        assert_eq!(registry.get_role(&"test_role".to_string()).unwrap().name, "Test Role");
544    }
545
546    #[test]
547    fn test_role_assignment() {
548        let mut manager = RoleAssignmentManager::new();
549
550        let assignment = RoleAssignment {
551            principal_id: "user1".to_string(),
552            role_id: "role1".to_string(),
553            scope: None,
554            conditions: None,
555            assigned_at: chrono::Utc::now(),
556            expires_at: None,
557            active: true,
558        };
559
560        manager.assign_role(assignment).unwrap();
561
562        let user_roles = manager.get_principal_roles(&"user1".to_string());
563        assert_eq!(user_roles.len(), 1);
564        assert_eq!(user_roles[0].role_id, "role1");
565    }
566
567    #[test]
568    fn test_rbac_permission_check() {
569        let mut rbac = RBACService::new();
570
571        // Create a role with read permission
572        let role = Role::new("reader".to_string(), "Reader".to_string())
573            .add_capability(Capability::new(ResourceType::Graph, Action::Read, None));
574
575        rbac.add_role(role).unwrap();
576
577        // Assign role to user
578        let assignment = RoleAssignment {
579            principal_id: "user1".to_string(),
580            role_id: "reader".to_string(),
581            scope: None,
582            conditions: None,
583            assigned_at: chrono::Utc::now(),
584            expires_at: None,
585            active: true,
586        };
587
588        rbac.assign_role(assignment).unwrap();
589
590        // Check permission
591        assert!(rbac.check_permission(&"user1".to_string(), &ResourceType::Graph, &Action::Read, None).unwrap());
592        assert!(!rbac.check_permission(&"user1".to_string(), &ResourceType::Graph, &Action::Write, None).unwrap());
593    }
594
595    #[test]
596    fn test_role_hierarchy() {
597        let mut registry = RoleRegistry::new();
598
599        // Create parent role
600        let parent_role = Role::new("parent".to_string(), "Parent Role".to_string())
601            .add_capability(Capability::new(ResourceType::Graph, Action::Read, None));
602
603        // Create child role that inherits from parent
604        let child_role = Role::new("child".to_string(), "Child Role".to_string())
605            .add_parent_role("parent".to_string())
606            .add_capability(Capability::new(ResourceType::Graph, Action::Write, None));
607
608        registry.add_role(parent_role).unwrap();
609        registry.add_role(child_role).unwrap();
610
611        let child = registry.get_role(&"child".to_string()).unwrap();
612        let all_caps = child.get_all_capabilities(&registry).unwrap();
613
614        // Child should have both read (from parent) and write (own) permissions
615        assert!(all_caps.has_capability(&ResourceType::Graph, &Action::Read, None));
616        assert!(all_caps.has_capability(&ResourceType::Graph, &Action::Write, None));
617    }
618}