Skip to main content

subcog/security/
rbac.rs

1//! Role-Based Access Control (RBAC) Foundation.
2//!
3//! Provides separation of duties through role-based permissions for SOC2 compliance.
4//!
5//! # Overview
6//!
7//! This module implements the foundation for RBAC with:
8//! - Pre-defined roles with appropriate permission sets
9//! - Fine-grained permissions for all operations
10//! - Permission checking and enforcement
11//! - Audit integration for access control events
12//!
13//! # Roles
14//!
15//! | Role | Description | Key Permissions |
16//! |------|-------------|-----------------|
17//! | `Admin` | Full system access | All permissions |
18//! | `Operator` | Day-to-day operations | Capture, Recall, Sync, Configure |
19//! | `User` | Standard user access | Capture, Recall |
20//! | `Auditor` | Read-only audit access | `ViewAudit`, `GenerateReports` |
21//! | `ReadOnly` | Read-only data access | Recall only |
22//!
23//! # Example
24//!
25//! ```rust
26//! use subcog::security::rbac::{Role, Permission, AccessControl};
27//!
28//! let ac = AccessControl::new();
29//!
30//! // Check if a role has a permission
31//! assert!(ac.has_permission(&Role::Admin, &Permission::Delete));
32//! assert!(!ac.has_permission(&Role::ReadOnly, &Permission::Delete));
33//!
34//! // Get all permissions for a role
35//! let user_perms = ac.permissions_for(&Role::User);
36//! assert!(user_perms.contains(&Permission::Capture));
37//! ```
38
39use serde::{Deserialize, Serialize};
40use std::collections::{HashMap, HashSet};
41
42/// System roles with predefined permission sets.
43///
44/// Roles implement the principle of least privilege, giving each role
45/// only the permissions necessary for its function.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum Role {
49    /// Full system administrator with all permissions.
50    Admin,
51    /// Operations role for day-to-day management.
52    Operator,
53    /// Standard user with basic capture and recall.
54    User,
55    /// Audit role with read-only access to audit logs and reports.
56    Auditor,
57    /// Read-only access to data (no capture or modification).
58    ReadOnly,
59}
60
61impl Role {
62    /// Returns all available roles.
63    #[must_use]
64    pub const fn all() -> &'static [Self] {
65        &[
66            Self::Admin,
67            Self::Operator,
68            Self::User,
69            Self::Auditor,
70            Self::ReadOnly,
71        ]
72    }
73
74    /// Returns the display name for the role.
75    #[must_use]
76    pub const fn display_name(&self) -> &'static str {
77        match self {
78            Self::Admin => "Administrator",
79            Self::Operator => "Operator",
80            Self::User => "User",
81            Self::Auditor => "Auditor",
82            Self::ReadOnly => "Read-Only",
83        }
84    }
85
86    /// Returns a description of the role's purpose.
87    #[must_use]
88    pub const fn description(&self) -> &'static str {
89        match self {
90            Self::Admin => "Full system access with all permissions",
91            Self::Operator => "Day-to-day operations and configuration",
92            Self::User => "Standard user access for capture and recall",
93            Self::Auditor => "Read-only access to audit logs and reports",
94            Self::ReadOnly => "Read-only access to data without modification",
95        }
96    }
97}
98
99/// Fine-grained permissions for system operations.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
101#[serde(rename_all = "snake_case")]
102pub enum Permission {
103    // Memory operations
104    /// Capture new memories.
105    Capture,
106    /// Recall/search memories.
107    Recall,
108    /// Delete memories.
109    Delete,
110    /// Consolidate memories.
111    Consolidate,
112
113    // Sync operations
114    /// Sync memories with remote.
115    Sync,
116    /// Push changes to remote.
117    Push,
118    /// Pull changes from remote.
119    Pull,
120
121    // Configuration
122    /// Modify system configuration.
123    Configure,
124    /// Manage feature flags.
125    ManageFeatures,
126
127    // User management
128    /// Manage user accounts.
129    ManageUsers,
130    /// Assign roles to users.
131    AssignRoles,
132
133    // Audit and compliance
134    /// View audit logs.
135    ViewAudit,
136    /// Generate compliance reports.
137    GenerateReports,
138    /// Export audit data.
139    ExportAudit,
140
141    // Data subject rights (GDPR)
142    /// Export user data (GDPR Article 15).
143    ExportData,
144    /// Delete user data (GDPR Article 17).
145    DeleteUserData,
146    /// Manage consent records.
147    ManageConsent,
148
149    // Prompt management
150    /// Create and save prompts.
151    CreatePrompt,
152    /// Execute prompts.
153    RunPrompt,
154    /// Delete prompts.
155    DeletePrompt,
156
157    // System administration
158    /// Access system health and metrics.
159    ViewHealth,
160    /// Manage encryption keys.
161    ManageEncryption,
162    /// Perform maintenance operations.
163    Maintenance,
164}
165
166impl Permission {
167    /// Returns all available permissions.
168    #[must_use]
169    pub fn all() -> Vec<Self> {
170        vec![
171            Self::Capture,
172            Self::Recall,
173            Self::Delete,
174            Self::Consolidate,
175            Self::Sync,
176            Self::Push,
177            Self::Pull,
178            Self::Configure,
179            Self::ManageFeatures,
180            Self::ManageUsers,
181            Self::AssignRoles,
182            Self::ViewAudit,
183            Self::GenerateReports,
184            Self::ExportAudit,
185            Self::ExportData,
186            Self::DeleteUserData,
187            Self::ManageConsent,
188            Self::CreatePrompt,
189            Self::RunPrompt,
190            Self::DeletePrompt,
191            Self::ViewHealth,
192            Self::ManageEncryption,
193            Self::Maintenance,
194        ]
195    }
196
197    /// Returns the display name for the permission.
198    #[must_use]
199    pub const fn display_name(&self) -> &'static str {
200        match self {
201            Self::Capture => "Capture Memories",
202            Self::Recall => "Recall Memories",
203            Self::Delete => "Delete Memories",
204            Self::Consolidate => "Consolidate Memories",
205            Self::Sync => "Sync",
206            Self::Push => "Push to Remote",
207            Self::Pull => "Pull from Remote",
208            Self::Configure => "Configure System",
209            Self::ManageFeatures => "Manage Features",
210            Self::ManageUsers => "Manage Users",
211            Self::AssignRoles => "Assign Roles",
212            Self::ViewAudit => "View Audit Logs",
213            Self::GenerateReports => "Generate Reports",
214            Self::ExportAudit => "Export Audit Data",
215            Self::ExportData => "Export User Data",
216            Self::DeleteUserData => "Delete User Data",
217            Self::ManageConsent => "Manage Consent",
218            Self::CreatePrompt => "Create Prompts",
219            Self::RunPrompt => "Run Prompts",
220            Self::DeletePrompt => "Delete Prompts",
221            Self::ViewHealth => "View Health",
222            Self::ManageEncryption => "Manage Encryption",
223            Self::Maintenance => "Maintenance",
224        }
225    }
226
227    /// Returns the category of this permission.
228    #[must_use]
229    pub const fn category(&self) -> PermissionCategory {
230        match self {
231            Self::Capture | Self::Recall | Self::Delete | Self::Consolidate => {
232                PermissionCategory::Memory
233            },
234            Self::Sync | Self::Push | Self::Pull => PermissionCategory::Sync,
235            Self::Configure | Self::ManageFeatures => PermissionCategory::Configuration,
236            Self::ManageUsers | Self::AssignRoles => PermissionCategory::UserManagement,
237            Self::ViewAudit | Self::GenerateReports | Self::ExportAudit => {
238                PermissionCategory::Audit
239            },
240            Self::ExportData | Self::DeleteUserData | Self::ManageConsent => {
241                PermissionCategory::DataSubject
242            },
243            Self::CreatePrompt | Self::RunPrompt | Self::DeletePrompt => {
244                PermissionCategory::Prompts
245            },
246            Self::ViewHealth | Self::ManageEncryption | Self::Maintenance => {
247                PermissionCategory::System
248            },
249        }
250    }
251}
252
253/// Categories of permissions for grouping and display.
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
255#[serde(rename_all = "snake_case")]
256pub enum PermissionCategory {
257    /// Memory operations (capture, recall, delete).
258    Memory,
259    /// Sync operations (push, pull).
260    Sync,
261    /// System configuration.
262    Configuration,
263    /// User and role management.
264    UserManagement,
265    /// Audit and compliance.
266    Audit,
267    /// Data subject rights (GDPR).
268    DataSubject,
269    /// Prompt management.
270    Prompts,
271    /// System administration.
272    System,
273}
274
275impl PermissionCategory {
276    /// Returns the display name for the category.
277    #[must_use]
278    pub const fn display_name(&self) -> &'static str {
279        match self {
280            Self::Memory => "Memory Operations",
281            Self::Sync => "Synchronization",
282            Self::Configuration => "Configuration",
283            Self::UserManagement => "User Management",
284            Self::Audit => "Audit & Compliance",
285            Self::DataSubject => "Data Subject Rights",
286            Self::Prompts => "Prompt Management",
287            Self::System => "System Administration",
288        }
289    }
290}
291
292/// Result of an access control check.
293#[derive(Debug, Clone, PartialEq, Eq)]
294pub enum AccessResult {
295    /// Access granted.
296    Granted,
297    /// Access denied with reason.
298    Denied(String),
299}
300
301impl AccessResult {
302    /// Returns true if access was granted.
303    #[must_use]
304    pub const fn is_granted(&self) -> bool {
305        matches!(self, Self::Granted)
306    }
307
308    /// Returns true if access was denied.
309    #[must_use]
310    pub const fn is_denied(&self) -> bool {
311        matches!(self, Self::Denied(_))
312    }
313}
314
315/// Access control manager for checking role permissions.
316#[derive(Debug, Clone)]
317pub struct AccessControl {
318    /// Mapping of roles to their permissions.
319    role_permissions: HashMap<Role, HashSet<Permission>>,
320}
321
322impl Default for AccessControl {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328impl AccessControl {
329    /// Creates a new access control instance with default role-permission mappings.
330    #[must_use]
331    pub fn new() -> Self {
332        let mut role_permissions = HashMap::new();
333
334        // Admin: All permissions
335        role_permissions.insert(Role::Admin, Permission::all().into_iter().collect());
336
337        // Operator: Day-to-day operations
338        role_permissions.insert(
339            Role::Operator,
340            [
341                Permission::Capture,
342                Permission::Recall,
343                Permission::Delete,
344                Permission::Consolidate,
345                Permission::Sync,
346                Permission::Push,
347                Permission::Pull,
348                Permission::Configure,
349                Permission::CreatePrompt,
350                Permission::RunPrompt,
351                Permission::DeletePrompt,
352                Permission::ViewHealth,
353            ]
354            .into_iter()
355            .collect(),
356        );
357
358        // User: Basic capture and recall
359        role_permissions.insert(
360            Role::User,
361            [
362                Permission::Capture,
363                Permission::Recall,
364                Permission::Sync,
365                Permission::CreatePrompt,
366                Permission::RunPrompt,
367            ]
368            .into_iter()
369            .collect(),
370        );
371
372        // Auditor: Audit and reporting only
373        role_permissions.insert(
374            Role::Auditor,
375            [
376                Permission::Recall,
377                Permission::ViewAudit,
378                Permission::GenerateReports,
379                Permission::ExportAudit,
380                Permission::ViewHealth,
381            ]
382            .into_iter()
383            .collect(),
384        );
385
386        // ReadOnly: Just recall
387        role_permissions.insert(
388            Role::ReadOnly,
389            [Permission::Recall, Permission::RunPrompt]
390                .into_iter()
391                .collect(),
392        );
393
394        Self { role_permissions }
395    }
396
397    /// Checks if a role has a specific permission.
398    #[must_use]
399    pub fn has_permission(&self, role: &Role, permission: &Permission) -> bool {
400        self.role_permissions
401            .get(role)
402            .is_some_and(|perms| perms.contains(permission))
403    }
404
405    /// Checks access and returns a detailed result.
406    #[must_use]
407    pub fn check_access(&self, role: &Role, permission: &Permission) -> AccessResult {
408        if self.has_permission(role, permission) {
409            AccessResult::Granted
410        } else {
411            AccessResult::Denied(format!(
412                "Role '{}' does not have permission '{}'",
413                role.display_name(),
414                permission.display_name()
415            ))
416        }
417    }
418
419    /// Returns all permissions for a role.
420    #[must_use]
421    pub fn permissions_for(&self, role: &Role) -> HashSet<Permission> {
422        self.role_permissions.get(role).cloned().unwrap_or_default()
423    }
424
425    /// Returns all roles that have a specific permission.
426    #[must_use]
427    pub fn roles_with_permission(&self, permission: &Permission) -> Vec<Role> {
428        self.role_permissions
429            .iter()
430            .filter(|(_, perms)| perms.contains(permission))
431            .map(|(role, _)| *role)
432            .collect()
433    }
434
435    /// Adds a custom permission to a role.
436    pub fn grant_permission(&mut self, role: &Role, permission: Permission) {
437        self.role_permissions
438            .entry(*role)
439            .or_default()
440            .insert(permission);
441    }
442
443    /// Removes a permission from a role.
444    pub fn revoke_permission(&mut self, role: &Role, permission: &Permission) {
445        if let Some(perms) = self.role_permissions.get_mut(role) {
446            perms.remove(permission);
447        }
448    }
449
450    /// Returns a summary of all role-permission mappings.
451    #[must_use]
452    pub fn summary(&self) -> RbacSummary {
453        let role_summaries: Vec<RoleSummary> = Role::all()
454            .iter()
455            .map(|role| {
456                let permissions = self.permissions_for(role);
457                RoleSummary {
458                    role: *role,
459                    permission_count: permissions.len(),
460                    permissions: permissions.into_iter().collect(),
461                }
462            })
463            .collect();
464
465        RbacSummary {
466            total_roles: Role::all().len(),
467            total_permissions: Permission::all().len(),
468            role_summaries,
469        }
470    }
471}
472
473/// Summary of a role's permissions.
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct RoleSummary {
476    /// The role.
477    pub role: Role,
478    /// Number of permissions.
479    pub permission_count: usize,
480    /// List of permissions.
481    pub permissions: Vec<Permission>,
482}
483
484/// Summary of the entire RBAC configuration.
485#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct RbacSummary {
487    /// Total number of roles.
488    pub total_roles: usize,
489    /// Total number of permissions.
490    pub total_permissions: usize,
491    /// Per-role summaries.
492    pub role_summaries: Vec<RoleSummary>,
493}
494
495#[cfg(test)]
496mod tests {
497    use super::*;
498
499    #[test]
500    fn test_admin_has_all_permissions() {
501        let ac = AccessControl::new();
502        for permission in Permission::all() {
503            assert!(
504                ac.has_permission(&Role::Admin, &permission),
505                "Admin should have {permission:?}"
506            );
507        }
508    }
509
510    #[test]
511    fn test_readonly_limited_permissions() {
512        let ac = AccessControl::new();
513
514        // ReadOnly should have recall
515        assert!(ac.has_permission(&Role::ReadOnly, &Permission::Recall));
516        assert!(ac.has_permission(&Role::ReadOnly, &Permission::RunPrompt));
517
518        // ReadOnly should NOT have these
519        assert!(!ac.has_permission(&Role::ReadOnly, &Permission::Capture));
520        assert!(!ac.has_permission(&Role::ReadOnly, &Permission::Delete));
521        assert!(!ac.has_permission(&Role::ReadOnly, &Permission::Configure));
522    }
523
524    #[test]
525    fn test_auditor_audit_permissions() {
526        let ac = AccessControl::new();
527
528        // Auditor should have audit permissions
529        assert!(ac.has_permission(&Role::Auditor, &Permission::ViewAudit));
530        assert!(ac.has_permission(&Role::Auditor, &Permission::GenerateReports));
531        assert!(ac.has_permission(&Role::Auditor, &Permission::ExportAudit));
532
533        // Auditor should NOT have modification permissions
534        assert!(!ac.has_permission(&Role::Auditor, &Permission::Capture));
535        assert!(!ac.has_permission(&Role::Auditor, &Permission::Delete));
536        assert!(!ac.has_permission(&Role::Auditor, &Permission::Configure));
537    }
538
539    #[test]
540    fn test_user_basic_permissions() {
541        let ac = AccessControl::new();
542
543        // User should have basic permissions
544        assert!(ac.has_permission(&Role::User, &Permission::Capture));
545        assert!(ac.has_permission(&Role::User, &Permission::Recall));
546        assert!(ac.has_permission(&Role::User, &Permission::Sync));
547
548        // User should NOT have admin permissions
549        assert!(!ac.has_permission(&Role::User, &Permission::ManageUsers));
550        assert!(!ac.has_permission(&Role::User, &Permission::Configure));
551        assert!(!ac.has_permission(&Role::User, &Permission::Delete));
552    }
553
554    #[test]
555    fn test_operator_operations_permissions() {
556        let ac = AccessControl::new();
557
558        // Operator should have operational permissions
559        assert!(ac.has_permission(&Role::Operator, &Permission::Capture));
560        assert!(ac.has_permission(&Role::Operator, &Permission::Recall));
561        assert!(ac.has_permission(&Role::Operator, &Permission::Delete));
562        assert!(ac.has_permission(&Role::Operator, &Permission::Configure));
563
564        // Operator should NOT have user management
565        assert!(!ac.has_permission(&Role::Operator, &Permission::ManageUsers));
566        assert!(!ac.has_permission(&Role::Operator, &Permission::AssignRoles));
567    }
568
569    #[test]
570    fn test_check_access_granted() {
571        let ac = AccessControl::new();
572        let result = ac.check_access(&Role::Admin, &Permission::Delete);
573        assert!(result.is_granted());
574    }
575
576    #[test]
577    fn test_check_access_denied() {
578        let ac = AccessControl::new();
579        let result = ac.check_access(&Role::ReadOnly, &Permission::Delete);
580        assert!(result.is_denied());
581        if let AccessResult::Denied(reason) = result {
582            assert!(reason.contains("Read-Only"));
583            assert!(reason.contains("Delete"));
584        }
585    }
586
587    #[test]
588    fn test_permissions_for_role() {
589        let ac = AccessControl::new();
590        let admin_perms = ac.permissions_for(&Role::Admin);
591        assert_eq!(admin_perms.len(), Permission::all().len());
592
593        let readonly_perms = ac.permissions_for(&Role::ReadOnly);
594        assert!(readonly_perms.len() < admin_perms.len());
595    }
596
597    #[test]
598    fn test_roles_with_permission() {
599        let ac = AccessControl::new();
600
601        // All roles except ReadOnly should have Recall
602        let recall_roles = ac.roles_with_permission(&Permission::Recall);
603        assert!(recall_roles.contains(&Role::Admin));
604        assert!(recall_roles.contains(&Role::User));
605        assert!(recall_roles.contains(&Role::Auditor));
606        assert!(recall_roles.contains(&Role::ReadOnly));
607
608        // Only Admin should have ManageUsers
609        let manage_roles = ac.roles_with_permission(&Permission::ManageUsers);
610        assert_eq!(manage_roles.len(), 1);
611        assert!(manage_roles.contains(&Role::Admin));
612    }
613
614    #[test]
615    fn test_grant_permission() {
616        let mut ac = AccessControl::new();
617
618        assert!(!ac.has_permission(&Role::ReadOnly, &Permission::Capture));
619        ac.grant_permission(&Role::ReadOnly, Permission::Capture);
620        assert!(ac.has_permission(&Role::ReadOnly, &Permission::Capture));
621    }
622
623    #[test]
624    fn test_revoke_permission() {
625        let mut ac = AccessControl::new();
626
627        assert!(ac.has_permission(&Role::User, &Permission::Capture));
628        ac.revoke_permission(&Role::User, &Permission::Capture);
629        assert!(!ac.has_permission(&Role::User, &Permission::Capture));
630    }
631
632    #[test]
633    fn test_permission_categories() {
634        assert_eq!(Permission::Capture.category(), PermissionCategory::Memory);
635        assert_eq!(Permission::Sync.category(), PermissionCategory::Sync);
636        assert_eq!(
637            Permission::Configure.category(),
638            PermissionCategory::Configuration
639        );
640        assert_eq!(
641            Permission::ManageUsers.category(),
642            PermissionCategory::UserManagement
643        );
644        assert_eq!(Permission::ViewAudit.category(), PermissionCategory::Audit);
645        assert_eq!(
646            Permission::ExportData.category(),
647            PermissionCategory::DataSubject
648        );
649        assert_eq!(
650            Permission::CreatePrompt.category(),
651            PermissionCategory::Prompts
652        );
653        assert_eq!(
654            Permission::ViewHealth.category(),
655            PermissionCategory::System
656        );
657    }
658
659    #[test]
660    fn test_role_display_names() {
661        assert_eq!(Role::Admin.display_name(), "Administrator");
662        assert_eq!(Role::ReadOnly.display_name(), "Read-Only");
663    }
664
665    #[test]
666    fn test_permission_display_names() {
667        assert_eq!(Permission::Capture.display_name(), "Capture Memories");
668        assert_eq!(Permission::ViewAudit.display_name(), "View Audit Logs");
669    }
670
671    #[test]
672    fn test_rbac_summary() {
673        let ac = AccessControl::new();
674        let summary = ac.summary();
675
676        assert_eq!(summary.total_roles, 5);
677        assert_eq!(summary.total_permissions, Permission::all().len());
678        assert_eq!(summary.role_summaries.len(), 5);
679
680        // Admin should have most permissions
681        let admin_summary = summary
682            .role_summaries
683            .iter()
684            .find(|s| s.role == Role::Admin)
685            .unwrap();
686        assert_eq!(admin_summary.permission_count, Permission::all().len());
687    }
688
689    #[test]
690    fn test_role_serialization() {
691        let role = Role::Admin;
692        let json = serde_json::to_string(&role).unwrap();
693        assert_eq!(json, "\"admin\"");
694
695        let deserialized: Role = serde_json::from_str(&json).unwrap();
696        assert_eq!(deserialized, Role::Admin);
697    }
698
699    #[test]
700    fn test_permission_serialization() {
701        let perm = Permission::Capture;
702        let json = serde_json::to_string(&perm).unwrap();
703        assert_eq!(json, "\"capture\"");
704
705        let deserialized: Permission = serde_json::from_str(&json).unwrap();
706        assert_eq!(deserialized, Permission::Capture);
707    }
708
709    #[test]
710    fn test_access_result_methods() {
711        let granted = AccessResult::Granted;
712        assert!(granted.is_granted());
713        assert!(!granted.is_denied());
714
715        let denied = AccessResult::Denied("test".to_string());
716        assert!(!denied.is_granted());
717        assert!(denied.is_denied());
718    }
719
720    #[test]
721    fn test_separation_of_duties() {
722        let ac = AccessControl::new();
723
724        // Auditor should not be able to modify data they audit
725        assert!(ac.has_permission(&Role::Auditor, &Permission::ViewAudit));
726        assert!(!ac.has_permission(&Role::Auditor, &Permission::Capture));
727        assert!(!ac.has_permission(&Role::Auditor, &Permission::Delete));
728
729        // User should not be able to manage users
730        assert!(!ac.has_permission(&Role::User, &Permission::ManageUsers));
731        assert!(!ac.has_permission(&Role::User, &Permission::AssignRoles));
732
733        // Operator should not manage users (separation from admin)
734        assert!(!ac.has_permission(&Role::Operator, &Permission::ManageUsers));
735    }
736}