Skip to main content

ri/auth/
permissions.rs

1//! Copyright © 2025 Wenze Wei. All Rights Reserved.
2//! 
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//! 
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//! 
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//! 
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! Role-based access control (RBAC) implementation for Ri.
19//! 
20//! This module provides a comprehensive RBAC system for managing permissions,
21//! roles, and user role assignments. It supports:
22//! - Fine-grained permission definitions
23//! - Role management with inheritance support
24//! - User role assignments
25//! - Permission checking for users
26//! - System roles that cannot be deleted
27//! - Wildcard permissions for administrative access
28//! 
29//! # Design Principles
30//! - **Separation of Concerns**: Permissions, roles, and user assignments are managed separately
31//! - **Thread Safety**: Uses RwLock for concurrent access to data structures
32//! - **Flexibility**: Supports both explicit and wildcard permissions
33//! - **Security**: System roles are protected from deletion
34//! - **Performance**: Efficient permission checking with hash sets
35//! 
36//! # Usage Examples
37//! ```rust
38//! // Create a permission manager
39//! let permission_manager = RiPermissionManager::new();
40//! 
41//! // Create a permission
42//! let read_device_perm = RiPermission {
43//!     id: "read:device".to_string(),
44//!     name: "Read Device".to_string(),
45//!     description: "Allows reading device information".to_string(),
46//!     resource: "device".to_string(),
47//!     action: "read".to_string(),
48//! };
49//! permission_manager.create_permission(read_device_perm).await?;
50//! 
51//! // Create a role
52//! let device_admin_role = RiRole::new(
53//!     "device_admin".to_string(),
54//!     "Device Administrator".to_string(),
55//!     "Manages devices".to_string(),
56//!     vec!["read:device", "write:device"].iter().map(|s| s.to_string()).collect()
57//! );
58//! permission_manager.create_role(device_admin_role).await?;
59//! 
60//! // Assign role to user
61//! permission_manager.assign_role_to_user("user123".to_string(), "device_admin".to_string()).await?;
62//! 
63//! // Check if user has permission
64//! let has_perm = permission_manager.has_permission("user123", "read:device").await?;
65//! ```
66
67#![allow(non_snake_case)]
68
69use serde::{Deserialize, Serialize};
70use std::collections::HashSet;
71use crate::core::concurrent::RiShardedLock;
72
73#[cfg(feature = "pyo3")]
74use pyo3::PyResult;
75
76/// Permission definition for fine-grained access control.
77///
78/// This struct defines a permission with a unique ID, name, description,
79/// resource, and action. Permissions follow the "resource:action" convention.
80#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct RiPermission {
83    /// Unique permission identifier following "resource:action" format (e.g., "read:device")
84    pub id: String,
85    /// Human-readable name for the permission
86    pub name: String,
87    /// Detailed description explaining what this permission allows
88    pub description: String,
89    /// Resource being accessed (e.g., "device", "user", "data")
90    pub resource: String,
91    /// Action being performed (e.g., "read", "write", "delete")
92    pub action: String,
93}
94
95#[cfg(feature = "pyo3")]
96#[pyo3::prelude::pymethods]
97impl RiPermission {
98    #[new]
99    fn py_new(
100        id: Option<String>,
101        name: String,
102        description: String,
103        resource: String,
104        action: String,
105    ) -> Self {
106        Self {
107            id: id.unwrap_or_else(|| format!("{}:{}", resource, action)),
108            name,
109            description,
110            resource,
111            action,
112        }
113    }
114}
115
116/// Role definition for grouping permissions.
117///
118/// Roles are collections of permissions that can be assigned to users.
119/// System roles cannot be deleted and are created automatically during initialization.
120#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all, set_all))]
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct RiRole {
123    /// Unique role identifier
124    pub id: String,
125    /// Human-readable name for the role
126    pub name: String,
127    /// Detailed description explaining the role's purpose and access level
128    pub description: String,
129    /// Set of permission IDs assigned to this role
130    pub permissions: HashSet<String>,
131    /// Whether this is a system role that cannot be deleted
132    pub is_system: bool,
133}
134
135#[cfg(feature = "pyo3")]
136#[pyo3::prelude::pymethods]
137impl RiRole {
138    #[new]
139    fn py_new(
140        id: Option<String>,
141        name: String,
142        description: String,
143        permissions: Vec<String>,
144        is_system: bool,
145    ) -> Self {
146        Self {
147            id: id.unwrap_or_else(|| name.to_lowercase().replace(' ', "_")),
148            name,
149            description,
150            permissions: permissions.into_iter().collect(),
151            is_system,
152        }
153    }
154}
155
156impl RiRole {
157    /// Creates a new role with the specified permissions.
158    /// 
159    /// # Parameters
160    /// - `id`: Unique role identifier
161    /// - `name`: Human-readable name
162    /// - `description`: Detailed description
163    /// - `permissions`: Set of permission IDs
164    /// 
165    /// # Returns
166    /// A new instance of `RiRole`
167    pub fn new(id: String, name: String, description: String, permissions: HashSet<String>) -> Self {
168        Self {
169            id,
170            name,
171            description,
172            permissions,
173            is_system: false,
174        }
175    }
176
177    /// Checks if the role has the specified permission.
178    /// 
179    /// # Parameters
180    /// - `permission_id`: Permission ID to check
181    /// 
182    /// # Returns
183    /// `true` if the role has the permission, otherwise `false`
184    #[inline]
185    pub fn has_permission(&self, permission_id: &str) -> bool {
186        self.permissions.contains(permission_id)
187    }
188
189    /// Adds a permission to the role.
190    /// 
191    /// # Parameters
192    /// - `permission_id`: Permission ID to add
193    #[inline]
194    pub fn add_permission(&mut self, permission_id: String) {
195        self.permissions.insert(permission_id);
196    }
197
198    /// Removes a permission from the role.
199    /// 
200    /// # Parameters
201    /// - `permission_id`: Permission ID to remove
202    #[inline]
203    pub fn remove_permission(&mut self, permission_id: &str) {
204        self.permissions.remove(permission_id);
205    }
206}
207
208/// Permission manager for handling permissions, roles, and user assignments.
209///
210/// This struct manages the entire RBAC system, including:
211/// - Permission CRUD operations
212/// - Role CRUD operations
213/// - User role assignments
214/// - Permission checking for users
215#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
216pub struct RiPermissionManager {
217    permissions: RiShardedLock<String, RiPermission>,
218    roles: RiShardedLock<String, RiRole>,
219    user_roles: RiShardedLock<String, HashSet<String>>,
220}
221
222impl Default for RiPermissionManager {
223    fn default() -> Self {
224        Self::new()
225    }
226}
227
228impl RiPermissionManager {
229    /// Creates a new permission manager with default system roles.
230    /// 
231    /// Initializes the manager with two default system roles:
232    /// - `admin`: Has wildcard permission ("*") for all access
233    /// - `user`: Has basic user permissions
234    /// 
235    /// **Performance Note**: This method uses `blocking_write` during initialization
236    /// to set up default roles. For production use, consider using `new_async()` or
237    /// lazy initialization patterns to avoid blocking the async runtime.
238    /// 
239    /// # Returns
240    /// A new instance of `RiPermissionManager`
241    pub fn new() -> Self {
242        let mut manager = Self {
243            permissions: RiShardedLock::with_default_shards(),
244            roles: RiShardedLock::with_default_shards(),
245            user_roles: RiShardedLock::with_default_shards(),
246        };
247        
248        manager.initialize_default_roles();
249        manager
250    }
251
252    /// Creates a new permission manager asynchronously with default system roles.
253    /// 
254    /// This is the preferred method for creating a permission manager in async contexts
255    /// as it avoids blocking the runtime during initialization.
256    /// 
257    /// Initializes the manager with two default system roles:
258    /// - `admin`: Has wildcard permission ("*") for all access
259    /// - `user`: Has basic user permissions
260    /// 
261    /// # Returns
262    /// A new instance of `RiPermissionManager`
263    pub async fn new_async() -> Self {
264        let manager = Self {
265            permissions: RiShardedLock::with_default_shards(),
266            roles: RiShardedLock::with_default_shards(),
267            user_roles: RiShardedLock::with_default_shards(),
268        };
269        
270        manager.initialize_default_roles_async().await;
271        manager
272    }
273
274    fn initialize_default_roles(&mut self) {
275        let rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
276        rt.block_on(async {
277            self.initialize_default_roles_async().await
278        });
279    }
280
281    async fn initialize_default_roles_async(&self) {
282        let admin_permissions: HashSet<String> = vec![
283            "*".to_string(),
284        ].into_iter().collect();
285        
286        let admin_role = RiRole {
287            id: "admin".to_string(),
288            name: "Administrator".to_string(),
289            description: "Full system access".to_string(),
290            permissions: admin_permissions,
291            is_system: true,
292        };
293        
294        self.roles.insert("admin".to_string(), admin_role).await;
295        
296        let user_permissions: HashSet<String> = vec![
297            "read:profile".to_string(),
298            "update:profile".to_string(),
299            "read:own_data".to_string(),
300        ].into_iter().collect();
301        
302        let user_role = RiRole {
303            id: "user".to_string(),
304            name: "User".to_string(),
305            description: "Standard user access".to_string(),
306            permissions: user_permissions,
307            is_system: true,
308        };
309        
310        self.roles.insert("user".to_string(), user_role).await;
311    }
312
313    pub async fn create_permission(&self, permission: RiPermission) -> crate::core::RiResult<()> {
314        self.permissions.insert(permission.id.clone(), permission).await;
315        Ok(())
316    }
317
318    pub async fn get_permission(&self, permission_id: &str) -> crate::core::RiResult<Option<RiPermission>> {
319        Ok(self.permissions.get(permission_id).await)
320    }
321
322    pub async fn create_role(&self, role: RiRole) -> crate::core::RiResult<()> {
323        self.roles.insert(role.id.clone(), role).await;
324        Ok(())
325    }
326
327    pub async fn get_role(&self, role_id: &str) -> crate::core::RiResult<Option<RiRole>> {
328        Ok(self.roles.get(role_id).await)
329    }
330
331    pub async fn assign_role_to_user(&self, user_id: String, role_id: String) -> crate::core::RiResult<bool> {
332        let role_exists = self.roles.contains_key(&role_id).await;
333        if !role_exists {
334            return Ok(false);
335        }
336
337        let user_role_set = self.user_roles.get(&user_id).await.unwrap_or_default();
338        let mut new_set = user_role_set.clone();
339        let was_added = new_set.insert(role_id);
340        self.user_roles.insert(user_id, new_set).await;
341        Ok(was_added)
342    }
343
344    pub async fn remove_role_from_user(&self, user_id: &str, role_id: &str) -> crate::core::RiResult<bool> {
345        let user_role_set = self.user_roles.get(user_id).await;
346        
347        match user_role_set {
348            Some(mut set) => {
349                let was_removed = set.remove(role_id);
350                if set.is_empty() {
351                    self.user_roles.remove(user_id).await;
352                } else {
353                    self.user_roles.insert(user_id.to_string(), set).await;
354                }
355                Ok(was_removed)
356            }
357            None => Ok(false),
358        }
359    }
360
361    pub async fn get_user_roles(&self, user_id: &str) -> crate::core::RiResult<Vec<RiRole>> {
362        let user_role_set = self.user_roles.get(user_id).await;
363        
364        match user_role_set {
365            Some(role_ids) => {
366                let mut result = Vec::with_capacity(role_ids.len());
367                for role_id in role_ids {
368                    if let Some(role) = self.roles.get(&role_id).await {
369                        result.push(role);
370                    }
371                }
372                Ok(result)
373            }
374            None => Ok(Vec::new()),
375        }
376    }
377
378    pub async fn has_permission(&self, user_id: &str, permission_id: &str) -> crate::core::RiResult<bool> {
379        let user_role_set = self.user_roles.get(user_id).await;
380        
381        if let Some(role_ids) = user_role_set {
382            for role_id in role_ids {
383                if let Some(role) = self.roles.get(&role_id).await {
384                    if role.permissions.contains("*") {
385                        return Ok(true);
386                    }
387                    
388                    if role.permissions.contains(permission_id) {
389                        return Ok(true);
390                    }
391                }
392            }
393        }
394        
395        Ok(false)
396    }
397
398    pub async fn has_any_permission(&self, user_id: &str, permissions: &[String]) -> crate::core::RiResult<bool> {
399        for permission in permissions {
400            if self.has_permission(user_id, permission).await? {
401                return Ok(true);
402            }
403        }
404        Ok(false)
405    }
406
407    pub async fn has_all_permissions(&self, user_id: &str, permissions: &[String]) -> crate::core::RiResult<bool> {
408        for permission in permissions {
409            if !self.has_permission(user_id, permission).await? {
410                return Ok(false);
411            }
412        }
413        Ok(true)
414    }
415
416    pub async fn get_user_permissions(&self, user_id: &str) -> crate::core::RiResult<HashSet<String>> {
417        let user_role_set = self.user_roles.get(user_id).await;
418        
419        let mut permissions = HashSet::new();
420        
421        if let Some(role_ids) = user_role_set {
422            for role_id in role_ids {
423                if let Some(role) = self.roles.get(&role_id).await {
424                    permissions.extend(role.permissions);
425                }
426            }
427        }
428        
429        Ok(permissions)
430    }
431
432    pub async fn delete_permission(&self, permission_id: &str) -> crate::core::RiResult<bool> {
433        Ok(self.permissions.remove(permission_id).await.is_some())
434    }
435
436    pub async fn delete_role(&self, role_id: &str) -> crate::core::RiResult<bool> {
437        let role = self.roles.get(role_id).await;
438        if let Some(r) = role {
439            if r.is_system {
440                return Ok(false);
441            }
442        }
443
444        let was_deleted = self.roles.remove(role_id).await.is_some();
445        
446        if was_deleted {
447            self.user_roles.for_each_mut(|_, role_set| {
448                role_set.remove(role_id);
449            }).await;
450        }
451        
452        Ok(was_deleted)
453    }
454
455    pub async fn list_permissions(&self) -> crate::core::RiResult<Vec<RiPermission>> {
456        Ok(self.permissions.collect_all().await.into_values().collect())
457    }
458
459    pub async fn list_roles(&self) -> crate::core::RiResult<Vec<RiRole>> {
460        Ok(self.roles.collect_all().await.into_values().collect())
461    }
462}
463
464#[cfg(feature = "pyo3")]
465/// Python bindings for the Permission Manager.
466///
467/// This module provides Python interface to Ri RBAC functionality,
468/// enabling Python applications to manage permissions, roles, and user assignments.
469///
470/// ## Supported Operations
471///
472/// - Permission creation and management
473/// - Role creation and management with permission assignments
474/// - User role assignments and removal
475/// - Permission checking for users
476/// - Permission and role listing
477///
478/// ## Python Usage Example
479///
480/// ```python
481/// from ri import RiPermission, RiRole, RiPermissionManager
482///
483/// # Create permission manager
484/// perm_manager = RiPermissionManager()
485///
486/// # Create a permission
487/// permission = RiPermission(
488///     id="read:device",
489///     name="Read Device",
490///     description="Allows reading device information",
491///     resource="device",
492///     action="read",
493/// )
494///
495/// # Create a role
496/// role = RiRole(
497///     id="device_admin",
498///     name="Device Administrator",
499///     description="Manages devices",
500///     permissions=["read:device", "write:device"],
501///     is_system=False,
502/// )
503/// # Note: Async operations require Python 3.7+ with asyncio
504/// ```
505#[pyo3::prelude::pymethods]
506impl RiPermissionManager {
507    #[new]
508    fn py_new() -> PyResult<Self> {
509        Ok(Self::new())
510    }
511    
512    #[pyo3(name = "create_permission")]
513    fn create_permission_impl(&self, permission: RiPermission) -> PyResult<()> {
514        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
515        rt.block_on(async {
516            self.create_permission(permission).await
517                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
518        })
519    }
520    
521    #[pyo3(name = "create_role")]
522    fn create_role_impl(&self, role: RiRole) -> PyResult<()> {
523        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
524        rt.block_on(async {
525            self.create_role(role).await
526                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
527        })
528    }
529    
530    #[pyo3(name = "assign_role_to_user")]
531    fn assign_role_to_user_impl(&self, user_id: String, role_id: String) -> PyResult<bool> {
532        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
533        rt.block_on(async {
534            self.assign_role_to_user(user_id, role_id).await
535                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
536        })
537    }
538    
539    #[pyo3(name = "has_permission")]
540    fn has_permission_impl(&self, user_id: String, permission_id: String) -> PyResult<bool> {
541        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
542        rt.block_on(async {
543            self.has_permission(&user_id, &permission_id).await
544                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
545        })
546    }
547    
548    #[pyo3(name = "get_user_roles")]
549    fn get_user_roles_impl(&self, user_id: String) -> PyResult<Vec<RiRole>> {
550        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
551        rt.block_on(async {
552            self.get_user_roles(&user_id).await
553                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
554        })
555    }
556    
557    #[pyo3(name = "get_user_permissions")]
558    fn get_user_permissions_impl(&self, user_id: String) -> PyResult<Vec<String>> {
559        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
560        rt.block_on(async {
561            self.get_user_permissions(&user_id).await
562                .map(|perms| perms.into_iter().collect())
563                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
564        })
565    }
566    
567    #[pyo3(name = "remove_role_from_user")]
568    fn remove_role_from_user_impl(&self, user_id: String, role_id: String) -> PyResult<bool> {
569        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
570        rt.block_on(async {
571            self.remove_role_from_user(&user_id, &role_id).await
572                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
573        })
574    }
575    
576    #[pyo3(name = "list_roles")]
577    fn list_roles_impl(&self) -> PyResult<Vec<RiRole>> {
578        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
579        rt.block_on(async {
580            self.list_roles().await
581                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
582        })
583    }
584    
585    #[pyo3(name = "list_permissions")]
586    fn list_permissions_impl(&self) -> PyResult<Vec<RiPermission>> {
587        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
588        rt.block_on(async {
589            self.list_permissions().await
590                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
591        })
592    }
593    
594    #[pyo3(name = "delete_role")]
595    fn delete_role_impl(&self, role_id: String) -> PyResult<bool> {
596        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
597        rt.block_on(async {
598            self.delete_role(&role_id).await
599                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
600        })
601    }
602    
603    #[pyo3(name = "delete_permission")]
604    fn delete_permission_impl(&self, permission_id: String) -> PyResult<bool> {
605        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
606        rt.block_on(async {
607            self.delete_permission(&permission_id).await
608                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
609        })
610    }
611    
612    #[pyo3(name = "get_role")]
613    fn get_role_impl(&self, role_id: String) -> PyResult<Option<RiRole>> {
614        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
615        rt.block_on(async {
616            self.get_role(&role_id).await
617                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
618        })
619    }
620    
621    #[pyo3(name = "get_permission")]
622    fn get_permission_impl(&self, permission_id: String) -> PyResult<Option<RiPermission>> {
623        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
624        rt.block_on(async {
625            self.get_permission(&permission_id).await
626                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
627        })
628    }
629    
630    #[pyo3(name = "has_any_permission")]
631    fn has_any_permission_impl(&self, user_id: String, permissions: Vec<String>) -> PyResult<bool> {
632        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
633        rt.block_on(async {
634            self.has_any_permission(&user_id, &permissions).await
635                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
636        })
637    }
638    
639    #[pyo3(name = "has_all_permissions")]
640    fn has_all_permissions_impl(&self, user_id: String, permissions: Vec<String>) -> PyResult<bool> {
641        let rt = tokio::runtime::Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
642        rt.block_on(async {
643            self.has_all_permissions(&user_id, &permissions).await
644                .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
645        })
646    }
647}