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