1use crate::error::{Error, Result};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
22pub struct Permission {
23 action: String,
25 resource_type: String,
27 instance: Option<String>,
29 #[cfg_attr(feature = "persistence", serde(skip))]
31 condition: Option<PermissionCondition>,
32}
33
34pub type PermissionCondition = Arc<dyn Fn(&HashMap<String, String>) -> bool + Send + Sync>;
36
37impl Permission {
38 pub fn new(action: impl Into<String>, resource_type: impl Into<String>) -> Self {
40 let action = action.into();
41 let resource_type = resource_type.into();
42
43 Self::validate_permission_field(&action, "action").expect("Invalid action in permission");
45 Self::validate_permission_field(&resource_type, "resource_type")
46 .expect("Invalid resource_type in permission");
47
48 Self {
49 action,
50 resource_type,
51 instance: None,
52 condition: None,
53 }
54 }
55
56 pub fn try_new(action: impl Into<String>, resource_type: impl Into<String>) -> Result<Self> {
58 let action = action.into();
59 let resource_type = resource_type.into();
60
61 Self::validate_permission_field(&action, "action")?;
63 Self::validate_permission_field(&resource_type, "resource_type")?;
64
65 Ok(Self {
66 action,
67 resource_type,
68 instance: None,
69 condition: None,
70 })
71 }
72
73 pub fn with_instance(
75 action: impl Into<String>,
76 resource_type: impl Into<String>,
77 instance: impl Into<String>,
78 ) -> Self {
79 let mut permission = Self::new(action, resource_type);
80 let instance = instance.into();
81
82 Self::validate_permission_field(&instance, "instance")
83 .expect("Invalid instance in permission");
84
85 permission.instance = Some(instance.to_owned());
86 permission
87 }
88
89 pub fn with_condition<F>(
91 action: impl Into<String>,
92 resource_type: impl Into<String>,
93 condition: F,
94 ) -> Self
95 where
96 F: Fn(&HashMap<String, String>) -> bool + Send + Sync + 'static,
97 {
98 let mut permission = Self::new(action, resource_type);
99 permission.condition = Some(Arc::new(condition));
100 permission
101 }
102
103 pub fn with_instance_and_condition<F>(
105 action: impl Into<String>,
106 resource_type: impl Into<String>,
107 instance: impl Into<String>,
108 condition: F,
109 ) -> Self
110 where
111 F: Fn(&HashMap<String, String>) -> bool + Send + Sync + 'static,
112 {
113 let mut permission = Self::with_instance(action, resource_type, instance);
114 permission.condition = Some(Arc::new(condition));
115 permission
116 }
117
118 pub fn wildcard(resource_type: impl Into<String>) -> Self {
120 Self::new("*", resource_type)
121 }
122
123 pub fn super_admin() -> Self {
125 Self::new("*", "*")
126 }
127
128 pub fn with_context(
136 resource_type: impl Into<String>,
137 action: impl Into<String>,
138 context: Option<impl Into<String>>,
139 ) -> Self {
140 let mut permission = Self::new(action, resource_type);
141 if let Some(ctx) = context {
142 permission.instance = Some(ctx.into());
143 }
144 permission
145 }
146
147 pub fn with_scope(
155 resource_type: impl Into<String>,
156 action: impl Into<String>,
157 scopes: Vec<impl Into<String>>,
158 ) -> Vec<Self> {
159 let resource_type = resource_type.into();
160 let action = action.into();
161
162 scopes
163 .into_iter()
164 .map(|scope| Self::with_instance(action.clone(), resource_type.clone(), scope.into()))
165 .collect()
166 }
167
168 pub fn conditional(
177 resource_type: impl Into<String>,
178 action: impl Into<String>,
179 ) -> ConditionalPermissionBuilder {
180 ConditionalPermissionBuilder::new(resource_type, action)
181 }
182
183 pub fn action(&self) -> &str {
185 &self.action
186 }
187
188 pub fn resource_type(&self) -> &str {
190 &self.resource_type
191 }
192
193 pub fn instance(&self) -> Option<&str> {
195 self.instance.as_deref()
196 }
197
198 pub fn matches(&self, action: &str, resource_type: &str) -> bool {
201 let action_match = self.action == "*" || self.action == action;
202 let resource_match = self.resource_type == "*" || self.resource_type == resource_type;
203 action_match && resource_match
204 }
205
206 pub fn matches_with_instance(
208 &self,
209 action: &str,
210 resource_type: &str,
211 instance: Option<&str>,
212 ) -> bool {
213 let action_match = self.action == "*" || self.action == action;
214 let resource_match = self.resource_type == "*" || self.resource_type == resource_type;
215
216 let instance_match = match (&self.instance, instance) {
217 (None, _) => true, (Some(perm_inst), Some(req_inst)) => perm_inst == "*" || perm_inst == req_inst,
219 (Some(_), None) => false, };
221
222 action_match && resource_match && instance_match
223 }
224
225 pub fn implies(&self, other: &Permission) -> bool {
234 let action_implies = self.action == "*" || self.action == other.action;
236
237 let resource_implies =
239 self.resource_type == "*" || self.resource_type == other.resource_type;
240
241 let instance_implies = match (&self.instance, &other.instance) {
243 (None, _) => true, (Some(_), None) => false, (Some(self_inst), Some(other_inst)) => self_inst == "*" || self_inst == other_inst,
246 };
247
248 action_implies && resource_implies && instance_implies
249 }
250
251 pub fn is_granted(
253 &self,
254 action: &str,
255 resource_type: &str,
256 context: &HashMap<String, String>,
257 ) -> bool {
258 if !self.matches(action, resource_type) {
259 return false;
260 }
261
262 if let Some(condition) = &self.condition {
264 condition(context)
265 } else {
266 true
267 }
268 }
269
270 pub fn parse(permission_str: &str) -> Result<Self> {
272 let parts: Vec<&str> = permission_str.split(':').collect();
273
274 match parts.len() {
275 2 => {
276 let action = parts[0].trim();
277 let resource_type = parts[1].trim();
278
279 Self::validate_permission_field(action, "action")?;
280 Self::validate_permission_field(resource_type, "resource_type")?;
281
282 Ok(Self::new(action, resource_type))
283 }
284 3 => {
285 let action = parts[0].trim();
286 let resource_type = parts[1].trim();
287 let instance = parts[2].trim();
288
289 Self::validate_permission_field(action, "action")?;
290 Self::validate_permission_field(resource_type, "resource_type")?;
291 Self::validate_permission_field(instance, "instance")?;
292
293 Ok(Self::with_instance(action, resource_type, instance))
294 }
295 _ => Err(Error::InvalidPermission(format!(
296 "Permission must be in format 'action:resource_type' or 'action:resource_type:instance', got: '{permission_str}'"
297 ))),
298 }
299 }
300
301 fn validate_permission_field(value: &str, field_name: &str) -> Result<()> {
303 if value.trim().is_empty() {
304 return Err(Error::ValidationError {
305 field: field_name.to_string(),
306 reason: "cannot be empty".to_string(),
307 invalid_value: Some(value.to_string()),
308 });
309 }
310
311 if value.len() > 255 {
312 return Err(Error::ValidationError {
313 field: field_name.to_string(),
314 reason: "exceeds maximum length of 255 characters".to_string(),
315 invalid_value: Some(format!("{}...", &value[..50])),
316 });
317 }
318
319 if value
322 .chars()
323 .any(|c| c.is_control() || "'\";{}[]\\<>".contains(c))
324 {
325 return Err(Error::ValidationError {
326 field: field_name.to_string(),
327 reason: "contains invalid characters".to_string(),
328 invalid_value: Some(value.to_string()),
329 });
330 }
331
332 if value.contains("..") || value.contains('\0') {
334 return Err(Error::ValidationError {
335 field: field_name.to_string(),
336 reason: "contains path traversal sequences".to_string(),
337 invalid_value: Some(value.to_string()),
338 });
339 }
340
341 Ok(())
342 }
343}
344
345pub struct ConditionalPermissionBuilder {
347 resource_type: String,
348 action: String,
349 conditions: Vec<PermissionCondition>,
350}
351
352impl ConditionalPermissionBuilder {
353 pub fn new(resource_type: impl Into<String>, action: impl Into<String>) -> Self {
355 Self {
356 resource_type: resource_type.into(),
357 action: action.into(),
358 conditions: Vec::new(),
359 }
360 }
361
362 pub fn when<F>(mut self, condition: F) -> Self
364 where
365 F: Fn(&HashMap<String, String>) -> bool + Send + Sync + 'static,
366 {
367 self.conditions.push(Arc::new(condition));
368 self
369 }
370
371 pub fn or_when<F>(mut self, condition: F) -> Self
373 where
374 F: Fn(&HashMap<String, String>) -> bool + Send + Sync + 'static,
375 {
376 self.conditions.push(Arc::new(condition));
377 self
378 }
379
380 pub fn build(self) -> Permission {
382 let combined_condition = if self.conditions.is_empty() {
383 None
384 } else {
385 Some(Arc::new(move |context: &HashMap<String, String>| {
386 self.conditions.iter().any(|condition| condition(context))
388 }) as PermissionCondition)
389 };
390
391 Permission {
392 action: self.action,
393 resource_type: self.resource_type,
394 instance: None,
395 condition: combined_condition,
396 }
397 }
398}
399
400impl std::fmt::Debug for Permission {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 f.debug_struct("Permission")
403 .field("action", &self.action)
404 .field("resource_type", &self.resource_type)
405 .field("instance", &self.instance)
406 .field("has_condition", &self.condition.is_some())
407 .finish()
408 }
409}
410
411impl Clone for Permission {
412 fn clone(&self) -> Self {
413 Self {
414 action: self.action.clone(),
415 resource_type: self.resource_type.clone(),
416 instance: self.instance.clone(),
417 condition: self.condition.clone(), }
419 }
420}
421
422impl PartialEq for Permission {
423 fn eq(&self, other: &Self) -> bool {
424 self.action == other.action
425 && self.resource_type == other.resource_type
426 && self.instance == other.instance
427 }
429}
430
431impl Eq for Permission {}
432
433impl std::hash::Hash for Permission {
434 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
435 self.action.hash(state);
436 self.resource_type.hash(state);
437 self.instance.hash(state);
438 }
440}
441
442impl std::fmt::Display for Permission {
443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444 match &self.instance {
445 Some(instance) => write!(f, "{}:{}:{}", self.action, self.resource_type, instance),
446 None => write!(f, "{}:{}", self.action, self.resource_type),
447 }
448 }
449}
450
451impl std::str::FromStr for Permission {
452 type Err = Error;
453
454 fn from_str(s: &str) -> Result<Self> {
455 Self::parse(s)
456 }
457}
458
459#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
461pub struct PermissionSet {
462 permissions: Vec<Permission>,
463}
464
465impl std::fmt::Debug for PermissionSet {
466 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467 f.debug_struct("PermissionSet")
468 .field("permissions", &self.permissions)
469 .field("count", &self.permissions.len())
470 .finish()
471 }
472}
473
474impl Clone for PermissionSet {
475 fn clone(&self) -> Self {
476 Self {
477 permissions: self.permissions.clone(),
478 }
479 }
480}
481
482impl Default for PermissionSet {
483 fn default() -> Self {
484 Self::new()
485 }
486}
487
488impl PermissionSet {
489 pub fn new() -> Self {
491 Self {
492 permissions: Vec::new(),
493 }
494 }
495
496 pub fn add(&mut self, permission: Permission) {
498 self.permissions.push(permission);
499 }
500
501 pub fn remove(&mut self, permission: &Permission) {
503 self.permissions.retain(|p| p != permission);
504 }
505
506 pub fn contains(&self, permission: &Permission) -> bool {
508 self.permissions.contains(permission)
509 }
510
511 pub fn grants(
513 &self,
514 action: &str,
515 resource_type: &str,
516 context: &HashMap<String, String>,
517 ) -> bool {
518 self.permissions
519 .iter()
520 .any(|p| p.is_granted(action, resource_type, context))
521 }
522
523 pub fn grants_with_instance(
525 &self,
526 action: &str,
527 resource_type: &str,
528 instance: Option<&str>,
529 context: &HashMap<String, String>,
530 ) -> bool {
531 self.permissions.iter().any(|p| {
532 p.matches_with_instance(action, resource_type, instance)
533 && (p.condition.is_none() || p.condition.as_ref().unwrap()(context))
534 })
535 }
536
537 pub fn implies(&self, permission: &Permission) -> bool {
539 self.permissions.iter().any(|p| p.implies(permission))
540 }
541
542 pub fn permissions(&self) -> &[Permission] {
544 &self.permissions
545 }
546
547 pub fn len(&self) -> usize {
549 self.permissions.len()
550 }
551
552 pub fn is_empty(&self) -> bool {
554 self.permissions.is_empty()
555 }
556
557 pub fn merge(&mut self, other: PermissionSet) {
559 for permission in other.permissions {
560 if !self.contains(&permission) {
561 self.add(permission);
562 }
563 }
564 }
565}
566
567impl From<Vec<Permission>> for PermissionSet {
568 fn from(permissions: Vec<Permission>) -> Self {
569 Self { permissions }
570 }
571}
572
573impl From<Permission> for PermissionSet {
574 fn from(permission: Permission) -> Self {
575 Self {
576 permissions: vec![permission],
577 }
578 }
579}
580
581impl IntoIterator for PermissionSet {
582 type Item = Permission;
583 type IntoIter = std::vec::IntoIter<Permission>;
584
585 fn into_iter(self) -> Self::IntoIter {
586 self.permissions.into_iter()
587 }
588}
589
590impl<'a> IntoIterator for &'a PermissionSet {
591 type Item = &'a Permission;
592 type IntoIter = std::slice::Iter<'a, Permission>;
593
594 fn into_iter(self) -> Self::IntoIter {
595 self.permissions.iter()
596 }
597}
598
599#[cfg(test)]
600mod tests {
601 use super::*;
602
603 #[test]
604 fn test_permission_creation() {
605 let permission = Permission::new("read", "documents");
606 assert_eq!(permission.action(), "read");
607 assert_eq!(permission.resource_type(), "documents");
608 assert_eq!(permission.instance(), None);
609 }
610
611 #[test]
612 fn test_permission_with_instance() {
613 let permission = Permission::with_instance("read", "documents", "doc123");
614 assert_eq!(permission.action(), "read");
615 assert_eq!(permission.resource_type(), "documents");
616 assert_eq!(permission.instance(), Some("doc123"));
617 }
618
619 #[test]
620 fn test_permission_matching() {
621 let permission = Permission::new("read", "documents");
622 assert!(permission.matches("read", "documents"));
623 assert!(!permission.matches("write", "documents"));
624 assert!(!permission.matches("read", "users"));
625 }
626
627 #[test]
628 fn test_permission_matching_with_instance() {
629 let permission = Permission::with_instance("read", "documents", "doc123");
630
631 assert!(permission.matches_with_instance("read", "documents", Some("doc123")));
633
634 assert!(!permission.matches_with_instance("read", "documents", Some("doc456")));
636
637 assert!(!permission.matches_with_instance("read", "documents", None));
639
640 let general_permission = Permission::new("read", "documents");
642 assert!(general_permission.matches_with_instance("read", "documents", Some("doc123")));
643 assert!(general_permission.matches_with_instance("read", "documents", None));
644 }
645
646 #[test]
647 fn test_permission_implication() {
648 let general = Permission::new("read", "documents");
649 let specific = Permission::with_instance("read", "documents", "doc123");
650
651 assert!(general.implies(&specific));
653
654 assert!(!specific.implies(&general));
656
657 assert!(general.implies(&general));
659 assert!(specific.implies(&specific));
660
661 let wildcard_action = Permission::new("*", "documents");
663 let wildcard_resource = Permission::new("read", "*");
664 let super_admin = Permission::super_admin();
665
666 assert!(wildcard_action.implies(&general));
667 assert!(wildcard_resource.implies(&general));
668 assert!(super_admin.implies(&general));
669 assert!(super_admin.implies(&specific));
670 }
671
672 #[test]
673 fn test_wildcard_permission() {
674 let permission = Permission::wildcard("documents");
675 assert!(permission.matches("read", "documents"));
676 assert!(permission.matches("write", "documents"));
677 assert!(!permission.matches("read", "users"));
678 }
679
680 #[test]
681 fn test_super_admin_permission() {
682 let permission = Permission::super_admin();
683 assert!(permission.matches("read", "documents"));
684 assert!(permission.matches("write", "users"));
685 assert!(permission.matches("delete", "anything"));
686 }
687
688 #[test]
689 fn test_permission_parsing() {
690 let permission = Permission::parse("read:documents").unwrap();
692 assert_eq!(permission.action(), "read");
693 assert_eq!(permission.resource_type(), "documents");
694 assert_eq!(permission.instance(), None);
695
696 let permission = Permission::parse("read:documents:doc123").unwrap();
698 assert_eq!(permission.action(), "read");
699 assert_eq!(permission.resource_type(), "documents");
700 assert_eq!(permission.instance(), Some("doc123"));
701
702 assert!(Permission::parse("invalid").is_err());
704 assert!(Permission::parse("read:").is_err());
705 assert!(Permission::parse(":documents").is_err());
706 assert!(Permission::parse("read:documents:").is_err());
707 assert!(Permission::parse("read:documents:instance:extra").is_err());
708 }
709
710 #[test]
711 fn test_permission_display() {
712 let permission = Permission::new("read", "documents");
713 assert_eq!(permission.to_string(), "read:documents");
714
715 let permission_with_instance = Permission::with_instance("read", "documents", "doc123");
716 assert_eq!(
717 permission_with_instance.to_string(),
718 "read:documents:doc123"
719 );
720 }
721
722 #[test]
723 fn test_permission_set() {
724 let mut set = PermissionSet::new();
725 let perm1 = Permission::new("read", "documents");
726 let perm2 = Permission::new("write", "documents");
727
728 set.add(perm1.clone());
729 set.add(perm2.clone());
730
731 assert_eq!(set.len(), 2);
732 assert!(set.contains(&perm1));
733 assert!(set.contains(&perm2));
734
735 let context = HashMap::new();
736 assert!(set.grants("read", "documents", &context));
737 assert!(set.grants("write", "documents", &context));
738 assert!(!set.grants("delete", "documents", &context));
739 }
740
741 #[test]
742 fn test_permission_set_with_instances() {
743 let mut set = PermissionSet::new();
744 let general_perm = Permission::new("read", "documents");
745 let specific_perm = Permission::with_instance("write", "documents", "doc123");
746
747 set.add(general_perm);
748 set.add(specific_perm);
749
750 let context = HashMap::new();
751
752 assert!(set.grants_with_instance("read", "documents", Some("doc123"), &context));
754 assert!(set.grants_with_instance("read", "documents", Some("doc456"), &context));
755 assert!(set.grants_with_instance("read", "documents", None, &context));
756
757 assert!(set.grants_with_instance("write", "documents", Some("doc123"), &context));
759 assert!(!set.grants_with_instance("write", "documents", Some("doc456"), &context));
760 assert!(!set.grants_with_instance("write", "documents", None, &context));
761 }
762
763 #[test]
764 fn test_permission_set_implication() {
765 let mut set = PermissionSet::new();
766 let general_perm = Permission::new("read", "documents");
767 let admin_perm = Permission::new("admin", "*");
768
769 set.add(general_perm);
770 set.add(admin_perm);
771
772 let specific_perm = Permission::with_instance("read", "documents", "doc123");
773 let admin_users_perm = Permission::new("admin", "users");
774
775 assert!(set.implies(&specific_perm));
776 assert!(set.implies(&admin_users_perm));
777 }
778}