Skip to main content

Mountain/IPC/Permission/Role/
ManageRole.rs

1//! # Manage Role
2//!
3//! ## File: IPC/Permission/Role/ManageRole.rs
4//!
5//! ## Role in Mountain Architecture
6//!
7//! Defines and manages role structures for role-based access control (RBAC),
8//! providing organizational hierarchy for user permissions across the system.
9//!
10//! ## Primary Responsibility
11//!
12//! Define role and permission structures for RBAC system with inheritance
13//! support.
14//!
15//! ## Secondary Responsibilities
16//!
17//! - Create role definitions with assigned permissions
18//! - Create permission definitions with categorization
19//! - Support role hierarchy and permission inheritance
20//! - Validate role and permission integrity
21//!
22//! ## Dependencies
23//!
24//! **External Crates:**
25//! - `serde` - Serialization for storage and transport
26//! - `std::collections::HashSet` - Unique permission tracking
27//!
28//! **Internal Modules:**
29//! - `Validate::{SecurityContext}` - Security context validation
30//! - `LogEvent::{SecurityEvent}` - Security event logging
31//!
32//! ## Dependents
33//!
34//! - `Validate` - Uses roles for permission validation
35//! - `TauriIPCServer` - Manages roles for IPC authorization
36//!
37//! ## VSCode Pattern Reference
38//!
39//! Matches VSCode's role system in
40//! `vs/platform/permissions/common/permissions.ts`
41//! - Hierarchical role definitions
42//! - Permission categorization
43//! - Role inheritance support
44//! - Permission uniqueness validation
45//!
46//! ## Security Considerations
47//!
48//! - Role names are case-sensitive for precise control
49//! - Permission names follow hierarchical naming (category.action)
50//! - Role inheritance prevents permission escalation through ambiguity
51//! - Role modifications logged for audit trails
52//! - Default roles cannot be deleted without confirmation
53//! - Permission deduplication prevents duplicate permissions in roles
54//!
55//! ## Performance Considerations
56//!
57//! - HashSet for unique permissions enables O(1) lookup
58//! - Role hierarchy flattened for fast permission resolution
59//! - Lazy initialization of role collections
60//! - Minimal copying of permission data
61//!
62//! ## Error Handling Strategy
63//!
64//! - Returns Result for explicit error handling
65//! - Duplicate permissions ignored with warning
66//! - Invalid role/permission names rejected early
67//! - Circular dependency detection in role hierarchy
68//!
69//! ## Thread Safety
70//!
71//! - Immutable role definitions after creation
72//! - Clone semantics for safe sharing across threads
73//!
74//! ## TODO Items
75//!
76//! - [ ] Implement role hierarchy with parent/child relationships
77//! - [ ] Add permission negation (deny permissions)
78//! - [ ] Support role templates for common permission sets
79
80use std::collections::HashSet;
81
82use serde::{Deserialize, Serialize};
83
84use crate::dev_log;
85
86/// Role definition for RBAC system
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct Role {
89	/// Unique role identifier (case-sensitive)
90	pub Name:String,
91
92	/// Permissions granted by this role (unique, deduplicated)
93	pub Permissions:Vec<String>,
94
95	/// Human-readable description of role purpose
96	pub Description:String,
97
98	/// Optional parent role for inheritance (not yet implemented)
99	pub ParentRole:Option<String>,
100
101	/// Role priority for conflict resolution (higher = more important)
102	pub Priority:u32,
103}
104
105/// Permission definition
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Permission {
108	/// Unique permission identifier (formatted as category.action)
109	pub Name:String,
110
111	/// Human-readable description of permission purpose
112	pub Description:String,
113
114	/// Permission category for organization
115	pub Category:String,
116
117	/// Whether this permission is sensitive (requires special logging)
118	pub IsSensitive:bool,
119}
120
121impl Role {
122	/// Create a new role definition
123	///
124	/// ## Parameters
125	/// - `Name`: Unique role identifier
126	/// - `Permissions`: List of permission strings
127	/// - `Description`: Human-readable description
128	///
129	/// ## Returns
130	/// New Role instance with deduplicated permissions
131	///
132	/// ## Notes
133	/// - Permissions are automatically deduplicated
134	/// - Default priority is 0
135	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	/// Create a new role with parent inheritance
142	///
143	/// ## Parameters
144	/// - `Name`: Unique role identifier
145	/// - `Permissions`: List of permission strings
146	/// - `Description`: Human-readable description
147	/// - `ParentRole`: Name of parent role to inherit from
148	/// - `Priority`: Role priority level
149	///
150	/// ## Returns
151	/// New Role instance with inheritance configuration
152	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	/// Add a permission to this role
171	///
172	/// ## Parameters
173	/// - `Permission`: Permission string to add
174	///
175	/// ## Returns
176	/// Self for method chaining
177	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	/// Add multiple permissions to this role
193	///
194	/// ## Parameters
195	/// - `Permissions`: Iterator of permission strings to add
196	///
197	/// ## Returns
198	/// Self for method chaining
199	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	/// Check if this role has a specific permission
210	///
211	/// ## Parameters
212	/// - `Permission`: Permission string to check
213	///
214	/// ## Returns
215	/// true if role has permission, false otherwise
216	pub fn HasPermission(&self, Permission:&str) -> bool { self.Permissions.contains(&Permission.to_string()) }
217
218	/// Get the count of permissions in this role
219	///
220	/// ## Returns
221	/// Number of unique permissions
222	pub fn PermissionCount(&self) -> usize { self.Permissions.len() }
223
224	/// Validate role structure integrity
225	///
226	/// ## Returns
227	/// Result indicating success or validation error
228	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		// Validate permission names
242		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	/// Create a new permission definition
265	///
266	/// ## Parameters
267	/// - `Name`: Unique permission identifier (category.action format)
268	/// - `Description`: Human-readable description
269	/// - `Category`: Permission category
270	///
271	/// ## Returns
272	/// New Permission instance
273	pub fn New(Name:String, Description:String, Category:String) -> Self {
274		Self { Name, Description, Category, IsSensitive:false }
275	}
276
277	/// Create a new sensitive permission (requires special logging)
278	///
279	/// ## Parameters
280	/// - `Name`: Unique permission identifier
281	/// - `Description`: Human-readable description
282	/// - `Category`: Permission category
283	///
284	/// ## Returns
285	/// New Permission instance marked as sensitive
286	pub fn NewSensitive(Name:String, Description:String, Category:String) -> Self {
287		Self { Name, Description, Category, IsSensitive:true }
288	}
289
290	/// Mark permission as sensitive
291	///
292	/// ## Returns
293	/// Self for method chaining
294	pub fn SetSensitive(mut self) -> Self {
295		self.IsSensitive = true;
296		self
297	}
298
299	/// Get the action part of the permission name (after last dot)
300	///
301	/// ## Returns
302	/// Action string or "unknown" if format is invalid
303	pub fn GetAction(&self) -> String { self.Name.rsplit('.').next().unwrap_or("unknown").to_string() }
304
305	/// Get the category part of the permission name (before last dot)
306	///
307	/// ## Returns
308	/// Category string or "unknown" if format is invalid
309	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	/// Validate permission structure integrity
318	///
319	/// ## Returns
320	/// Result indicating success or validation error
321	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
346/// Create standard user role
347///
348/// ## Returns
349/// Role configured with read-only permissions
350pub 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
358/// Create developer role
359///
360/// ## Returns
361/// Role configured with read/write permissions
362pub 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
376/// Create administrator role
377///
378/// ## Returns
379/// Role configured with full system access
380pub 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
399/// Create all standard roles
400///
401/// ## Returns
402/// Vector containing user, developer, and admin roles
403pub fn CreateStandardRoles() -> Vec<Role> {
404	dev_log!("ipc", "[ManageRole] Creating standard roles");
405	vec![CreateUserRole(), CreateDeveloperRole(), CreateAdminRole()]
406}
407
408/// Create all standard permissions
409///
410/// ## Returns
411/// Vector containing standard permission definitions
412pub fn CreateStandardPermissions() -> Vec<Permission> {
413	dev_log!("ipc", "[ManageRole] Creating standard permissions");
414	vec![
415		// File permissions
416		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		// Config permissions
428		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		// Storage permissions
440		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		// System permissions
448		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		// Admin permissions
461		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}