role_system/
core.rs

1//! Core role system implementation.
2//! 
3//! This module contains the main implementation of the role-based access control system.
4//! It provides the central `RoleSystem` struct which manages roles, permissions, subjects,
5//! and resources, along with all access control operations.
6//!
7//! # Architecture
8//!
9//! The role system is built around these key components:
10//!
11//! - **Roles**: Named entities with assigned permissions
12//! - **Permissions**: Grant access to perform actions on resource types
13//! - **Subjects**: Users, services, or other entities that are assigned roles
14//! - **Resources**: Objects that are protected by permissions
15//! - **Storage**: Backend for persisting roles and other entities
16//!
17//! # Thread Safety
18//!
19//! The implementation uses `DashMap` for concurrent access to internal data structures,
20//! making it thread-safe for use in multi-threaded applications.
21//!
22//! # Caching
23//!
24//! Permission checks are cached to improve performance for repeated access checks,
25//! with configurable cache TTL and invalidation on role changes.
26
27#[cfg(feature = "audit")]
28use log::{info, warn};
29
30use crate::{
31    error::{Error, Result},
32    resource::Resource,
33    role::{Role, RoleElevation},
34    storage::{Storage, MemoryStorage},
35    subject::Subject,
36};
37use dashmap::DashMap;
38use std::collections::{HashMap, HashSet};
39use std::time::{Duration, Instant};
40
41/// The result of an access check.
42#[derive(Debug, Clone, PartialEq)]
43pub enum AccessResult {
44    /// Access is granted.
45    Granted,
46    /// Access is denied with a reason.
47    Denied(String),
48}
49
50impl AccessResult {
51    /// Returns true if access was granted.
52    pub fn is_granted(&self) -> bool {
53        matches!(self, AccessResult::Granted)
54    }
55
56    /// Returns true if access was denied.
57    pub fn is_denied(&self) -> bool {
58        !self.is_granted()
59    }
60
61    /// Returns the denial reason if access was denied.
62    pub fn denial_reason(&self) -> Option<&str> {
63        match self {
64            AccessResult::Denied(reason) => Some(reason),
65            AccessResult::Granted => None,
66        }
67    }
68}
69
70impl From<bool> for AccessResult {
71    fn from(granted: bool) -> Self {
72        if granted {
73            AccessResult::Granted
74        } else {
75            AccessResult::Denied("Access denied".to_string())
76        }
77    }
78}
79
80/// Configuration for the role system.
81#[derive(Debug, Clone)]
82pub struct RoleSystemConfig {
83    /// Maximum depth for role hierarchy traversal.
84    pub max_hierarchy_depth: usize,
85    /// Whether to enable permission caching.
86    pub enable_caching: bool,
87    /// Cache TTL in seconds.
88    pub cache_ttl_seconds: u64,
89    /// Whether to enable audit logging.
90    pub enable_audit: bool,
91}
92
93impl Default for RoleSystemConfig {
94    fn default() -> Self {
95        Self {
96            max_hierarchy_depth: 10,
97            enable_caching: true,
98            cache_ttl_seconds: 300, // 5 minutes
99            enable_audit: true,
100        }
101    }
102}
103
104/// The main role-based access control system.
105pub struct RoleSystem<S = MemoryStorage>
106where
107    S: Storage,
108{
109    storage: S,
110    config: RoleSystemConfig,
111    // Role hierarchy: child -> parents
112    role_hierarchy: DashMap<String, HashSet<String>>,
113    // Subject role assignments
114    subject_roles: DashMap<String, HashSet<String>>,
115    // Temporary role elevations
116    role_elevations: DashMap<String, Vec<RoleElevation>>,
117    // Permission cache: (subject_id, permission, resource_id, context_hash) -> (result, expiry)
118    permission_cache: DashMap<(String, String, String, String), (AccessResult, Instant)>,
119}
120
121impl RoleSystem<MemoryStorage> {
122    /// Create a new role system with default configuration and memory storage.
123    pub fn new() -> Self {
124        Self::with_config(RoleSystemConfig::default())
125    }
126
127    /// Create a new role system with custom configuration and memory storage.
128    pub fn with_config(config: RoleSystemConfig) -> Self {
129        Self {
130            storage: MemoryStorage::new(),
131            config,
132            role_hierarchy: DashMap::new(),
133            subject_roles: DashMap::new(),
134            role_elevations: DashMap::new(),
135            permission_cache: DashMap::new(),
136        }
137    }
138}
139
140impl<S> RoleSystem<S>
141where
142    S: Storage,
143{
144    /// Create a new role system with custom storage.
145    pub fn with_storage(storage: S, config: RoleSystemConfig) -> Self {
146        Self {
147            storage,
148            config,
149            role_hierarchy: DashMap::new(),
150            subject_roles: DashMap::new(),
151            role_elevations: DashMap::new(),
152            permission_cache: DashMap::new(),
153        }
154    }
155
156    /// Register a new role in the system.
157    pub fn register_role(&mut self, role: Role) -> Result<()> {
158        let role_name = role.name().to_string();
159        
160        if self.storage.role_exists(&role_name)? {
161            return Err(Error::RoleAlreadyExists(role_name));
162        }
163
164        self.storage.store_role(role)?;
165        
166        #[cfg(feature = "audit")]
167        info!("Role '{role_name}' registered");
168        
169        Ok(())
170    }
171
172    /// Get a role by name.
173    pub fn get_role(&self, name: &str) -> Result<Option<Role>> {
174        self.storage.get_role(name)
175    }
176
177    /// Update an existing role.
178    pub fn update_role(&mut self, role: Role) -> Result<()> {
179        let role_name = role.name().to_string();
180        
181        if !self.storage.role_exists(&role_name)? {
182            return Err(Error::RoleNotFound(role_name));
183        }
184
185        self.storage.update_role(role)?;
186        
187        // Clear permission cache for all subjects with this role
188        self.clear_role_cache(&role_name);
189        
190        #[cfg(feature = "audit")]
191        info!("Role '{role_name}' updated");
192        
193        Ok(())
194    }
195
196    /// Add role inheritance (child inherits from parent).
197    pub fn add_role_inheritance(&mut self, child: &str, parent: &str) -> Result<()> {
198        // Check that both roles exist
199        if !self.storage.role_exists(child)? {
200            return Err(Error::RoleNotFound(child.to_string()));
201        }
202        if !self.storage.role_exists(parent)? {
203            return Err(Error::RoleNotFound(parent.to_string()));
204        }
205
206        // Check for circular dependencies
207        if self.would_create_cycle(child, parent)? {
208            return Err(Error::CircularDependency(child.to_string()));
209        }
210
211        // Check if adding this inheritance would exceed the maximum depth
212        if self.would_exceed_max_depth(child, parent)? {
213            return Err(Error::MaxDepthExceeded(self.config.max_hierarchy_depth));
214        }
215
216        self.role_hierarchy
217            .entry(child.to_string())
218            .or_default()
219            .insert(parent.to_string());
220
221        #[cfg(feature = "audit")]
222        info!("Role inheritance added: '{child}' inherits from '{parent}'");
223
224        Ok(())
225    }
226
227    /// Remove role inheritance.
228    pub fn remove_role_inheritance(&mut self, child: &str, parent: &str) -> Result<()> {
229        if let Some(mut parents) = self.role_hierarchy.get_mut(child) {
230            parents.remove(parent);
231            if parents.is_empty() {
232                drop(parents);
233                self.role_hierarchy.remove(child);
234            }
235        }
236
237        #[cfg(feature = "audit")]
238        info!("Role inheritance removed: '{child}' no longer inherits from '{parent}'");
239
240        Ok(())
241    }
242
243    /// Assign a role to a subject.
244    pub fn assign_role(&mut self, subject: &Subject, role_name: &str) -> Result<()> {
245        if !self.storage.role_exists(role_name)? {
246            return Err(Error::RoleNotFound(role_name.to_string()));
247        }
248
249        self.subject_roles
250            .entry(subject.id().to_string())
251            .or_default()
252            .insert(role_name.to_string());
253
254        // Clear permission cache for this subject
255        self.clear_subject_cache(subject.id());
256
257        #[cfg(feature = "audit")]
258        info!("Role '{}' assigned to subject '{}'", role_name, subject.id());
259
260        Ok(())
261    }
262
263    /// Remove a role from a subject.
264    pub fn remove_role(&mut self, subject: &Subject, role_name: &str) -> Result<()> {
265        if let Some(mut roles) = self.subject_roles.get_mut(subject.id()) {
266            roles.remove(role_name);
267            if roles.is_empty() {
268                drop(roles);
269                self.subject_roles.remove(subject.id());
270            }
271        }
272
273        // Clear permission cache for this subject
274        self.clear_subject_cache(subject.id());
275
276        #[cfg(feature = "audit")]
277        info!("Role '{}' removed from subject '{}'", role_name, subject.id());
278
279        Ok(())
280    }
281
282    /// Temporarily elevate a subject's role.
283    pub fn elevate_role(
284        &mut self,
285        subject: &Subject,
286        role_name: &str,
287        duration: Option<Duration>,
288    ) -> Result<()> {
289        if !self.storage.role_exists(role_name)? {
290            return Err(Error::RoleNotFound(role_name.to_string()));
291        }
292
293        let elevation = RoleElevation::new(role_name.to_string(), duration);
294        
295        self.role_elevations
296            .entry(subject.id().to_string())
297            .or_default()
298            .push(elevation);
299
300        // Clear permission cache for this subject
301        self.clear_subject_cache(subject.id());
302
303        #[cfg(feature = "audit")]
304        info!("Role '{}' elevated for subject '{}' with duration {:?}", 
305               role_name, subject.id(), duration);
306
307        Ok(())
308    }
309
310    /// Check if a subject has a specific permission on a resource.
311    pub fn check_permission(
312        &self,
313        subject: &Subject,
314        action: &str,
315        resource: &Resource,
316    ) -> Result<bool> {
317        self.check_permission_with_context(subject, action, resource, &HashMap::new())
318    }
319
320    /// Check permission with additional context.
321    pub fn check_permission_with_context(
322        &self,
323        subject: &Subject,
324        action: &str,
325        resource: &Resource,
326        context: &HashMap<String, String>,
327    ) -> Result<bool> {
328        // Create cache key that includes context hash for conditional permissions
329        let context_hash = if context.is_empty() {
330            String::new()
331        } else {
332            // Create a simple hash of the context for caching
333            let mut sorted_context: Vec<_> = context.iter().collect();
334            sorted_context.sort_by_key(|(k, _)| *k);
335            format!("{sorted_context:?}")
336        };
337        
338        let cache_key = (
339            subject.id().to_string(),
340            action.to_string(),
341            resource.id().to_string(),
342            context_hash,
343        );
344
345        // Check cache first
346        if self.config.enable_caching {
347            if let Some(entry) = self.permission_cache.get(&cache_key) {
348                let (result, expiry) = entry.value();
349                
350                // Check if cache entry is still valid based on TTL
351                let cache_still_valid = expiry.elapsed().as_secs() < self.config.cache_ttl_seconds;
352                
353                // Additionally check if any role elevations have expired since cache entry was created
354                let elevations_still_valid = if let Some(elevations) = self.role_elevations.get(subject.id()) {
355                    let now = Instant::now();
356                    elevations.iter().all(|elevation| {
357                        // If elevation was active when cache was created, it should still be active now
358                        if elevation.created_at() <= *expiry {
359                            !elevation.is_expired(now)
360                        } else {
361                            true // Elevation was created after cache entry, so it doesn't affect cache validity
362                        }
363                    })
364                } else {
365                    true // No elevations, so cache is still valid from elevation perspective
366                };
367                
368                if cache_still_valid && elevations_still_valid {
369                    return Ok(result.is_granted());
370                }
371            }
372        }
373
374        let result = self.check_permission_internal(subject, action, resource, context)?;
375        
376        // Cache the result
377        if self.config.enable_caching {
378            self.permission_cache.insert(
379                cache_key,
380                (result.into(), Instant::now()),
381            );
382        }
383
384        #[cfg(feature = "audit")]
385        {
386            let granted = result;
387            if granted {
388                info!("Permission GRANTED for subject '{}', action '{}', resource '{}'",
389                      subject.id(), action, resource.id());
390            } else {
391                warn!("Permission DENIED for subject '{}', action '{}', resource '{}'",
392                      subject.id(), action, resource.id());
393            }
394        }
395
396        Ok(result)
397    }
398
399    /// Get all roles assigned to a subject (including inherited roles).
400    pub fn get_subject_roles(&self, subject: &Subject) -> Result<HashSet<String>> {
401        let mut all_roles = HashSet::new();
402        
403        // Get directly assigned roles
404        if let Some(direct_roles) = self.subject_roles.get(subject.id()) {
405            for role in direct_roles.iter() {
406                all_roles.insert(role.clone());
407                // Get inherited roles
408                self.collect_inherited_roles(role, &mut all_roles, 0)?;
409            }
410        }
411
412        // Get elevated roles
413        if let Some(elevations) = self.role_elevations.get(subject.id()) {
414            let now = Instant::now();
415            for elevation in elevations.iter() {
416                if !elevation.is_expired(now) {
417                    all_roles.insert(elevation.role_name().to_string());
418                    self.collect_inherited_roles(elevation.role_name(), &mut all_roles, 0)?;
419                }
420            }
421        }
422
423        Ok(all_roles)
424    }
425
426    // Internal implementation
427
428    fn check_permission_internal(
429        &self,
430        subject: &Subject,
431        action: &str,
432        resource: &Resource,
433        context: &HashMap<String, String>,
434    ) -> Result<bool> {
435        let subject_roles = self.get_subject_roles(subject)?;
436        
437        for role_name in subject_roles {
438            if let Some(role) = self.storage.get_role(&role_name)? {
439                if role.has_permission(action, resource.resource_type(), context) {
440                    return Ok(true);
441                }
442            }
443        }
444
445        Ok(false)
446    }
447
448    fn collect_inherited_roles(
449        &self,
450        role_name: &str,
451        collected: &mut HashSet<String>,
452        depth: usize,
453    ) -> Result<()> {
454        if depth >= self.config.max_hierarchy_depth {
455            return Err(Error::MaxDepthExceeded(self.config.max_hierarchy_depth));
456        }
457
458        if let Some(parents) = self.role_hierarchy.get(role_name) {
459            for parent in parents.iter() {
460                if collected.insert(parent.clone()) {
461                    self.collect_inherited_roles(parent, collected, depth + 1)?;
462                }
463            }
464        }
465
466        Ok(())
467    }
468
469    fn would_create_cycle(&self, child: &str, parent: &str) -> Result<bool> {
470        let mut visited = HashSet::new();
471        self.has_path(parent, child, &mut visited, 0)
472    }
473
474    fn would_exceed_max_depth(&self, child: &str, parent: &str) -> Result<bool> {
475        // Calculate the depth from the child downwards (how many levels inherit from child)
476        let child_downward_depth = self.calculate_downward_depth(child)?;
477        // Calculate the depth from the parent upwards (how many levels parent inherits from)
478        let parent_upward_depth = self.calculate_upward_depth(parent)?;
479        
480        // If child inherits from parent, the total depth would be:
481        // parent_upward_depth + 1 (for the new link) + child_downward_depth
482        let total_depth = parent_upward_depth + 1 + child_downward_depth;
483        
484        Ok(total_depth > self.config.max_hierarchy_depth)
485    }
486
487    fn calculate_downward_depth(&self, role_name: &str) -> Result<usize> {
488        let mut max_depth = 0;
489        let mut visited = HashSet::new();
490        self.calculate_downward_depth_recursive(role_name, &mut visited, 0, &mut max_depth)?;
491        Ok(max_depth)
492    }
493
494    fn calculate_downward_depth_recursive(
495        &self,
496        role_name: &str,
497        visited: &mut HashSet<String>,
498        current_depth: usize,
499        max_depth: &mut usize,
500    ) -> Result<()> {
501        if current_depth > self.config.max_hierarchy_depth {
502            return Err(Error::MaxDepthExceeded(self.config.max_hierarchy_depth));
503        }
504
505        if !visited.insert(role_name.to_string()) {
506            return Ok(()); // Already visited
507        }
508
509        *max_depth = std::cmp::max(*max_depth, current_depth);
510
511        // Find all roles that inherit from this role
512        for entry in self.role_hierarchy.iter() {
513            let (child, parents) = (entry.key(), entry.value());
514            if parents.contains(role_name) {
515                self.calculate_downward_depth_recursive(child, visited, current_depth + 1, max_depth)?;
516            }
517        }
518
519        Ok(())
520    }
521
522    fn calculate_upward_depth(&self, role_name: &str) -> Result<usize> {
523        let mut max_depth = 0;
524        let mut visited = HashSet::new();
525        self.calculate_upward_depth_recursive(role_name, &mut visited, 0, &mut max_depth)?;
526        Ok(max_depth)
527    }
528
529    fn calculate_upward_depth_recursive(
530        &self,
531        role_name: &str,
532        visited: &mut HashSet<String>,
533        current_depth: usize,
534        max_depth: &mut usize,
535    ) -> Result<()> {
536        if current_depth > self.config.max_hierarchy_depth {
537            return Err(Error::MaxDepthExceeded(self.config.max_hierarchy_depth));
538        }
539
540        if !visited.insert(role_name.to_string()) {
541            return Ok(()); // Already visited
542        }
543
544        *max_depth = std::cmp::max(*max_depth, current_depth);
545
546        if let Some(parents) = self.role_hierarchy.get(role_name) {
547            for parent in parents.iter() {
548                self.calculate_upward_depth_recursive(parent, visited, current_depth + 1, max_depth)?;
549            }
550        }
551
552        Ok(())
553    }
554
555    fn has_path(
556        &self,
557        from: &str,
558        to: &str,
559        visited: &mut HashSet<String>,
560        depth: usize,
561    ) -> Result<bool> {
562        if depth >= self.config.max_hierarchy_depth {
563            return Err(Error::MaxDepthExceeded(self.config.max_hierarchy_depth));
564        }
565
566        if from == to {
567            return Ok(true);
568        }
569
570        if !visited.insert(from.to_string()) {
571            return Ok(false); // Already visited
572        }
573
574        if let Some(parents) = self.role_hierarchy.get(from) {
575            for parent in parents.iter() {
576                if self.has_path(parent, to, visited, depth + 1)? {
577                    return Ok(true);
578                }
579            }
580        }
581
582        Ok(false)
583    }
584
585    fn clear_subject_cache(&self, subject_id: &str) {
586        if !self.config.enable_caching {
587            return;
588        }
589
590        let keys_to_remove: Vec<_> = self
591            .permission_cache
592            .iter()
593            .filter(|entry| entry.key().0 == subject_id)
594            .map(|entry| entry.key().clone())
595            .collect();
596
597        for key in keys_to_remove {
598            self.permission_cache.remove(&key);
599        }
600    }
601
602    fn clear_role_cache(&self, _role_name: &str) {
603        if !self.config.enable_caching {
604            return;
605        }
606
607        // We need to clear cache for all subjects that have this role
608        // This is a simplified approach - in a real implementation you might
609        // want to track role assignments more efficiently
610        self.permission_cache.clear();
611    }
612}
613
614impl<S> Default for RoleSystem<S>
615where
616    S: Storage + Default,
617{
618    fn default() -> Self {
619        Self::with_storage(S::default(), RoleSystemConfig::default())
620    }
621}