1use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct Permission {
13 pub resource: String,
15
16 pub action: String,
18}
19
20impl Permission {
21 pub fn new(resource: impl Into<String>, action: impl Into<String>) -> Self {
23 Self {
24 resource: resource.into(),
25 action: action.into(),
26 }
27 }
28
29 pub fn matches(&self, other: &Permission) -> bool {
31 let resource_match = self.resource == "*" || self.resource == other.resource;
32 let action_match = self.action == "*" || self.action == other.action;
33 resource_match && action_match
34 }
35
36 pub fn from_string(s: &str) -> Result<Self, RbacError> {
38 let parts: Vec<&str> = s.split(':').collect();
39 if parts.len() != 2 {
40 return Err(RbacError::InvalidPermissionFormat(s.to_string()));
41 }
42 Ok(Permission::new(parts[0], parts[1]))
43 }
44
45 pub fn to_string(&self) -> String {
47 format!("{}:{}", self.resource, self.action)
48 }
49}
50
51impl fmt::Display for Permission {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "{}:{}", self.resource, self.action)
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct Role {
60 pub name: String,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub description: Option<String>,
66
67 pub permissions: HashSet<Permission>,
69
70 #[serde(default, skip_serializing_if = "Vec::is_empty")]
72 pub inherits_from: Vec<String>,
73}
74
75impl Role {
76 pub fn new(name: impl Into<String>) -> Self {
78 Self {
79 name: name.into(),
80 description: None,
81 permissions: HashSet::new(),
82 inherits_from: Vec::new(),
83 }
84 }
85
86 pub fn add_permission(&mut self, permission: Permission) {
88 self.permissions.insert(permission);
89 }
90
91 pub fn add_permissions(&mut self, permissions: Vec<Permission>) {
93 self.permissions.extend(permissions);
94 }
95
96 pub fn add_parent(&mut self, parent_role: impl Into<String>) {
98 self.inherits_from.push(parent_role.into());
99 }
100
101 pub fn with_description(mut self, description: impl Into<String>) -> Self {
103 self.description = Some(description.into());
104 self
105 }
106
107 pub fn has_permission(&self, permission: &Permission) -> bool {
109 self.permissions.iter().any(|p| p.matches(permission))
110 }
111}
112
113#[derive(Debug, Clone)]
115pub struct RbacPolicy {
116 roles: HashMap<String, Role>,
118
119 permission_cache: HashMap<String, HashSet<Permission>>,
121}
122
123impl Default for RbacPolicy {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129impl RbacPolicy {
130 pub fn new() -> Self {
132 let mut policy = Self {
133 roles: HashMap::new(),
134 permission_cache: HashMap::new(),
135 };
136
137 policy.add_default_roles();
139 policy
140 }
141
142 fn add_default_roles(&mut self) {
144 let mut admin = Role::new("admin");
146 admin.description = Some("System administrator with full access".to_string());
147 admin.add_permission(Permission::new("*", "*"));
148 self.add_role(admin);
149
150 let mut developer = Role::new("developer");
152 developer.description = Some("Developer with asset management and API access".to_string());
153 developer.add_permissions(vec![
154 Permission::new("asset", "read"),
155 Permission::new("asset", "write"),
156 Permission::new("asset", "delete"),
157 Permission::new("api-key", "create"),
158 Permission::new("api-key", "read"),
159 ]);
160 self.add_role(developer);
161
162 let mut viewer = Role::new("viewer");
164 viewer.description = Some("Read-only access to assets".to_string());
165 viewer.add_permissions(vec![
166 Permission::new("asset", "read"),
167 Permission::new("dependency", "read"),
168 ]);
169 self.add_role(viewer);
170
171 let mut user = Role::new("user");
173 user.description = Some("Regular user with basic permissions".to_string());
174 user.add_permissions(vec![
175 Permission::new("asset", "read"),
176 Permission::new("asset", "write"),
177 ]);
178 self.add_role(user);
179 }
180
181 pub fn add_role(&mut self, role: Role) {
183 let role_name = role.name.clone();
184 self.roles.insert(role_name.clone(), role);
185 self.invalidate_cache(&role_name);
186 }
187
188 pub fn get_role(&self, name: &str) -> Option<&Role> {
190 self.roles.get(name)
191 }
192
193 pub fn list_roles(&self) -> Vec<&Role> {
195 self.roles.values().collect()
196 }
197
198 pub fn remove_role(&mut self, name: &str) -> Option<Role> {
200 self.invalidate_cache(name);
201 self.roles.remove(name)
202 }
203
204 fn invalidate_cache(&mut self, role_name: &str) {
206 self.permission_cache.remove(role_name);
207 let dependent_roles: Vec<String> = self
209 .roles
210 .iter()
211 .filter(|(_, role)| role.inherits_from.contains(&role_name.to_string()))
212 .map(|(name, _)| name.clone())
213 .collect();
214
215 for role in dependent_roles {
216 self.permission_cache.remove(&role);
217 }
218 }
219
220 pub fn get_role_permissions(&mut self, role_name: &str) -> Option<HashSet<Permission>> {
222 if let Some(cached) = self.permission_cache.get(role_name) {
224 return Some(cached.clone());
225 }
226
227 let (direct_permissions, parent_roles) = {
229 let role = self.roles.get(role_name)?;
230 (role.permissions.clone(), role.inherits_from.clone())
231 };
232
233 let mut permissions = direct_permissions;
235
236 for parent_role_name in &parent_roles {
238 if let Some(parent_permissions) = self.get_role_permissions(parent_role_name) {
239 permissions.extend(parent_permissions);
240 }
241 }
242
243 self.permission_cache
245 .insert(role_name.to_string(), permissions.clone());
246
247 Some(permissions)
248 }
249
250 pub fn has_permission(&mut self, roles: &[String], permission: &Permission) -> bool {
252 for role_name in roles {
253 if let Some(role_permissions) = self.get_role_permissions(role_name) {
254 if role_permissions.iter().any(|p| p.matches(permission)) {
255 return true;
256 }
257 }
258 }
259 false
260 }
261
262 pub fn has_any_permission(
264 &mut self,
265 roles: &[String],
266 permissions: &[Permission],
267 ) -> bool {
268 permissions
269 .iter()
270 .any(|p| self.has_permission(roles, p))
271 }
272
273 pub fn has_all_permissions(
275 &mut self,
276 roles: &[String],
277 permissions: &[Permission],
278 ) -> bool {
279 permissions
280 .iter()
281 .all(|p| self.has_permission(roles, p))
282 }
283}
284
285#[derive(Debug, thiserror::Error)]
287pub enum RbacError {
288 #[error("Invalid permission format: {0}. Expected format: resource:action")]
289 InvalidPermissionFormat(String),
290
291 #[error("Role not found: {0}")]
292 RoleNotFound(String),
293
294 #[error("Permission denied: {0}")]
295 PermissionDenied(String),
296
297 #[error("Circular role inheritance detected")]
298 CircularInheritance,
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn test_permission_creation() {
307 let perm = Permission::new("asset", "read");
308 assert_eq!(perm.resource, "asset");
309 assert_eq!(perm.action, "read");
310 assert_eq!(perm.to_string(), "asset:read");
311 }
312
313 #[test]
314 fn test_permission_from_string() {
315 let perm = Permission::from_string("asset:write").unwrap();
316 assert_eq!(perm.resource, "asset");
317 assert_eq!(perm.action, "write");
318
319 assert!(Permission::from_string("invalid").is_err());
320 }
321
322 #[test]
323 fn test_permission_wildcard_matching() {
324 let perm1 = Permission::new("*", "*");
325 let perm2 = Permission::new("asset", "read");
326
327 assert!(perm1.matches(&perm2));
328 assert!(!perm2.matches(&perm1));
329 }
330
331 #[test]
332 fn test_role_creation() {
333 let mut role = Role::new("developer");
334 role.add_permission(Permission::new("asset", "read"));
335 role.add_permission(Permission::new("asset", "write"));
336
337 assert_eq!(role.name, "developer");
338 assert_eq!(role.permissions.len(), 2);
339 }
340
341 #[test]
342 fn test_rbac_policy() {
343 let mut policy = RbacPolicy::new();
344
345 assert!(policy.get_role("admin").is_some());
347 assert!(policy.get_role("developer").is_some());
348 assert!(policy.get_role("viewer").is_some());
349 }
350
351 #[test]
352 fn test_permission_checking() {
353 let mut policy = RbacPolicy::new();
354
355 let admin_roles = vec!["admin".to_string()];
356 let viewer_roles = vec!["viewer".to_string()];
357
358 let read_perm = Permission::new("asset", "read");
359 let delete_perm = Permission::new("asset", "delete");
360
361 assert!(policy.has_permission(&admin_roles, &read_perm));
363 assert!(policy.has_permission(&admin_roles, &delete_perm));
364
365 assert!(policy.has_permission(&viewer_roles, &read_perm));
367 assert!(!policy.has_permission(&viewer_roles, &delete_perm));
368 }
369
370 #[test]
371 fn test_role_inheritance() {
372 let mut policy = RbacPolicy::new();
373
374 let mut moderator = Role::new("moderator");
376 moderator.add_parent("viewer");
377 moderator.add_permission(Permission::new("asset", "delete"));
378 policy.add_role(moderator);
379
380 let moderator_roles = vec!["moderator".to_string()];
381
382 assert!(policy.has_permission(
384 &moderator_roles,
385 &Permission::new("asset", "read")
386 ));
387 assert!(policy.has_permission(
388 &moderator_roles,
389 &Permission::new("asset", "delete")
390 ));
391 }
392
393 #[test]
394 fn test_has_any_permission() {
395 let mut policy = RbacPolicy::new();
396
397 let developer_roles = vec!["developer".to_string()];
398 let permissions = vec![
399 Permission::new("asset", "delete"),
400 Permission::new("user", "admin"),
401 ];
402
403 assert!(policy.has_any_permission(&developer_roles, &permissions));
405 }
406
407 #[test]
408 fn test_has_all_permissions() {
409 let mut policy = RbacPolicy::new();
410
411 let admin_roles = vec!["admin".to_string()];
412 let permissions = vec![
413 Permission::new("asset", "read"),
414 Permission::new("asset", "write"),
415 Permission::new("asset", "delete"),
416 ];
417
418 assert!(policy.has_all_permissions(&admin_roles, &permissions));
420 }
421
422 #[test]
423 fn test_cache_invalidation() {
424 let mut policy = RbacPolicy::new();
425
426 let _ = policy.get_role_permissions("developer");
428 assert!(policy.permission_cache.contains_key("developer"));
429
430 if let Some(role) = policy.roles.get_mut("developer") {
432 role.add_permission(Permission::new("new", "permission"));
433 }
434 policy.invalidate_cache("developer");
435
436 assert!(!policy.permission_cache.contains_key("developer"));
437 }
438}