Skip to main content

backbone_auth/
permissions.rs

1//! Permission and role-based access control (RBAC)
2//!
3//! This module provides a **generic** RBAC system using traits.
4//! Modules can implement these traits with their own domain entities.
5//!
6//! ## Generic Design
7//!
8//! Instead of hardcoded entity structs, this module uses traits:
9//! - `PermissionLike` - Any type representing a permission
10//! - `RoleLike` - Any type representing a role with permissions
11//! - `PermissionChecker` - Trait for checking permissions
12//!
13//! ## Usage
14//!
15//! ```rust,ignore
16//! // Module implements traits for its domain entities
17//! impl PermissionLike for MyPermission {
18//!     fn name(&self) -> &str { &self.name }
19//!     fn resource(&self) -> &str { &self.resource }
20//!     fn action(&self) -> &str { &self.action }
21//! }
22//! ```
23
24use anyhow::Result;
25use std::collections::HashMap;
26
27// ============================================================================
28// GENERIC TRAITS - Modules implement these with their domain entities
29// ============================================================================
30
31/// Trait for permission-like types
32///
33/// Implement this trait for your domain Permission entity to use with RBAC.
34pub trait PermissionLike: Clone + Send + Sync {
35    /// Permission name (e.g., "user:read", "admin:all")
36    fn name(&self) -> &str;
37
38    /// Resource this permission applies to (e.g., "user", "*")
39    fn resource(&self) -> &str;
40
41    /// Action allowed (e.g., "read", "write", "*")
42    fn action(&self) -> &str;
43
44    /// Optional description
45    fn description(&self) -> Option<&str> { None }
46}
47
48/// Trait for role-like types
49///
50/// Implement this trait for your domain Role entity.
51pub trait RoleLike: Clone + Send + Sync {
52    /// The permission type this role uses
53    type Permission: PermissionLike;
54
55    /// Role name (e.g., "admin", "user")
56    fn name(&self) -> &str;
57
58    /// Optional description
59    fn description(&self) -> Option<&str> { None }
60
61    /// Permissions assigned to this role
62    fn permissions(&self) -> &[Self::Permission];
63}
64
65/// Trait for checking permissions
66///
67/// Implement this for your permission checking service.
68pub trait PermissionChecker: Send + Sync {
69    /// Check if a user has a specific permission
70    fn has_permission(&self, user_id: &str, permission: &str) -> Result<bool>;
71
72    /// Check if a user has a specific role
73    fn has_role(&self, user_id: &str, role_name: &str) -> Result<bool>;
74
75    /// Get all roles for a user
76    fn get_user_roles(&self, user_id: &str) -> Vec<String>;
77}
78
79// ============================================================================
80// DEFAULT IMPLEMENTATIONS - Simple in-memory RBAC for testing/demos
81// ============================================================================
82
83/// Simple permission for default RBAC implementation
84#[derive(Debug, Clone, PartialEq, Eq, Hash)]
85pub struct SimplePermission {
86    pub name: String,
87    pub resource: String,
88    pub action: String,
89    pub description: Option<String>,
90}
91
92impl PermissionLike for SimplePermission {
93    fn name(&self) -> &str { &self.name }
94    fn resource(&self) -> &str { &self.resource }
95    fn action(&self) -> &str { &self.action }
96    fn description(&self) -> Option<&str> { self.description.as_deref() }
97}
98
99/// Simple role for default RBAC implementation
100#[derive(Debug, Clone)]
101pub struct SimpleRole {
102    pub name: String,
103    pub description: Option<String>,
104    pub permissions: Vec<SimplePermission>,
105}
106
107impl RoleLike for SimpleRole {
108    type Permission = SimplePermission;
109
110    fn name(&self) -> &str { &self.name }
111    fn description(&self) -> Option<&str> { self.description.as_deref() }
112    fn permissions(&self) -> &[SimplePermission] { &self.permissions }
113}
114
115/// In-memory permission service for testing and simple use cases
116///
117/// For production, modules should implement their own PermissionChecker
118/// backed by a database.
119pub struct InMemoryPermissionService<R: RoleLike = SimpleRole> {
120    roles: HashMap<String, R>,
121    user_roles: HashMap<String, Vec<String>>, // user_id -> role_names
122}
123
124impl InMemoryPermissionService<SimpleRole> {
125    /// Create a new in-memory permission service with default roles
126    pub fn new() -> Self {
127        let mut roles = HashMap::new();
128
129        // Add default roles
130        roles.insert("admin".to_string(), SimpleRole {
131            name: "admin".to_string(),
132            description: Some("Administrator with full access".to_string()),
133            permissions: vec![
134                SimplePermission {
135                    name: "admin:all".to_string(),
136                    description: Some("Full administrative access".to_string()),
137                    resource: "*".to_string(),
138                    action: "*".to_string(),
139                },
140            ],
141        });
142
143        roles.insert("user".to_string(), SimpleRole {
144            name: "user".to_string(),
145            description: Some("Regular user with basic permissions".to_string()),
146            permissions: vec![
147                SimplePermission {
148                    name: "user:read".to_string(),
149                    description: Some("Read own user data".to_string()),
150                    resource: "user".to_string(),
151                    action: "read".to_string(),
152                },
153                SimplePermission {
154                    name: "user:write".to_string(),
155                    description: Some("Update own user data".to_string()),
156                    resource: "user".to_string(),
157                    action: "write".to_string(),
158                },
159            ],
160        });
161
162        Self {
163            roles,
164            user_roles: HashMap::new(),
165        }
166    }
167}
168
169impl<R: RoleLike> InMemoryPermissionService<R> {
170    /// Create an empty permission service
171    pub fn empty() -> Self {
172        Self {
173            roles: HashMap::new(),
174            user_roles: HashMap::new(),
175        }
176    }
177
178    /// Check if user has permission using generic role type
179    fn check_permission_internal(&self, user_id: &str, permission: &str) -> Result<bool> {
180        let user_role_names = match self.user_roles.get(user_id) {
181            Some(roles) => roles,
182            None => return Ok(false),
183        };
184
185        for role_name in user_role_names {
186            if let Some(role) = self.roles.get(role_name) {
187                for perm in role.permissions() {
188                    // Check exact match
189                    if perm.name() == permission {
190                        return Ok(true);
191                    }
192
193                    // Check wildcard permissions
194                    if perm.resource() == "*" && perm.action() == "*" {
195                        return Ok(true);
196                    }
197
198                    // Check resource:action format
199                    let required_parts: Vec<&str> = permission.split(':').collect();
200                    let perm_parts: Vec<&str> = perm.name().split(':').collect();
201
202                    if required_parts.len() == 2 && perm_parts.len() == 2 {
203                        let resource_match = perm_parts[0] == "*" || perm_parts[0] == required_parts[0];
204                        let action_match = perm_parts[1] == "*" || perm_parts[1] == required_parts[1];
205
206                        if resource_match && action_match {
207                            return Ok(true);
208                        }
209                    }
210                }
211            }
212        }
213
214        Ok(false)
215    }
216
217    /// Get user permissions as SimplePermission (for backwards compatibility)
218    pub fn get_user_permissions(&self, user_id: &str) -> Result<Vec<SimplePermission>> {
219        let mut permissions = Vec::new();
220        let mut permission_names = std::collections::HashSet::new();
221
222        let user_role_names = match self.user_roles.get(user_id) {
223            Some(roles) => roles,
224            None => return Ok(permissions),
225        };
226
227        for role_name in user_role_names {
228            if let Some(role) = self.roles.get(role_name) {
229                for perm in role.permissions() {
230                    if permission_names.insert(perm.name().to_string()) {
231                        permissions.push(SimplePermission {
232                            name: perm.name().to_string(),
233                            resource: perm.resource().to_string(),
234                            action: perm.action().to_string(),
235                            description: perm.description().map(String::from),
236                        });
237                    }
238                }
239            }
240        }
241
242        Ok(permissions)
243    }
244
245    /// Add role
246    pub fn add_role(&mut self, role: R) -> Result<()> {
247        self.roles.insert(role.name().to_string(), role);
248        Ok(())
249    }
250
251    /// Get role
252    pub fn get_role(&self, role_name: &str) -> Option<&R> {
253        self.roles.get(role_name)
254    }
255
256    /// List all roles
257    pub fn list_roles(&self) -> Vec<&R> {
258        self.roles.values().collect()
259    }
260
261    /// Assign role to user
262    pub fn assign_role(&mut self, user_id: &str, role_name: &str) -> Result<()> {
263        if !self.roles.contains_key(role_name) {
264            return Err(anyhow::anyhow!("Role '{}' does not exist", role_name));
265        }
266
267        let user_roles = self.user_roles.entry(user_id.to_string()).or_default();
268
269        if user_roles.contains(&role_name.to_string()) {
270            return Err(anyhow::anyhow!("User already has role '{}'", role_name));
271        }
272
273        user_roles.push(role_name.to_string());
274        Ok(())
275    }
276
277    /// Remove role from user
278    pub fn remove_role(&mut self, user_id: &str, role_name: &str) -> Result<()> {
279        let user_roles = self.user_roles.get_mut(user_id)
280            .ok_or_else(|| anyhow::anyhow!("User has no roles assigned"))?;
281
282        let original_len = user_roles.len();
283        user_roles.retain(|r| r != role_name);
284
285        if user_roles.len() == original_len {
286            return Err(anyhow::anyhow!("User does not have role '{}'", role_name));
287        }
288
289        if user_roles.is_empty() {
290            self.user_roles.remove(user_id);
291        }
292
293        Ok(())
294    }
295
296    /// Check if user has specific role
297    fn has_role_internal(&self, user_id: &str, role_name: &str) -> bool {
298        self.user_roles.get(user_id)
299            .map(|roles| roles.contains(&role_name.to_string()))
300            .unwrap_or(false)
301    }
302
303    /// Get user roles (internal)
304    fn get_user_roles_internal(&self, user_id: &str) -> Vec<String> {
305        self.user_roles.get(user_id)
306            .cloned()
307            .unwrap_or_default()
308    }
309}
310
311// Implement PermissionChecker trait for InMemoryPermissionService
312impl<R: RoleLike> PermissionChecker for InMemoryPermissionService<R> {
313    fn has_permission(&self, user_id: &str, permission: &str) -> Result<bool> {
314        self.check_permission_internal(user_id, permission)
315    }
316
317    fn has_role(&self, user_id: &str, role_name: &str) -> Result<bool> {
318        Ok(self.has_role_internal(user_id, role_name))
319    }
320
321    fn get_user_roles(&self, user_id: &str) -> Vec<String> {
322        self.get_user_roles_internal(user_id)
323    }
324}
325
326impl Default for InMemoryPermissionService<SimpleRole> {
327    fn default() -> Self {
328        Self::new()
329    }
330}
331
332// ============================================================================
333// BACKWARDS COMPATIBILITY - Type aliases for existing code
334// ============================================================================
335
336/// Type alias for backwards compatibility
337///
338/// DEPRECATED: Use `InMemoryPermissionService` directly or implement
339/// `PermissionChecker` trait for your own service.
340pub type PermissionService = InMemoryPermissionService<SimpleRole>;
341
342/// Type alias for backwards compatibility
343///
344/// DEPRECATED: Use `SimplePermission` or implement `PermissionLike` trait.
345pub type Permission = SimplePermission;
346
347/// Type alias for backwards compatibility
348///
349/// DEPRECATED: Use `SimpleRole` or implement `RoleLike` trait.
350pub type Role = SimpleRole;