auth_framework/
permissions.rs

1//! Permission and role-based access control system.
2
3use crate::errors::{PermissionError, Result};
4use crate::tokens::AuthToken;
5use serde::{Deserialize, Serialize};
6use std::collections::{HashMap, HashSet};
7
8/// Represents a permission with action and resource.
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct Permission {
11    /// The action being performed (e.g., "read", "write", "delete")
12    pub action: String,
13    
14    /// The resource being accessed (e.g., "documents", "users", "settings")
15    pub resource: String,
16    
17    /// Optional resource instance (e.g., specific document ID)
18    pub instance: Option<String>,
19}
20
21/// Represents a role with associated permissions.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Role {
24    /// Role name
25    pub name: String,
26    
27    /// Role description
28    pub description: Option<String>,
29    
30    /// Permissions granted to this role
31    pub permissions: HashSet<Permission>,
32    
33    /// Parent roles this role inherits from
34    pub parent_roles: HashSet<String>,
35    
36    /// Whether this role is active
37    pub active: bool,
38}
39
40/// User permissions and roles.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct UserPermissions {
43    /// User ID
44    pub user_id: String,
45    
46    /// Direct permissions granted to the user
47    pub direct_permissions: HashSet<Permission>,
48    
49    /// Roles assigned to the user
50    pub roles: HashSet<String>,
51    
52    /// Cached computed permissions (includes role permissions)
53    pub computed_permissions: Option<HashSet<Permission>>,
54    
55    /// When the permissions were last updated
56    pub last_updated: chrono::DateTime<chrono::Utc>,
57}
58
59/// Permission checker for validating access rights.
60#[derive(Debug, Clone)]
61pub struct PermissionChecker {
62    /// All defined roles
63    roles: HashMap<String, Role>,
64    
65    /// User permissions cache
66    user_permissions: HashMap<String, UserPermissions>,
67    
68    /// Permission hierarchy (for resource hierarchies)
69    #[allow(dead_code)]
70    resource_hierarchy: HashMap<String, Vec<String>>,
71}
72
73impl Permission {
74    /// Create a new permission.
75    pub fn new(action: impl Into<String>, resource: impl Into<String>) -> Self {
76        Self {
77            action: action.into(),
78            resource: resource.into(),
79            instance: None,
80        }
81    }
82
83    /// Create a new permission with a specific instance.
84    pub fn with_instance(
85        action: impl Into<String>,
86        resource: impl Into<String>,
87        instance: impl Into<String>,
88    ) -> Self {
89        Self {
90            action: action.into(),
91            resource: resource.into(),
92            instance: Some(instance.into()),
93        }
94    }
95
96    /// Parse a permission from a string format "action:resource" or "action:resource:instance".
97    pub fn parse(permission_str: &str) -> Result<Self> {
98        let parts: Vec<&str> = permission_str.split(':').collect();
99        
100        match parts.len() {
101            2 => Ok(Self::new(parts[0], parts[1])),
102            3 => Ok(Self::with_instance(parts[0], parts[1], parts[2])),
103            _ => Err(PermissionError::invalid_format(
104                format!("Invalid permission format: {permission_str}")
105            ).into()),
106        }
107    }
108
109}
110
111impl std::fmt::Display for Permission {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        match &self.instance {
114            Some(instance) => write!(f, "{}:{}:{}", self.action, self.resource, instance),
115            None => write!(f, "{}:{}", self.action, self.resource),
116        }
117    }
118}
119
120impl Permission {
121    /// Check if this permission matches another permission (considering wildcards).
122    pub fn matches(&self, other: &Permission) -> bool {
123        // Check action
124        if self.action != "*" && other.action != "*" && self.action != other.action {
125            return false;
126        }
127
128        // Check resource
129        if self.resource != "*" && other.resource != "*" && self.resource != other.resource {
130            return false;
131        }
132
133        // Check instance
134        match (&self.instance, &other.instance) {
135            (Some(self_instance), Some(other_instance)) => {
136                self_instance == "*" || other_instance == "*" || self_instance == other_instance
137            }
138            (None, None) => true,
139            (Some(_), None) => false, // Specific instance doesn't match general permission
140            (None, Some(_)) => true,  // General permission matches specific instance
141        }
142    }
143
144    /// Check if this permission implies another permission.
145    pub fn implies(&self, other: &Permission) -> bool {
146        // A permission implies another if it's more general or equal
147        let action_implies = self.action == "*" || self.action == other.action;
148        let resource_implies = self.resource == "*" || self.resource == other.resource;
149        let instance_implies = match (&self.instance, &other.instance) {
150            (None, _) => true,          // General permission implies specific
151            (Some(self_instance), Some(other_instance)) => {
152                self_instance == "*" || self_instance == other_instance
153            }
154            (Some(_), None) => false,   // Specific doesn't imply general
155        };
156
157        action_implies && resource_implies && instance_implies
158    }
159}
160
161impl Role {
162    /// Create a new role.
163    pub fn new(name: impl Into<String>) -> Self {
164        Self {
165            name: name.into(),
166            description: None,
167            permissions: HashSet::new(),
168            parent_roles: HashSet::new(),
169            active: true,
170        }
171    }
172
173    /// Set the role description.
174    pub fn with_description(mut self, description: impl Into<String>) -> Self {
175        self.description = Some(description.into());
176        self
177    }
178
179    /// Add a permission to the role.
180    pub fn add_permission(&mut self, permission: Permission) {
181        self.permissions.insert(permission);
182    }
183
184    /// Remove a permission from the role.
185    pub fn remove_permission(&mut self, permission: &Permission) {
186        self.permissions.remove(permission);
187    }
188
189    /// Add a parent role.
190    pub fn add_parent_role(&mut self, parent_role: impl Into<String>) {
191        self.parent_roles.insert(parent_role.into());
192    }
193
194    /// Check if the role has a specific permission.
195    pub fn has_permission(&self, permission: &Permission) -> bool {
196        self.permissions.iter().any(|p| p.implies(permission))
197    }
198
199    /// Get all permissions including inherited ones.
200    pub fn get_all_permissions(&self, role_resolver: &dyn Fn(&str) -> Option<Role>) -> HashSet<Permission> {
201        let mut all_permissions = self.permissions.clone();
202        
203        // Add permissions from parent roles
204        for parent_role_name in &self.parent_roles {
205            if let Some(parent_role) = role_resolver(parent_role_name) {
206                all_permissions.extend(parent_role.get_all_permissions(role_resolver));
207            }
208        }
209        
210        all_permissions
211    }
212
213    /// Activate or deactivate the role.
214    pub fn set_active(&mut self, active: bool) {
215        self.active = active;
216    }
217}
218
219impl UserPermissions {
220    /// Create new user permissions.
221    pub fn new(user_id: impl Into<String>) -> Self {
222        Self {
223            user_id: user_id.into(),
224            direct_permissions: HashSet::new(),
225            roles: HashSet::new(),
226            computed_permissions: None,
227            last_updated: chrono::Utc::now(),
228        }
229    }
230
231    /// Add a direct permission to the user.
232    pub fn add_permission(&mut self, permission: Permission) {
233        self.direct_permissions.insert(permission);
234        self.computed_permissions = None; // Invalidate cache
235        self.last_updated = chrono::Utc::now();
236    }
237
238    /// Remove a direct permission from the user.
239    pub fn remove_permission(&mut self, permission: &Permission) {
240        self.direct_permissions.remove(permission);
241        self.computed_permissions = None; // Invalidate cache
242        self.last_updated = chrono::Utc::now();
243    }
244
245    /// Add a role to the user.
246    pub fn add_role(&mut self, role: impl Into<String>) {
247        self.roles.insert(role.into());
248        self.computed_permissions = None; // Invalidate cache
249        self.last_updated = chrono::Utc::now();
250    }
251
252    /// Remove a role from the user.
253    pub fn remove_role(&mut self, role: &str) {
254        self.roles.remove(role);
255        self.computed_permissions = None; // Invalidate cache
256        self.last_updated = chrono::Utc::now();
257    }
258
259    /// Compute all permissions for the user (including role permissions).
260    pub fn compute_permissions(&mut self, role_resolver: &dyn Fn(&str) -> Option<Role>) -> &HashSet<Permission> {
261        if self.computed_permissions.is_none() {
262            let mut all_permissions = self.direct_permissions.clone();
263            
264            // Add permissions from roles
265            for role_name in &self.roles {
266                if let Some(role) = role_resolver(role_name) {
267                    if role.active {
268                        all_permissions.extend(role.get_all_permissions(role_resolver));
269                    }
270                }
271            }
272            
273            self.computed_permissions = Some(all_permissions);
274        }
275        
276        self.computed_permissions.as_ref().unwrap()
277    }
278
279    /// Check if the user has a specific permission.
280    pub fn has_permission(&mut self, permission: &Permission, role_resolver: &dyn Fn(&str) -> Option<Role>) -> bool {
281        let all_permissions = self.compute_permissions(role_resolver);
282        all_permissions.iter().any(|p| p.implies(permission))
283    }
284}
285
286impl PermissionChecker {
287    /// Create a new permission checker.
288    pub fn new() -> Self {
289        Self {
290            roles: HashMap::new(),
291            user_permissions: HashMap::new(),
292            resource_hierarchy: HashMap::new(),
293        }
294    }
295
296    /// Add a role definition.
297    pub fn add_role(&mut self, role: Role) {
298        self.roles.insert(role.name.clone(), role);
299    }
300
301    /// Remove a role definition.
302    pub fn remove_role(&mut self, role_name: &str) {
303        self.roles.remove(role_name);
304    }
305
306    /// Get a role by name.
307    pub fn get_role(&self, role_name: &str) -> Option<&Role> {
308        self.roles.get(role_name)
309    }
310
311    /// Set user permissions.
312    pub fn set_user_permissions(&mut self, user_permissions: UserPermissions) {
313        self.user_permissions.insert(user_permissions.user_id.clone(), user_permissions);
314    }
315
316    /// Get user permissions.
317    pub fn get_user_permissions(&self, user_id: &str) -> Option<&UserPermissions> {
318        self.user_permissions.get(user_id)
319    }
320
321    /// Get mutable user permissions.
322    pub fn get_user_permissions_mut(&mut self, user_id: &str) -> Option<&mut UserPermissions> {
323        self.user_permissions.get_mut(user_id)
324    }
325
326    /// Add a permission to a user.
327    pub fn add_user_permission(&mut self, user_id: &str, permission: Permission) {
328        let user_perms = self.user_permissions
329            .entry(user_id.to_string())
330            .or_insert_with(|| UserPermissions::new(user_id));
331        
332        user_perms.add_permission(permission);
333    }
334
335    /// Add a role to a user.
336    pub fn add_user_role(&mut self, user_id: &str, role: impl Into<String>) {
337        let user_perms = self.user_permissions
338            .entry(user_id.to_string())
339            .or_insert_with(|| UserPermissions::new(user_id));
340        
341        user_perms.add_role(role);
342    }
343
344    /// Check if a user has a specific permission.
345    pub fn check_permission(&mut self, user_id: &str, permission: &Permission) -> Result<bool> {
346        let user_perms = self.user_permissions
347            .get_mut(user_id)
348            .ok_or_else(|| PermissionError::access_denied(
349                permission.to_string(),
350                "unknown user".to_string(),
351            ))?;
352
353        let role_resolver = |role_name: &str| self.roles.get(role_name).cloned();
354        
355        Ok(user_perms.has_permission(permission, &role_resolver))
356    }
357
358    /// Check if a user has permission for a specific action on a resource.
359    pub fn check_access(&mut self, user_id: &str, action: &str, resource: &str) -> Result<bool> {
360        let permission = Permission::new(action, resource);
361        self.check_permission(user_id, &permission)
362    }
363
364    /// Check if a user has permission for a specific action on a resource instance.
365    pub fn check_instance_access(
366        &mut self,
367        user_id: &str,
368        action: &str,
369        resource: &str,
370        instance: &str,
371    ) -> Result<bool> {
372        let permission = Permission::with_instance(action, resource, instance);
373        self.check_permission(user_id, &permission)
374    }
375
376    /// Check permission from an auth token.
377    pub fn check_token_permission(&mut self, token: &AuthToken, permission: &Permission) -> Result<bool> {
378        if !token.is_valid() {
379            return Ok(false);
380        }
381
382        // Check if the token has the required scope
383        let required_scope = permission.to_string();
384        if !token.has_scope(&required_scope) {
385            // Also check for wildcard scopes
386            let wildcard_action = format!("*:{}", permission.resource);
387            let wildcard_resource = format!("{}:*", permission.action);
388            let wildcard_all = "*:*".to_string();
389            
390            if !token.has_scope(&wildcard_action) 
391                && !token.has_scope(&wildcard_resource) 
392                && !token.has_scope(&wildcard_all) {
393                return Ok(false);
394            }
395        }
396
397        // Check user permissions
398        self.check_permission(&token.user_id, permission)
399    }
400
401    /// Create some default roles for common use cases.
402    pub fn create_default_roles(&mut self) {
403        // Admin role with all permissions
404        let mut admin_role = Role::new("admin")
405            .with_description("Administrator with full access");
406        admin_role.add_permission(Permission::new("*", "*"));
407        self.add_role(admin_role);
408
409        // User role with basic permissions
410        let mut user_role = Role::new("user")
411            .with_description("Regular user with basic access");
412        user_role.add_permission(Permission::new("read", "profile"));
413        user_role.add_permission(Permission::new("write", "profile"));
414        user_role.add_permission(Permission::new("read", "public"));
415        self.add_role(user_role);
416
417        // Guest role with read-only access
418        let mut guest_role = Role::new("guest")
419            .with_description("Guest user with read-only access");
420        guest_role.add_permission(Permission::new("read", "public"));
421        self.add_role(guest_role);
422    }
423
424    /// Load permissions from a configuration or database.
425    pub fn load_permissions(&mut self, _config: &str) -> Result<()> {
426        // This would typically load from a configuration file or database
427        // For now, we'll create some default permissions
428        self.create_default_roles();
429        Ok(())
430    }
431}
432
433impl Default for PermissionChecker {
434    fn default() -> Self {
435        Self::new()
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn test_permission_parsing() {
445        let perm = Permission::parse("read:documents").unwrap();
446        assert_eq!(perm.action, "read");
447        assert_eq!(perm.resource, "documents");
448        assert_eq!(perm.instance, None);
449
450        let perm = Permission::parse("write:documents:123").unwrap();
451        assert_eq!(perm.action, "write");
452        assert_eq!(perm.resource, "documents");
453        assert_eq!(perm.instance, Some("123".to_string()));
454    }
455
456    #[test]
457    fn test_permission_matching() {
458        let perm1 = Permission::new("read", "documents");
459        let perm2 = Permission::new("read", "documents");
460        let perm3 = Permission::new("write", "documents");
461        let wildcard = Permission::new("*", "documents");
462
463        assert!(perm1.matches(&perm2));
464        assert!(!perm1.matches(&perm3));
465        assert!(wildcard.matches(&perm1));
466        assert!(wildcard.matches(&perm3));
467    }
468
469    #[test]
470    fn test_permission_implies() {
471        let general = Permission::new("read", "documents");
472        let specific = Permission::with_instance("read", "documents", "123");
473        let wildcard = Permission::new("*", "*");
474
475        assert!(general.implies(&specific));
476        assert!(!specific.implies(&general));
477        assert!(wildcard.implies(&general));
478        assert!(wildcard.implies(&specific));
479    }
480
481    #[test]
482    fn test_role_permissions() {
483        let mut role = Role::new("editor");
484        role.add_permission(Permission::new("read", "documents"));
485        role.add_permission(Permission::new("write", "documents"));
486
487        let read_perm = Permission::new("read", "documents");
488        let delete_perm = Permission::new("delete", "documents");
489
490        assert!(role.has_permission(&read_perm));
491        assert!(!role.has_permission(&delete_perm));
492    }
493
494    #[test]
495    fn test_user_permissions() {
496        let mut user_perms = UserPermissions::new("user123");
497        user_perms.add_permission(Permission::new("read", "profile"));
498        user_perms.add_role("user");
499
500        let role_resolver = |_: &str| Some(Role::new("user"));
501        
502        let read_perm = Permission::new("read", "profile");
503        assert!(user_perms.has_permission(&read_perm, &role_resolver));
504    }
505
506    #[test]
507    fn test_permission_checker() {
508        let mut checker = PermissionChecker::new();
509        checker.create_default_roles();
510
511        checker.add_user_role("user123", "admin");
512        
513        let result = checker.check_access("user123", "read", "documents").unwrap();
514        assert!(result);
515
516        let result = checker.check_access("user123", "delete", "system").unwrap();
517        assert!(result); // Admin has all permissions
518    }
519}