1#[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#[derive(Debug, Clone, PartialEq)]
43pub enum AccessResult {
44 Granted,
46 Denied(String),
48}
49
50impl AccessResult {
51 pub fn is_granted(&self) -> bool {
53 matches!(self, AccessResult::Granted)
54 }
55
56 pub fn is_denied(&self) -> bool {
58 !self.is_granted()
59 }
60
61 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#[derive(Debug, Clone)]
82pub struct RoleSystemConfig {
83 pub max_hierarchy_depth: usize,
85 pub enable_caching: bool,
87 pub cache_ttl_seconds: u64,
89 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, enable_audit: true,
100 }
101 }
102}
103
104pub struct RoleSystem<S = MemoryStorage>
106where
107 S: Storage,
108{
109 storage: S,
110 config: RoleSystemConfig,
111 role_hierarchy: DashMap<String, HashSet<String>>,
113 subject_roles: DashMap<String, HashSet<String>>,
115 role_elevations: DashMap<String, Vec<RoleElevation>>,
117 permission_cache: DashMap<(String, String, String, String), (AccessResult, Instant)>,
119}
120
121impl RoleSystem<MemoryStorage> {
122 pub fn new() -> Self {
124 Self::with_config(RoleSystemConfig::default())
125 }
126
127 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 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 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 pub fn get_role(&self, name: &str) -> Result<Option<Role>> {
174 self.storage.get_role(name)
175 }
176
177 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 self.clear_role_cache(&role_name);
189
190 #[cfg(feature = "audit")]
191 info!("Role '{role_name}' updated");
192
193 Ok(())
194 }
195
196 pub fn add_role_inheritance(&mut self, child: &str, parent: &str) -> Result<()> {
198 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 if self.would_create_cycle(child, parent)? {
208 return Err(Error::CircularDependency(child.to_string()));
209 }
210
211 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 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 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 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 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 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 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 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 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 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 let context_hash = if context.is_empty() {
330 String::new()
331 } else {
332 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 if self.config.enable_caching {
347 if let Some(entry) = self.permission_cache.get(&cache_key) {
348 let (result, expiry) = entry.value();
349
350 let cache_still_valid = expiry.elapsed().as_secs() < self.config.cache_ttl_seconds;
352
353 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.created_at() <= *expiry {
359 !elevation.is_expired(now)
360 } else {
361 true }
363 })
364 } else {
365 true };
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 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 pub fn get_subject_roles(&self, subject: &Subject) -> Result<HashSet<String>> {
401 let mut all_roles = HashSet::new();
402
403 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 self.collect_inherited_roles(role, &mut all_roles, 0)?;
409 }
410 }
411
412 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 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 let child_downward_depth = self.calculate_downward_depth(child)?;
477 let parent_upward_depth = self.calculate_upward_depth(parent)?;
479
480 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(()); }
508
509 *max_depth = std::cmp::max(*max_depth, current_depth);
510
511 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(()); }
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); }
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 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}