llm_registry_api/
rbac.rs

1//! Role-Based Access Control (RBAC)
2//!
3//! This module provides a comprehensive RBAC system with roles, permissions,
4//! and policy-based access control.
5
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8use std::fmt;
9
10/// Permission representing a specific action on a resource
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct Permission {
13    /// Resource type (e.g., "asset", "user", "api-key")
14    pub resource: String,
15
16    /// Action (e.g., "read", "write", "delete", "admin")
17    pub action: String,
18}
19
20impl Permission {
21    /// Create a new permission
22    pub fn new(resource: impl Into<String>, action: impl Into<String>) -> Self {
23        Self {
24            resource: resource.into(),
25            action: action.into(),
26        }
27    }
28
29    /// Check if this permission matches another (supports wildcards)
30    pub fn matches(&self, other: &Permission) -> bool {
31        let resource_match = self.resource == "*" || self.resource == other.resource;
32        let action_match = self.action == "*" || self.action == other.action;
33        resource_match && action_match
34    }
35
36    /// Create from string format "resource:action"
37    pub fn from_string(s: &str) -> Result<Self, RbacError> {
38        let parts: Vec<&str> = s.split(':').collect();
39        if parts.len() != 2 {
40            return Err(RbacError::InvalidPermissionFormat(s.to_string()));
41        }
42        Ok(Permission::new(parts[0], parts[1]))
43    }
44
45    /// Convert to string format "resource:action"
46    pub fn to_string(&self) -> String {
47        format!("{}:{}", self.resource, self.action)
48    }
49}
50
51impl fmt::Display for Permission {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(f, "{}:{}", self.resource, self.action)
54    }
55}
56
57/// Role with associated permissions
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct Role {
60    /// Role name
61    pub name: String,
62
63    /// Role description
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub description: Option<String>,
66
67    /// Permissions granted to this role
68    pub permissions: HashSet<Permission>,
69
70    /// Parent roles (for role hierarchy)
71    #[serde(default, skip_serializing_if = "Vec::is_empty")]
72    pub inherits_from: Vec<String>,
73}
74
75impl Role {
76    /// Create a new role
77    pub fn new(name: impl Into<String>) -> Self {
78        Self {
79            name: name.into(),
80            description: None,
81            permissions: HashSet::new(),
82            inherits_from: Vec::new(),
83        }
84    }
85
86    /// Add a permission to this role
87    pub fn add_permission(&mut self, permission: Permission) {
88        self.permissions.insert(permission);
89    }
90
91    /// Add multiple permissions
92    pub fn add_permissions(&mut self, permissions: Vec<Permission>) {
93        self.permissions.extend(permissions);
94    }
95
96    /// Add parent role for inheritance
97    pub fn add_parent(&mut self, parent_role: impl Into<String>) {
98        self.inherits_from.push(parent_role.into());
99    }
100
101    /// Set description
102    pub fn with_description(mut self, description: impl Into<String>) -> Self {
103        self.description = Some(description.into());
104        self
105    }
106
107    /// Check if role has a specific permission
108    pub fn has_permission(&self, permission: &Permission) -> bool {
109        self.permissions.iter().any(|p| p.matches(permission))
110    }
111}
112
113/// RBAC policy manager
114#[derive(Debug, Clone)]
115pub struct RbacPolicy {
116    /// Map of role name to role definition
117    roles: HashMap<String, Role>,
118
119    /// Cached permission sets for roles (including inherited)
120    permission_cache: HashMap<String, HashSet<Permission>>,
121}
122
123impl Default for RbacPolicy {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl RbacPolicy {
130    /// Create a new RBAC policy
131    pub fn new() -> Self {
132        let mut policy = Self {
133            roles: HashMap::new(),
134            permission_cache: HashMap::new(),
135        };
136
137        // Add default roles
138        policy.add_default_roles();
139        policy
140    }
141
142    /// Add default system roles
143    fn add_default_roles(&mut self) {
144        // Super admin role with all permissions
145        let mut admin = Role::new("admin");
146        admin.description = Some("System administrator with full access".to_string());
147        admin.add_permission(Permission::new("*", "*"));
148        self.add_role(admin);
149
150        // Developer role
151        let mut developer = Role::new("developer");
152        developer.description = Some("Developer with asset management and API access".to_string());
153        developer.add_permissions(vec![
154            Permission::new("asset", "read"),
155            Permission::new("asset", "write"),
156            Permission::new("asset", "delete"),
157            Permission::new("api-key", "create"),
158            Permission::new("api-key", "read"),
159        ]);
160        self.add_role(developer);
161
162        // Viewer role
163        let mut viewer = Role::new("viewer");
164        viewer.description = Some("Read-only access to assets".to_string());
165        viewer.add_permissions(vec![
166            Permission::new("asset", "read"),
167            Permission::new("dependency", "read"),
168        ]);
169        self.add_role(viewer);
170
171        // User role
172        let mut user = Role::new("user");
173        user.description = Some("Regular user with basic permissions".to_string());
174        user.add_permissions(vec![
175            Permission::new("asset", "read"),
176            Permission::new("asset", "write"),
177        ]);
178        self.add_role(user);
179    }
180
181    /// Add a role to the policy
182    pub fn add_role(&mut self, role: Role) {
183        let role_name = role.name.clone();
184        self.roles.insert(role_name.clone(), role);
185        self.invalidate_cache(&role_name);
186    }
187
188    /// Get a role by name
189    pub fn get_role(&self, name: &str) -> Option<&Role> {
190        self.roles.get(name)
191    }
192
193    /// Get all roles
194    pub fn list_roles(&self) -> Vec<&Role> {
195        self.roles.values().collect()
196    }
197
198    /// Remove a role
199    pub fn remove_role(&mut self, name: &str) -> Option<Role> {
200        self.invalidate_cache(name);
201        self.roles.remove(name)
202    }
203
204    /// Invalidate permission cache for a role
205    fn invalidate_cache(&mut self, role_name: &str) {
206        self.permission_cache.remove(role_name);
207        // Also invalidate cache for roles that inherit from this role
208        let dependent_roles: Vec<String> = self
209            .roles
210            .iter()
211            .filter(|(_, role)| role.inherits_from.contains(&role_name.to_string()))
212            .map(|(name, _)| name.clone())
213            .collect();
214
215        for role in dependent_roles {
216            self.permission_cache.remove(&role);
217        }
218    }
219
220    /// Get all permissions for a role (including inherited)
221    pub fn get_role_permissions(&mut self, role_name: &str) -> Option<HashSet<Permission>> {
222        // Check cache first
223        if let Some(cached) = self.permission_cache.get(role_name) {
224            return Some(cached.clone());
225        }
226
227        // Get role and collect parent role names
228        let (direct_permissions, parent_roles) = {
229            let role = self.roles.get(role_name)?;
230            (role.permissions.clone(), role.inherits_from.clone())
231        };
232
233        // Start with direct permissions
234        let mut permissions = direct_permissions;
235
236        // Add inherited permissions
237        for parent_role_name in &parent_roles {
238            if let Some(parent_permissions) = self.get_role_permissions(parent_role_name) {
239                permissions.extend(parent_permissions);
240            }
241        }
242
243        // Cache the result
244        self.permission_cache
245            .insert(role_name.to_string(), permissions.clone());
246
247        Some(permissions)
248    }
249
250    /// Check if a set of roles has a specific permission
251    pub fn has_permission(&mut self, roles: &[String], permission: &Permission) -> bool {
252        for role_name in roles {
253            if let Some(role_permissions) = self.get_role_permissions(role_name) {
254                if role_permissions.iter().any(|p| p.matches(permission)) {
255                    return true;
256                }
257            }
258        }
259        false
260    }
261
262    /// Check if a set of roles has ANY of the specified permissions
263    pub fn has_any_permission(
264        &mut self,
265        roles: &[String],
266        permissions: &[Permission],
267    ) -> bool {
268        permissions
269            .iter()
270            .any(|p| self.has_permission(roles, p))
271    }
272
273    /// Check if a set of roles has ALL of the specified permissions
274    pub fn has_all_permissions(
275        &mut self,
276        roles: &[String],
277        permissions: &[Permission],
278    ) -> bool {
279        permissions
280            .iter()
281            .all(|p| self.has_permission(roles, p))
282    }
283}
284
285/// RBAC errors
286#[derive(Debug, thiserror::Error)]
287pub enum RbacError {
288    #[error("Invalid permission format: {0}. Expected format: resource:action")]
289    InvalidPermissionFormat(String),
290
291    #[error("Role not found: {0}")]
292    RoleNotFound(String),
293
294    #[error("Permission denied: {0}")]
295    PermissionDenied(String),
296
297    #[error("Circular role inheritance detected")]
298    CircularInheritance,
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    #[test]
306    fn test_permission_creation() {
307        let perm = Permission::new("asset", "read");
308        assert_eq!(perm.resource, "asset");
309        assert_eq!(perm.action, "read");
310        assert_eq!(perm.to_string(), "asset:read");
311    }
312
313    #[test]
314    fn test_permission_from_string() {
315        let perm = Permission::from_string("asset:write").unwrap();
316        assert_eq!(perm.resource, "asset");
317        assert_eq!(perm.action, "write");
318
319        assert!(Permission::from_string("invalid").is_err());
320    }
321
322    #[test]
323    fn test_permission_wildcard_matching() {
324        let perm1 = Permission::new("*", "*");
325        let perm2 = Permission::new("asset", "read");
326
327        assert!(perm1.matches(&perm2));
328        assert!(!perm2.matches(&perm1));
329    }
330
331    #[test]
332    fn test_role_creation() {
333        let mut role = Role::new("developer");
334        role.add_permission(Permission::new("asset", "read"));
335        role.add_permission(Permission::new("asset", "write"));
336
337        assert_eq!(role.name, "developer");
338        assert_eq!(role.permissions.len(), 2);
339    }
340
341    #[test]
342    fn test_rbac_policy() {
343        let mut policy = RbacPolicy::new();
344
345        // Test default roles
346        assert!(policy.get_role("admin").is_some());
347        assert!(policy.get_role("developer").is_some());
348        assert!(policy.get_role("viewer").is_some());
349    }
350
351    #[test]
352    fn test_permission_checking() {
353        let mut policy = RbacPolicy::new();
354
355        let admin_roles = vec!["admin".to_string()];
356        let viewer_roles = vec!["viewer".to_string()];
357
358        let read_perm = Permission::new("asset", "read");
359        let delete_perm = Permission::new("asset", "delete");
360
361        // Admin should have all permissions
362        assert!(policy.has_permission(&admin_roles, &read_perm));
363        assert!(policy.has_permission(&admin_roles, &delete_perm));
364
365        // Viewer should only have read permission
366        assert!(policy.has_permission(&viewer_roles, &read_perm));
367        assert!(!policy.has_permission(&viewer_roles, &delete_perm));
368    }
369
370    #[test]
371    fn test_role_inheritance() {
372        let mut policy = RbacPolicy::new();
373
374        // Create a moderator role that inherits from viewer
375        let mut moderator = Role::new("moderator");
376        moderator.add_parent("viewer");
377        moderator.add_permission(Permission::new("asset", "delete"));
378        policy.add_role(moderator);
379
380        let moderator_roles = vec!["moderator".to_string()];
381
382        // Should have permissions from both moderator and viewer
383        assert!(policy.has_permission(
384            &moderator_roles,
385            &Permission::new("asset", "read")
386        ));
387        assert!(policy.has_permission(
388            &moderator_roles,
389            &Permission::new("asset", "delete")
390        ));
391    }
392
393    #[test]
394    fn test_has_any_permission() {
395        let mut policy = RbacPolicy::new();
396
397        let developer_roles = vec!["developer".to_string()];
398        let permissions = vec![
399            Permission::new("asset", "delete"),
400            Permission::new("user", "admin"),
401        ];
402
403        // Developer should have at least one of these permissions
404        assert!(policy.has_any_permission(&developer_roles, &permissions));
405    }
406
407    #[test]
408    fn test_has_all_permissions() {
409        let mut policy = RbacPolicy::new();
410
411        let admin_roles = vec!["admin".to_string()];
412        let permissions = vec![
413            Permission::new("asset", "read"),
414            Permission::new("asset", "write"),
415            Permission::new("asset", "delete"),
416        ];
417
418        // Admin should have all permissions
419        assert!(policy.has_all_permissions(&admin_roles, &permissions));
420    }
421
422    #[test]
423    fn test_cache_invalidation() {
424        let mut policy = RbacPolicy::new();
425
426        // Get permissions to populate cache
427        let _ = policy.get_role_permissions("developer");
428        assert!(policy.permission_cache.contains_key("developer"));
429
430        // Modify role - should invalidate cache
431        if let Some(role) = policy.roles.get_mut("developer") {
432            role.add_permission(Permission::new("new", "permission"));
433        }
434        policy.invalidate_cache("developer");
435
436        assert!(!policy.permission_cache.contains_key("developer"));
437    }
438}