Skip to main content

oxigdal_security/access_control/
rbac.rs

1//! Role-Based Access Control (RBAC).
2
3use crate::access_control::{
4    AccessControlEvaluator, AccessDecision, AccessRequest, Action, ResourceType,
5    permissions::Permission, roles::Role,
6};
7use crate::error::{Result, SecurityError};
8use dashmap::DashMap;
9use std::collections::HashSet;
10use std::sync::Arc;
11
12/// RBAC policy engine.
13pub struct RbacEngine {
14    /// Role assignments (subject_id -> role_ids).
15    role_assignments: Arc<DashMap<String, HashSet<String>>>,
16    /// Roles (role_id -> Role).
17    roles: Arc<DashMap<String, Role>>,
18    /// Permissions (permission_id -> Permission).
19    permissions: Arc<DashMap<String, Permission>>,
20    /// Role inheritance (child_role_id -> parent_role_ids).
21    role_inheritance: Arc<DashMap<String, HashSet<String>>>,
22}
23
24impl RbacEngine {
25    /// Create a new RBAC engine.
26    pub fn new() -> Self {
27        Self {
28            role_assignments: Arc::new(DashMap::new()),
29            roles: Arc::new(DashMap::new()),
30            permissions: Arc::new(DashMap::new()),
31            role_inheritance: Arc::new(DashMap::new()),
32        }
33    }
34
35    /// Add a role.
36    pub fn add_role(&self, role: Role) -> Result<()> {
37        self.roles.insert(role.id.clone(), role);
38        Ok(())
39    }
40
41    /// Get a role by ID.
42    pub fn get_role(&self, role_id: &str) -> Option<Role> {
43        self.roles.get(role_id).map(|r| r.clone())
44    }
45
46    /// Remove a role.
47    pub fn remove_role(&self, role_id: &str) -> Result<()> {
48        self.roles.remove(role_id);
49        self.role_inheritance.remove(role_id);
50
51        // Remove role assignments
52        for mut assignment in self.role_assignments.iter_mut() {
53            assignment.value_mut().remove(role_id);
54        }
55
56        Ok(())
57    }
58
59    /// List all roles.
60    pub fn list_roles(&self) -> Vec<Role> {
61        self.roles.iter().map(|r| r.value().clone()).collect()
62    }
63
64    /// Add a permission.
65    pub fn add_permission(&self, permission: Permission) -> Result<()> {
66        self.permissions.insert(permission.id.clone(), permission);
67        Ok(())
68    }
69
70    /// Get a permission by ID.
71    pub fn get_permission(&self, permission_id: &str) -> Option<Permission> {
72        self.permissions.get(permission_id).map(|p| p.clone())
73    }
74
75    /// Assign a role to a subject.
76    pub fn assign_role(&self, subject_id: &str, role_id: &str) -> Result<()> {
77        // Verify role exists
78        if !self.roles.contains_key(role_id) {
79            return Err(SecurityError::role_not_found(role_id));
80        }
81
82        self.role_assignments
83            .entry(subject_id.to_string())
84            .or_default()
85            .insert(role_id.to_string());
86
87        Ok(())
88    }
89
90    /// Revoke a role from a subject.
91    pub fn revoke_role(&self, subject_id: &str, role_id: &str) -> Result<()> {
92        if let Some(mut roles) = self.role_assignments.get_mut(subject_id) {
93            roles.remove(role_id);
94        }
95        Ok(())
96    }
97
98    /// Get roles assigned to a subject.
99    pub fn get_subject_roles(&self, subject_id: &str) -> Vec<String> {
100        self.role_assignments
101            .get(subject_id)
102            .map(|roles| roles.iter().cloned().collect())
103            .unwrap_or_default()
104    }
105
106    /// Set role inheritance (child inherits from parent).
107    pub fn set_role_inheritance(&self, child_role_id: &str, parent_role_id: &str) -> Result<()> {
108        // Verify both roles exist
109        if !self.roles.contains_key(child_role_id) {
110            return Err(SecurityError::role_not_found(child_role_id));
111        }
112        if !self.roles.contains_key(parent_role_id) {
113            return Err(SecurityError::role_not_found(parent_role_id));
114        }
115
116        // Check for circular inheritance
117        if self.would_create_cycle(child_role_id, parent_role_id) {
118            return Err(SecurityError::policy_evaluation(
119                "Circular role inheritance detected",
120            ));
121        }
122
123        self.role_inheritance
124            .entry(child_role_id.to_string())
125            .or_default()
126            .insert(parent_role_id.to_string());
127
128        Ok(())
129    }
130
131    /// Get all roles for a subject including inherited roles.
132    pub fn get_effective_roles(&self, subject_id: &str) -> HashSet<String> {
133        let mut effective_roles = HashSet::new();
134        let direct_roles = self.get_subject_roles(subject_id);
135
136        for role_id in direct_roles {
137            self.collect_inherited_roles(&role_id, &mut effective_roles);
138        }
139
140        effective_roles
141    }
142
143    /// Collect all inherited roles recursively.
144    fn collect_inherited_roles(&self, role_id: &str, collected: &mut HashSet<String>) {
145        if collected.contains(role_id) {
146            return;
147        }
148
149        collected.insert(role_id.to_string());
150
151        if let Some(parents) = self.role_inheritance.get(role_id) {
152            for parent_id in parents.iter() {
153                self.collect_inherited_roles(parent_id, collected);
154            }
155        }
156    }
157
158    /// Check if adding inheritance would create a cycle.
159    fn would_create_cycle(&self, child_id: &str, parent_id: &str) -> bool {
160        let mut visited = HashSet::new();
161        self.has_cycle(parent_id, child_id, &mut visited)
162    }
163
164    /// Check for cycles in role inheritance.
165    fn has_cycle(&self, current: &str, target: &str, visited: &mut HashSet<String>) -> bool {
166        if current == target {
167            return true;
168        }
169
170        if visited.contains(current) {
171            return false;
172        }
173
174        visited.insert(current.to_string());
175
176        if let Some(parents) = self.role_inheritance.get(current) {
177            for parent in parents.iter() {
178                if self.has_cycle(parent, target, visited) {
179                    return true;
180                }
181            }
182        }
183
184        false
185    }
186
187    /// Check if a subject has a specific permission.
188    pub fn has_permission(
189        &self,
190        subject_id: &str,
191        action: Action,
192        resource_type: ResourceType,
193    ) -> bool {
194        let effective_roles = self.get_effective_roles(subject_id);
195
196        for role_id in effective_roles {
197            if let Some(role) = self.roles.get(&role_id) {
198                for permission_id in &role.permissions {
199                    if let Some(permission) = self.permissions.get(permission_id) {
200                        if permission.action == action && permission.resource_type == resource_type
201                        {
202                            return true;
203                        }
204                    }
205                }
206            }
207        }
208
209        false
210    }
211
212    /// Get all permissions for a subject.
213    pub fn get_subject_permissions(&self, subject_id: &str) -> Vec<Permission> {
214        let effective_roles = self.get_effective_roles(subject_id);
215        let mut permissions = Vec::new();
216        let mut seen = HashSet::new();
217
218        for role_id in effective_roles {
219            if let Some(role) = self.roles.get(&role_id) {
220                for permission_id in &role.permissions {
221                    if !seen.contains(permission_id) {
222                        if let Some(permission) = self.permissions.get(permission_id) {
223                            permissions.push(permission.clone());
224                            seen.insert(permission_id.clone());
225                        }
226                    }
227                }
228            }
229        }
230
231        permissions
232    }
233
234    /// Clear all role assignments.
235    pub fn clear_assignments(&self) {
236        self.role_assignments.clear();
237    }
238
239    /// Clear all roles.
240    pub fn clear_roles(&self) {
241        self.roles.clear();
242        self.role_inheritance.clear();
243    }
244
245    /// Clear all permissions.
246    pub fn clear_permissions(&self) {
247        self.permissions.clear();
248    }
249}
250
251impl Default for RbacEngine {
252    fn default() -> Self {
253        Self::new()
254    }
255}
256
257impl AccessControlEvaluator for RbacEngine {
258    fn evaluate(&self, request: &AccessRequest) -> Result<AccessDecision> {
259        let has_permission = self.has_permission(
260            &request.subject.id,
261            request.action,
262            request.resource.resource_type,
263        );
264
265        if has_permission {
266            Ok(AccessDecision::Allow)
267        } else {
268            Ok(AccessDecision::Deny)
269        }
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use crate::access_control::permissions::Permission;
277    use crate::access_control::roles::Role;
278
279    #[test]
280    fn test_role_assignment() {
281        let engine = RbacEngine::new();
282        let role = Role::new("admin".to_string(), "Administrator".to_string());
283
284        engine.add_role(role).expect("Failed to add role");
285        engine
286            .assign_role("user-123", "admin")
287            .expect("Failed to assign role");
288
289        let roles = engine.get_subject_roles("user-123");
290        assert_eq!(roles.len(), 1);
291        assert!(roles.contains(&"admin".to_string()));
292    }
293
294    #[test]
295    fn test_role_revocation() {
296        let engine = RbacEngine::new();
297        let role = Role::new("admin".to_string(), "Administrator".to_string());
298
299        engine.add_role(role).expect("Failed to add role");
300        engine
301            .assign_role("user-123", "admin")
302            .expect("Failed to assign role");
303        engine
304            .revoke_role("user-123", "admin")
305            .expect("Failed to revoke role");
306
307        let roles = engine.get_subject_roles("user-123");
308        assert_eq!(roles.len(), 0);
309    }
310
311    #[test]
312    fn test_role_inheritance() {
313        let engine = RbacEngine::new();
314
315        let admin_role = Role::new("admin".to_string(), "Administrator".to_string());
316        let user_role = Role::new("user".to_string(), "User".to_string());
317
318        engine
319            .add_role(admin_role)
320            .expect("Failed to add admin role");
321        engine.add_role(user_role).expect("Failed to add user role");
322
323        engine
324            .set_role_inheritance("admin", "user")
325            .expect("Failed to set inheritance");
326
327        engine
328            .assign_role("user-123", "admin")
329            .expect("Failed to assign role");
330
331        let effective_roles = engine.get_effective_roles("user-123");
332        assert_eq!(effective_roles.len(), 2);
333        assert!(effective_roles.contains("admin"));
334        assert!(effective_roles.contains("user"));
335    }
336
337    #[test]
338    fn test_circular_inheritance_prevention() {
339        let engine = RbacEngine::new();
340
341        let role_a = Role::new("role-a".to_string(), "Role A".to_string());
342        let role_b = Role::new("role-b".to_string(), "Role B".to_string());
343
344        engine.add_role(role_a).expect("Failed to add role A");
345        engine.add_role(role_b).expect("Failed to add role B");
346
347        engine
348            .set_role_inheritance("role-a", "role-b")
349            .expect("Failed to set inheritance");
350
351        // This should fail due to circular dependency
352        let result = engine.set_role_inheritance("role-b", "role-a");
353        assert!(result.is_err());
354    }
355
356    #[test]
357    fn test_permission_check() {
358        let engine = RbacEngine::new();
359
360        let permission = Permission::new(
361            "read-dataset".to_string(),
362            "Read Dataset".to_string(),
363            Action::Read,
364            ResourceType::Dataset,
365        );
366
367        let mut role = Role::new("viewer".to_string(), "Viewer".to_string());
368        role.add_permission("read-dataset".to_string());
369
370        engine
371            .add_permission(permission)
372            .expect("Failed to add permission");
373        engine.add_role(role).expect("Failed to add role");
374        engine
375            .assign_role("user-123", "viewer")
376            .expect("Failed to assign role");
377
378        assert!(engine.has_permission("user-123", Action::Read, ResourceType::Dataset));
379        assert!(!engine.has_permission("user-123", Action::Write, ResourceType::Dataset));
380    }
381}