mockforge_collab/
permissions.rs

1//! Permission checking and role-based access control
2
3use crate::error::{CollabError, Result};
4use crate::models::UserRole;
5use serde::{Deserialize, Serialize};
6
7/// Permission types in the system
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
9pub enum Permission {
10    // Workspace permissions
11    WorkspaceCreate,
12    WorkspaceRead,
13    WorkspaceUpdate,
14    WorkspaceDelete,
15    WorkspaceArchive,
16    WorkspaceManageMembers,
17
18    // Mock/Route permissions
19    MockCreate,
20    MockRead,
21    MockUpdate,
22    MockDelete,
23
24    // Collaboration permissions
25    InviteMembers,
26    RemoveMembers,
27    ChangeRoles,
28
29    // History permissions
30    ViewHistory,
31    CreateSnapshot,
32    RestoreSnapshot,
33
34    // Settings permissions
35    ManageSettings,
36    ManageIntegrations,
37
38    // Scenario-specific permissions
39    /// Modify chaos rules for scenarios (typically QA only)
40    ScenarioModifyChaosRules,
41    /// Change reality-level defaults for scenarios (typically Platform team only)
42    ScenarioModifyRealityDefaults,
43    /// Promote scenarios between environments
44    ScenarioPromote,
45    /// Approve scenario promotions
46    ScenarioApprove,
47    /// Modify drift budgets for scenarios
48    ScenarioModifyDriftBudgets,
49}
50
51impl std::fmt::Display for Permission {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::WorkspaceCreate => write!(f, "WorkspaceCreate"),
55            Self::WorkspaceRead => write!(f, "WorkspaceRead"),
56            Self::WorkspaceUpdate => write!(f, "WorkspaceUpdate"),
57            Self::WorkspaceDelete => write!(f, "WorkspaceDelete"),
58            Self::WorkspaceArchive => write!(f, "WorkspaceArchive"),
59            Self::WorkspaceManageMembers => write!(f, "WorkspaceManageMembers"),
60            Self::MockCreate => write!(f, "MockCreate"),
61            Self::MockRead => write!(f, "MockRead"),
62            Self::MockUpdate => write!(f, "MockUpdate"),
63            Self::MockDelete => write!(f, "MockDelete"),
64            Self::InviteMembers => write!(f, "InviteMembers"),
65            Self::RemoveMembers => write!(f, "RemoveMembers"),
66            Self::ChangeRoles => write!(f, "ChangeRoles"),
67            Self::ViewHistory => write!(f, "ViewHistory"),
68            Self::CreateSnapshot => write!(f, "CreateSnapshot"),
69            Self::RestoreSnapshot => write!(f, "RestoreSnapshot"),
70            Self::ManageSettings => write!(f, "ManageSettings"),
71            Self::ManageIntegrations => write!(f, "ManageIntegrations"),
72            Self::ScenarioModifyChaosRules => write!(f, "ScenarioModifyChaosRules"),
73            Self::ScenarioModifyRealityDefaults => write!(f, "ScenarioModifyRealityDefaults"),
74            Self::ScenarioPromote => write!(f, "ScenarioPromote"),
75            Self::ScenarioApprove => write!(f, "ScenarioApprove"),
76            Self::ScenarioModifyDriftBudgets => write!(f, "ScenarioModifyDriftBudgets"),
77        }
78    }
79}
80
81/// Role permissions mapping
82pub struct RolePermissions;
83
84impl RolePermissions {
85    /// Get all permissions for a role
86    #[must_use]
87    pub fn get_permissions(role: UserRole) -> Vec<Permission> {
88        match role {
89            UserRole::Admin => vec![
90                // Full access to everything
91                Permission::WorkspaceCreate,
92                Permission::WorkspaceRead,
93                Permission::WorkspaceUpdate,
94                Permission::WorkspaceDelete,
95                Permission::WorkspaceArchive,
96                Permission::WorkspaceManageMembers,
97                Permission::MockCreate,
98                Permission::MockRead,
99                Permission::MockUpdate,
100                Permission::MockDelete,
101                Permission::InviteMembers,
102                Permission::RemoveMembers,
103                Permission::ChangeRoles,
104                Permission::ViewHistory,
105                Permission::CreateSnapshot,
106                Permission::RestoreSnapshot,
107                Permission::ManageSettings,
108                Permission::ManageIntegrations,
109                // Scenario permissions - admins have all
110                Permission::ScenarioModifyChaosRules,
111                Permission::ScenarioModifyRealityDefaults,
112                Permission::ScenarioPromote,
113                Permission::ScenarioApprove,
114                Permission::ScenarioModifyDriftBudgets,
115            ],
116            UserRole::Editor => vec![
117                // Can edit but not manage workspace or members
118                Permission::WorkspaceRead,
119                Permission::MockCreate,
120                Permission::MockRead,
121                Permission::MockUpdate,
122                Permission::MockDelete,
123                Permission::ViewHistory,
124                Permission::CreateSnapshot,
125                // Editors can promote scenarios but not approve or modify sensitive configs
126                Permission::ScenarioPromote,
127            ],
128            UserRole::Viewer => vec![
129                // Read-only access
130                Permission::WorkspaceRead,
131                Permission::MockRead,
132                Permission::ViewHistory,
133            ],
134        }
135    }
136
137    /// Check if a role has a specific permission
138    #[must_use]
139    pub fn has_permission(role: UserRole, permission: Permission) -> bool {
140        Self::get_permissions(role).contains(&permission)
141    }
142}
143
144/// Permission checker for authorization
145pub struct PermissionChecker;
146
147impl PermissionChecker {
148    /// Check if a user has permission to perform an action
149    pub fn check(user_role: UserRole, required_permission: Permission) -> Result<()> {
150        if RolePermissions::has_permission(user_role, required_permission) {
151            Ok(())
152        } else {
153            Err(CollabError::AuthorizationFailed(format!(
154                "Role {user_role:?} does not have permission {required_permission:?}"
155            )))
156        }
157    }
158
159    /// Check multiple permissions (must have all)
160    pub fn check_all(user_role: UserRole, required_permissions: &[Permission]) -> Result<()> {
161        for permission in required_permissions {
162            Self::check(user_role, *permission)?;
163        }
164        Ok(())
165    }
166
167    /// Check multiple permissions (must have at least one)
168    pub fn check_any(user_role: UserRole, required_permissions: &[Permission]) -> Result<()> {
169        for permission in required_permissions {
170            if RolePermissions::has_permission(user_role, *permission) {
171                return Ok(());
172            }
173        }
174        Err(CollabError::AuthorizationFailed(format!(
175            "Role {user_role:?} does not have any of the required permissions"
176        )))
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_admin_permissions() {
186        let permissions = RolePermissions::get_permissions(UserRole::Admin);
187        assert!(permissions.contains(&Permission::WorkspaceDelete));
188        assert!(permissions.contains(&Permission::MockCreate));
189        assert!(permissions.contains(&Permission::ChangeRoles));
190    }
191
192    #[test]
193    fn test_editor_permissions() {
194        let permissions = RolePermissions::get_permissions(UserRole::Editor);
195        assert!(permissions.contains(&Permission::MockCreate));
196        assert!(permissions.contains(&Permission::MockUpdate));
197        assert!(!permissions.contains(&Permission::WorkspaceDelete));
198        assert!(!permissions.contains(&Permission::ChangeRoles));
199    }
200
201    #[test]
202    fn test_viewer_permissions() {
203        let permissions = RolePermissions::get_permissions(UserRole::Viewer);
204        assert!(permissions.contains(&Permission::WorkspaceRead));
205        assert!(permissions.contains(&Permission::MockRead));
206        assert!(!permissions.contains(&Permission::MockCreate));
207        assert!(!permissions.contains(&Permission::MockUpdate));
208    }
209
210    #[test]
211    fn test_permission_check() {
212        assert!(PermissionChecker::check(UserRole::Admin, Permission::WorkspaceDelete).is_ok());
213        assert!(PermissionChecker::check(UserRole::Editor, Permission::MockCreate).is_ok());
214        assert!(PermissionChecker::check(UserRole::Viewer, Permission::MockCreate).is_err());
215    }
216
217    #[test]
218    fn test_check_all() {
219        let permissions = vec![Permission::MockRead, Permission::MockCreate];
220        assert!(PermissionChecker::check_all(UserRole::Editor, &permissions).is_ok());
221        assert!(PermissionChecker::check_all(UserRole::Viewer, &permissions).is_err());
222    }
223
224    #[test]
225    fn test_check_any() {
226        let permissions = vec![Permission::MockCreate, Permission::WorkspaceDelete];
227        assert!(PermissionChecker::check_any(UserRole::Editor, &permissions).is_ok());
228
229        let admin_only = vec![Permission::WorkspaceDelete, Permission::ChangeRoles];
230        assert!(PermissionChecker::check_any(UserRole::Viewer, &admin_only).is_err());
231    }
232}