1use std::collections::HashSet;
81
82use serde::{Deserialize, Serialize};
83
84use crate::dev_log;
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct Role {
89 pub Name:String,
91
92 pub Permissions:Vec<String>,
94
95 pub Description:String,
97
98 pub ParentRole:Option<String>,
100
101 pub Priority:u32,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Permission {
108 pub Name:String,
110
111 pub Description:String,
113
114 pub Category:String,
116
117 pub IsSensitive:bool,
119}
120
121impl Role {
122 pub fn New(Name:String, Permissions:Vec<String>, Description:String) -> Self {
136 let UniquePermissions:Vec<String> = Permissions.into_iter().collect::<HashSet<String>>().into_iter().collect();
137
138 Self { Name, Permissions:UniquePermissions, Description, ParentRole:None, Priority:0 }
139 }
140
141 pub fn NewWithParent(
153 Name:String,
154 Permissions:Vec<String>,
155 Description:String,
156 ParentRole:String,
157 Priority:u32,
158 ) -> Self {
159 let UniquePermissions:Vec<String> = Permissions.into_iter().collect::<HashSet<String>>().into_iter().collect();
160
161 Self {
162 Name,
163 Permissions:UniquePermissions,
164 Description,
165 ParentRole:Some(ParentRole),
166 Priority,
167 }
168 }
169
170 pub fn AddPermission(mut self, Permission:String) -> Self {
178 if !self.Permissions.contains(&Permission) {
179 self.Permissions.push(Permission.clone());
180 dev_log!("ipc", "[Role] Added permission '{}' to role '{}'", Permission, self.Name);
181 } else {
182 dev_log!(
183 "ipc",
184 "[Role] Permission '{}' already exists in role '{}', skipping",
185 Permission,
186 self.Name
187 );
188 }
189 self
190 }
191
192 pub fn AddPermissions(mut self, Permissions:impl IntoIterator<Item = String>) -> Self {
200 for Permission in Permissions {
201 if !self.Permissions.contains(&Permission) {
202 self.Permissions.push(Permission.clone());
203 dev_log!("ipc", "[Role] Added permission '{}' to role '{}'", Permission, self.Name);
204 }
205 }
206 self
207 }
208
209 pub fn HasPermission(&self, Permission:&str) -> bool { self.Permissions.contains(&Permission.to_string()) }
217
218 pub fn PermissionCount(&self) -> usize { self.Permissions.len() }
223
224 pub fn Validate(&self) -> Result<(), String> {
229 if self.Name.is_empty() {
230 return Err("Role name cannot be empty".to_string());
231 }
232
233 if self.Name.contains(|c:char| c.is_whitespace()) {
234 return Err("Role name cannot contain whitespace".to_string());
235 }
236
237 if self.Description.is_empty() {
238 return Err("Role description cannot be empty".to_string());
239 }
240
241 for Permission in &self.Permissions {
243 if Permission.is_empty() {
244 return Err("Permission name cannot be empty".to_string());
245 }
246
247 if !Permission.contains('.') {
248 return Err(format!(
249 "Permission '{}' must contain a dot separating category and action",
250 Permission
251 ));
252 }
253
254 if Permission.contains(|c:char| c.is_whitespace()) {
255 return Err(format!("Permission '{}' cannot contain whitespace", Permission));
256 }
257 }
258
259 Ok(())
260 }
261}
262
263impl Permission {
264 pub fn New(Name:String, Description:String, Category:String) -> Self {
274 Self { Name, Description, Category, IsSensitive:false }
275 }
276
277 pub fn NewSensitive(Name:String, Description:String, Category:String) -> Self {
287 Self { Name, Description, Category, IsSensitive:true }
288 }
289
290 pub fn SetSensitive(mut self) -> Self {
295 self.IsSensitive = true;
296 self
297 }
298
299 pub fn GetAction(&self) -> String { self.Name.rsplit('.').next().unwrap_or("unknown").to_string() }
304
305 pub fn GetCategory(&self) -> String {
310 if let Some(pos) = self.Name.rfind('.') {
311 self.Name[..pos].to_string()
312 } else {
313 "unknown".to_string()
314 }
315 }
316
317 pub fn Validate(&self) -> Result<(), String> {
322 if self.Name.is_empty() {
323 return Err("Permission name cannot be empty".to_string());
324 }
325
326 if self.Name.contains(|c:char| c.is_whitespace()) {
327 return Err("Permission name cannot contain whitespace".to_string());
328 }
329
330 if !self.Name.contains('.') {
331 return Err("Permission name must contain a dot separating category and action".to_string());
332 }
333
334 if self.Description.is_empty() {
335 return Err("Permission description cannot be empty".to_string());
336 }
337
338 if self.Category.is_empty() {
339 return Err("Permission category cannot be empty".to_string());
340 }
341
342 Ok(())
343 }
344}
345
346pub fn CreateUserRole() -> Role {
351 Role::New(
352 "user".to_string(),
353 vec!["file.read".to_string(), "config.read".to_string(), "storage.read".to_string()],
354 "Standard user with read access".to_string(),
355 )
356}
357
358pub fn CreateDeveloperRole() -> Role {
363 Role::New(
364 "developer".to_string(),
365 vec![
366 "file.read".to_string(),
367 "file.write".to_string(),
368 "config.read".to_string(),
369 "storage.read".to_string(),
370 "storage.write".to_string(),
371 ],
372 "Developer with read/write access".to_string(),
373 )
374}
375
376pub fn CreateAdminRole() -> Role {
381 Role::New(
382 "admin".to_string(),
383 vec![
384 "file.read".to_string(),
385 "file.write".to_string(),
386 "config.read".to_string(),
387 "config.update".to_string(),
388 "storage.read".to_string(),
389 "storage.write".to_string(),
390 "system.external".to_string(),
391 "system.execute".to_string(),
392 "admin.manage".to_string(),
393 ],
394 "Administrator with full access".to_string(),
395 )
396 .AddPermission("role.manage".to_string())
397}
398
399pub fn CreateStandardRoles() -> Vec<Role> {
404 dev_log!("ipc", "[ManageRole] Creating standard roles");
405 vec![CreateUserRole(), CreateDeveloperRole(), CreateAdminRole()]
406}
407
408pub fn CreateStandardPermissions() -> Vec<Permission> {
413 dev_log!("ipc", "[ManageRole] Creating standard permissions");
414 vec![
415 Permission::New("file.read".to_string(), "Read file operations".to_string(), "file".to_string()),
417 Permission::New(
418 "file.write".to_string(),
419 "Write file operations".to_string(),
420 "file".to_string(),
421 ),
422 Permission::New(
423 "file.delete".to_string(),
424 "Delete file operations".to_string(),
425 "file".to_string(),
426 ),
427 Permission::New(
429 "config.read".to_string(),
430 "Read configuration".to_string(),
431 "config".to_string(),
432 ),
433 Permission::NewSensitive(
434 "config.update".to_string(),
435 "Update configuration".to_string(),
436 "config".to_string(),
437 )
438 .SetSensitive(),
439 Permission::New("storage.read".to_string(), "Read storage".to_string(), "storage".to_string()),
441 Permission::New("storage.write".to_string(), "Write storage".to_string(), "storage".to_string()),
442 Permission::New(
443 "storage.delete".to_string(),
444 "Delete from storage".to_string(),
445 "storage".to_string(),
446 ),
447 Permission::NewSensitive(
449 "system.external".to_string(),
450 "Access external system resources".to_string(),
451 "system".to_string(),
452 )
453 .SetSensitive(),
454 Permission::NewSensitive(
455 "system.execute".to_string(),
456 "Execute system commands".to_string(),
457 "system".to_string(),
458 )
459 .SetSensitive(),
460 Permission::NewSensitive(
462 "admin.manage".to_string(),
463 "Administrative management operations".to_string(),
464 "admin".to_string(),
465 )
466 .SetSensitive(),
467 Permission::NewSensitive(
468 "role.manage".to_string(),
469 "Manage roles and permissions".to_string(),
470 "admin".to_string(),
471 )
472 .SetSensitive(),
473 ]
474}
475
476#[cfg(test)]
477mod Tests {
478 use super::*;
479
480 #[test]
481 fn TestCreateRole() {
482 let role = Role::New(
483 "test.role".to_string(),
484 vec!["perm1".to_string(), "perm2".to_string(), "perm1".to_string()],
485 "Test role".to_string(),
486 );
487
488 assert_eq!(role.Name, "test.role");
489 assert_eq!(role.Description, "Test role");
490 assert_eq!(role.PermissionCount(), 2, "Should deduplicate permissions");
491 }
492
493 #[test]
494 fn TestRoleHasPermission() {
495 let role = Role::New(
496 "test.role".to_string(),
497 vec!["perm1".to_string(), "perm2".to_string()],
498 "Test role".to_string(),
499 );
500
501 assert!(role.HasPermission("perm1"));
502 assert!(role.HasPermission("perm2"));
503 assert!(!role.HasPermission("perm3"));
504 }
505
506 #[test]
507 fn TestAddPermission() {
508 let role = Role::New("test.role".to_string(), vec!["perm1".to_string()], "Test role".to_string())
509 .AddPermission("perm2".to_string());
510
511 assert!(role.HasPermission("perm1"));
512 assert!(role.HasPermission("perm2"));
513 }
514
515 #[test]
516 fn TestAddPermissions() {
517 let role = Role::New("test.role".to_string(), vec!["perm1".to_string()], "Test role".to_string())
518 .AddPermissions(vec!["perm2".to_string(), "perm3".to_string()]);
519
520 assert_eq!(role.PermissionCount(), 3);
521 }
522
523 #[test]
524 fn TestRoleValidateSuccess() {
525 let role = Role::New(
526 "test.role".to_string(),
527 vec!["category.action".to_string()],
528 "Valid role".to_string(),
529 );
530
531 assert!(role.Validate().is_ok());
532 }
533
534 #[test]
535 fn TestRoleValidateEmptyName() {
536 let role = Role::New("".to_string(), vec!["category.action".to_string()], "Valid role".to_string());
537
538 assert!(role.Validate().is_err());
539 }
540
541 #[test]
542 fn TestRoleValidateWhitespaceInName() {
543 let role = Role::New(
544 "test role".to_string(),
545 vec!["category.action".to_string()],
546 "Valid role".to_string(),
547 );
548
549 assert!(role.Validate().is_err());
550 }
551
552 #[test]
553 fn TestRoleValidateEmptyDescription() {
554 let role = Role::New("test.role".to_string(), vec!["category.action".to_string()], "".to_string());
555
556 assert!(role.Validate().is_err());
557 }
558
559 #[test]
560 fn TestPermissionNew() {
561 let perm = Permission::New("file.read".to_string(), "Read files".to_string(), "file".to_string());
562
563 assert_eq!(perm.Name, "file.read");
564 assert_eq!(perm.Description, "Read files");
565 assert_eq!(perm.Category, "file");
566 assert!(!perm.IsSensitive);
567 }
568
569 #[test]
570 fn TestPermissionNewSensitive() {
571 let perm =
572 Permission::NewSensitive("config.update".to_string(), "Update config".to_string(), "config".to_string());
573
574 assert!(perm.IsSensitive);
575 }
576
577 #[test]
578 fn TestPermissionGetAction() {
579 let perm = Permission::New("file.read".to_string(), "Read files".to_string(), "file".to_string());
580
581 assert_eq!(perm.GetAction(), "read");
582 }
583
584 #[test]
585 fn TestPermissionGetCategory() {
586 let perm = Permission::New("file.read".to_string(), "Read files".to_string(), "file".to_string());
587
588 assert_eq!(perm.GetCategory(), "file");
589 }
590
591 #[test]
592 fn TestPermissionValidateSuccess() {
593 let perm = Permission::New("file.read".to_string(), "Read files".to_string(), "file".to_string());
594
595 assert!(perm.Validate().is_ok());
596 }
597
598 #[test]
599 fn TestPermissionValidateMissingDot() {
600 let perm = Permission::New("fileread".to_string(), "Read files".to_string(), "file".to_string());
601
602 assert!(perm.Validate().is_err());
603 }
604
605 #[test]
606 fn TestCreateStandardRoles() {
607 let roles = CreateStandardRoles();
608 assert_eq!(roles.len(), 3);
609
610 let user_role = roles.iter().find(|r| r.Name == "user").unwrap();
611 assert!(user_role.HasPermission("file.read"));
612
613 let admin_role = roles.iter().find(|r| r.Name == "admin").unwrap();
614 assert!(admin_role.HasPermission("admin.manage"));
615 }
616
617 #[test]
618 fn TestCreateStandardPermissions() {
619 let perms = CreateStandardPermissions();
620 assert!(perms.len() > 0);
621
622 let file_read = perms.iter().find(|p| p.Name == "file.read").unwrap();
623 assert_eq!(file_read.Category, "file");
624
625 let config_update = perms.iter().find(|p| p.Name == "config.update").unwrap();
626 assert!(config_update.IsSensitive);
627 }
628
629 #[test]
630 fn TestRoleWithParent() {
631 let role = Role::NewWithParent(
632 "test.role".to_string(),
633 vec!["perm1".to_string()],
634 "Test role".to_string(),
635 "parent.role".to_string(),
636 10,
637 );
638
639 assert_eq!(role.ParentRole, Some("parent.role".to_string()));
640 assert_eq!(role.Priority, 10);
641 }
642}