1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum Role {
18 Admin,
20 #[default]
22 Operator,
23 Viewer,
25}
26
27impl std::fmt::Display for Role {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 Role::Admin => write!(f, "admin"),
31 Role::Operator => write!(f, "operator"),
32 Role::Viewer => write!(f, "viewer"),
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum Permission {
45 GenerateData,
47 ManageJobs,
49 ViewJobs,
51 ManageConfig,
53 ViewConfig,
55 ViewMetrics,
57 ManageApiKeys,
59}
60
61impl std::fmt::Display for Permission {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Permission::GenerateData => write!(f, "generate_data"),
65 Permission::ManageJobs => write!(f, "manage_jobs"),
66 Permission::ViewJobs => write!(f, "view_jobs"),
67 Permission::ManageConfig => write!(f, "manage_config"),
68 Permission::ViewConfig => write!(f, "view_config"),
69 Permission::ViewMetrics => write!(f, "view_metrics"),
70 Permission::ManageApiKeys => write!(f, "manage_api_keys"),
71 }
72 }
73}
74
75pub struct RolePermissions;
81
82impl RolePermissions {
83 pub fn has_permission(role: &Role, permission: &Permission) -> bool {
97 match role {
98 Role::Admin => true,
99 Role::Operator => matches!(
100 permission,
101 Permission::GenerateData
102 | Permission::ManageJobs
103 | Permission::ViewJobs
104 | Permission::ViewConfig
105 | Permission::ViewMetrics
106 ),
107 Role::Viewer => matches!(
108 permission,
109 Permission::ViewJobs | Permission::ViewConfig | Permission::ViewMetrics
110 ),
111 }
112 }
113
114 pub fn permissions_for(role: &Role) -> Vec<Permission> {
116 let all = [
117 Permission::GenerateData,
118 Permission::ManageJobs,
119 Permission::ViewJobs,
120 Permission::ManageConfig,
121 Permission::ViewConfig,
122 Permission::ViewMetrics,
123 Permission::ManageApiKeys,
124 ];
125 all.into_iter()
126 .filter(|p| Self::has_permission(role, p))
127 .collect()
128 }
129}
130
131#[derive(Debug, Clone, Default, Serialize, Deserialize)]
137pub struct RbacConfig {
138 #[serde(default)]
141 pub enabled: bool,
142}
143
144#[cfg(test)]
149#[allow(clippy::unwrap_used)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_admin_has_all_permissions() {
155 let all_permissions = [
156 Permission::GenerateData,
157 Permission::ManageJobs,
158 Permission::ViewJobs,
159 Permission::ManageConfig,
160 Permission::ViewConfig,
161 Permission::ViewMetrics,
162 Permission::ManageApiKeys,
163 ];
164 for perm in &all_permissions {
165 assert!(
166 RolePermissions::has_permission(&Role::Admin, perm),
167 "Admin should have permission: {}",
168 perm
169 );
170 }
171 }
172
173 #[test]
174 fn test_viewer_denied_generate() {
175 assert!(!RolePermissions::has_permission(
176 &Role::Viewer,
177 &Permission::GenerateData
178 ));
179 assert!(!RolePermissions::has_permission(
180 &Role::Viewer,
181 &Permission::ManageJobs
182 ));
183 assert!(!RolePermissions::has_permission(
184 &Role::Viewer,
185 &Permission::ManageConfig
186 ));
187 assert!(!RolePermissions::has_permission(
188 &Role::Viewer,
189 &Permission::ManageApiKeys
190 ));
191 }
192
193 #[test]
194 fn test_viewer_allowed_read_only() {
195 assert!(RolePermissions::has_permission(
196 &Role::Viewer,
197 &Permission::ViewJobs
198 ));
199 assert!(RolePermissions::has_permission(
200 &Role::Viewer,
201 &Permission::ViewConfig
202 ));
203 assert!(RolePermissions::has_permission(
204 &Role::Viewer,
205 &Permission::ViewMetrics
206 ));
207 }
208
209 #[test]
210 fn test_operator_permissions() {
211 assert!(RolePermissions::has_permission(
213 &Role::Operator,
214 &Permission::GenerateData
215 ));
216 assert!(RolePermissions::has_permission(
217 &Role::Operator,
218 &Permission::ManageJobs
219 ));
220 assert!(RolePermissions::has_permission(
221 &Role::Operator,
222 &Permission::ViewJobs
223 ));
224 assert!(RolePermissions::has_permission(
225 &Role::Operator,
226 &Permission::ViewConfig
227 ));
228 assert!(RolePermissions::has_permission(
229 &Role::Operator,
230 &Permission::ViewMetrics
231 ));
232
233 assert!(!RolePermissions::has_permission(
235 &Role::Operator,
236 &Permission::ManageConfig
237 ));
238 assert!(!RolePermissions::has_permission(
239 &Role::Operator,
240 &Permission::ManageApiKeys
241 ));
242 }
243
244 #[test]
245 fn test_default_role_is_operator() {
246 let role = Role::default();
247 assert_eq!(role, Role::Operator);
248 }
249
250 #[test]
251 fn test_rbac_config_default_disabled() {
252 let config = RbacConfig::default();
253 assert!(!config.enabled);
254 }
255
256 #[test]
257 fn test_role_serialization_roundtrip() {
258 let role = Role::Admin;
259 let json = serde_json::to_string(&role).unwrap();
260 assert_eq!(json, "\"admin\"");
261 let deserialized: Role = serde_json::from_str(&json).unwrap();
262 assert_eq!(deserialized, Role::Admin);
263 }
264
265 #[test]
266 fn test_permissions_for_role() {
267 let admin_perms = RolePermissions::permissions_for(&Role::Admin);
268 assert_eq!(admin_perms.len(), 7);
269
270 let operator_perms = RolePermissions::permissions_for(&Role::Operator);
271 assert_eq!(operator_perms.len(), 5);
272
273 let viewer_perms = RolePermissions::permissions_for(&Role::Viewer);
274 assert_eq!(viewer_perms.len(), 3);
275 }
276}