1use crate::ast::*;
18use crate::parser::Loc;
19use annotation::{Annotation, Annotations};
20use educe::Educe;
21use itertools::Itertools;
22use miette::Diagnostic;
23use nonempty::{nonempty, NonEmpty};
24use serde::{Deserialize, Serialize};
25use smol_str::SmolStr;
26use std::{collections::HashMap, sync::Arc};
27use thiserror::Error;
28
29#[cfg(feature = "wasm")]
30extern crate tsify;
31
32#[derive(Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
37#[serde(from = "TemplateBody")]
38#[serde(into = "TemplateBody")]
39pub struct Template {
40 body: TemplateBody,
41 slots: Vec<Slot>,
46}
47
48impl From<Template> for TemplateBody {
49 fn from(val: Template) -> Self {
50 val.body
51 }
52}
53
54impl Template {
55 #[cfg(test)]
57 pub fn check_invariant(&self) {
58 for slot in self.body.condition().slots() {
59 assert!(self.slots.contains(&slot));
60 }
61 for slot in self.slots() {
62 assert!(self.body.condition().slots().contains(slot));
63 }
64 }
65 #[allow(clippy::too_many_arguments)]
73 pub fn new(
74 id: PolicyID,
75 loc: Option<Loc>,
76 annotations: Annotations,
77 effect: Effect,
78 principal_constraint: PrincipalConstraint,
79 action_constraint: ActionConstraint,
80 resource_constraint: ResourceConstraint,
81 non_scope_constraint: Expr,
82 ) -> Self {
83 let body = TemplateBody::new(
84 id,
85 loc,
86 annotations,
87 effect,
88 principal_constraint,
89 action_constraint,
90 resource_constraint,
91 non_scope_constraint,
92 );
93 Template::from(body)
96 }
97
98 #[allow(clippy::too_many_arguments)]
100 pub fn new_shared(
101 id: PolicyID,
102 loc: Option<Loc>,
103 annotations: Arc<Annotations>,
104 effect: Effect,
105 principal_constraint: PrincipalConstraint,
106 action_constraint: ActionConstraint,
107 resource_constraint: ResourceConstraint,
108 non_scope_constraint: Arc<Expr>,
109 ) -> Self {
110 let body = TemplateBody::new_shared(
111 id,
112 loc,
113 annotations,
114 effect,
115 principal_constraint,
116 action_constraint,
117 resource_constraint,
118 non_scope_constraint,
119 );
120 Template::from(body)
123 }
124
125 pub fn principal_constraint(&self) -> &PrincipalConstraint {
127 self.body.principal_constraint()
128 }
129
130 pub fn action_constraint(&self) -> &ActionConstraint {
132 self.body.action_constraint()
133 }
134
135 pub fn resource_constraint(&self) -> &ResourceConstraint {
137 self.body.resource_constraint()
138 }
139
140 pub fn non_scope_constraints(&self) -> &Expr {
142 self.body.non_scope_constraints()
143 }
144
145 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
147 self.body.non_scope_constraints_arc()
148 }
149
150 pub fn id(&self) -> &PolicyID {
152 self.body.id()
153 }
154
155 pub fn new_id(&self, id: PolicyID) -> Self {
157 Template {
158 body: self.body.new_id(id),
159 slots: self.slots.clone(),
160 }
161 }
162
163 pub fn loc(&self) -> Option<&Loc> {
165 self.body.loc()
166 }
167
168 pub fn effect(&self) -> Effect {
170 self.body.effect()
171 }
172
173 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
175 self.body.annotation(key)
176 }
177
178 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
180 self.body.annotations()
181 }
182
183 pub fn annotations_arc(&self) -> &Arc<Annotations> {
185 self.body.annotations_arc()
186 }
187
188 pub fn condition(&self) -> Expr {
194 self.body.condition()
195 }
196
197 pub fn slots(&self) -> impl Iterator<Item = &Slot> {
199 self.slots.iter()
200 }
201
202 pub fn is_static(&self) -> bool {
207 self.slots.is_empty()
208 }
209
210 pub fn check_binding(
214 template: &Template,
215 values: &HashMap<SlotId, EntityUID>,
216 ) -> Result<(), LinkingError> {
217 let unbound = template
219 .slots
220 .iter()
221 .filter(|slot| !values.contains_key(&slot.id))
222 .collect::<Vec<_>>();
223
224 let extra = values
225 .iter()
226 .filter_map(|(slot, _)| {
227 if !template
228 .slots
229 .iter()
230 .any(|template_slot| template_slot.id == *slot)
231 {
232 Some(slot)
233 } else {
234 None
235 }
236 })
237 .collect::<Vec<_>>();
238
239 if unbound.is_empty() && extra.is_empty() {
240 Ok(())
241 } else {
242 Err(LinkingError::from_unbound_and_extras(
243 unbound.into_iter().map(|slot| slot.id),
244 extra.into_iter().copied(),
245 ))
246 }
247 }
248
249 pub fn link(
253 template: Arc<Template>,
254 new_id: PolicyID,
255 values: HashMap<SlotId, EntityUID>,
256 ) -> Result<Policy, LinkingError> {
257 Template::check_binding(&template, &values)
259 .map(|_| Policy::new(template, Some(new_id), values))
260 }
261
262 pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
265 let body: TemplateBody = p.into();
266 let t = Arc::new(Self {
270 body,
271 slots: vec![],
272 });
273 #[cfg(test)]
274 {
275 t.check_invariant();
276 }
277 let p = Policy::new(Arc::clone(&t), None, HashMap::new());
283 (t, p)
284 }
285}
286
287impl From<TemplateBody> for Template {
288 fn from(body: TemplateBody) -> Self {
289 let slots = body.condition().slots().collect::<Vec<_>>();
292 Self { body, slots }
293 }
294}
295
296impl std::fmt::Display for Template {
297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298 write!(f, "{}", self.body)
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
304pub enum LinkingError {
305 #[error(fmt = describe_arity_error)]
308 ArityError {
309 unbound_values: Vec<SlotId>,
311 extra_values: Vec<SlotId>,
313 },
314
315 #[error("failed to find a template with id `{id}`")]
317 NoSuchTemplate {
318 id: PolicyID,
320 },
321
322 #[error("template-linked policy id `{id}` conflicts with an existing policy id")]
324 PolicyIdConflict {
325 id: PolicyID,
327 },
328}
329
330impl LinkingError {
331 fn from_unbound_and_extras(
332 unbound: impl Iterator<Item = SlotId>,
333 extra: impl Iterator<Item = SlotId>,
334 ) -> Self {
335 Self::ArityError {
336 unbound_values: unbound.collect(),
337 extra_values: extra.collect(),
338 }
339 }
340}
341
342fn describe_arity_error(
343 unbound_values: &[SlotId],
344 extra_values: &[SlotId],
345 fmt: &mut std::fmt::Formatter<'_>,
346) -> std::fmt::Result {
347 match (unbound_values.len(), extra_values.len()) {
348 #[allow(clippy::unreachable)]
350 (0,0) => unreachable!(),
351 (_unbound, 0) => write!(fmt, "the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
352 (0, _extra) => write!(fmt, "the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
353 (_unbound, _extra) => write!(fmt, "the following slots were not provided as arguments: {}. The following slots were provided as arguments, but did not exist in the template: {}", unbound_values.iter().join(","), extra_values.iter().join(",")),
354 }
355}
356
357#[derive(Debug, Clone, Eq, PartialEq)]
365pub struct Policy {
366 template: Arc<Template>,
368 link: Option<PolicyID>,
371 values: HashMap<SlotId, EntityUID>,
377}
378
379impl Policy {
380 fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
384 #[cfg(test)]
385 {
386 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
387 }
388 Self {
394 template,
395 link: link_id,
396 values,
397 }
398 }
399
400 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID, loc: Option<Loc>) -> Self {
402 Self::from_when_clause_annos(
403 effect,
404 Arc::new(when),
405 id,
406 loc,
407 Arc::new(Annotations::default()),
408 )
409 }
410
411 pub fn from_when_clause_annos(
413 effect: Effect,
414 when: Arc<Expr>,
415 id: PolicyID,
416 loc: Option<Loc>,
417 annotations: Arc<Annotations>,
418 ) -> Self {
419 let t = Template::new_shared(
420 id,
421 loc,
422 annotations,
423 effect,
424 PrincipalConstraint::any(),
425 ActionConstraint::any(),
426 ResourceConstraint::any(),
427 when,
428 );
429 Self::new(Arc::new(t), None, SlotEnv::new())
430 }
431
432 pub fn template(&self) -> &Template {
434 &self.template
435 }
436
437 pub(crate) fn template_arc(&self) -> Arc<Template> {
439 Arc::clone(&self.template)
440 }
441
442 pub fn effect(&self) -> Effect {
444 self.template.effect()
445 }
446
447 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
449 self.template.annotation(key)
450 }
451
452 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
454 self.template.annotations()
455 }
456
457 pub fn annotations_arc(&self) -> &Arc<Annotations> {
459 self.template.annotations_arc()
460 }
461
462 pub fn principal_constraint(&self) -> PrincipalConstraint {
468 let constraint = self.template.principal_constraint().clone();
469 match self.values.get(&SlotId::principal()) {
470 None => constraint,
471 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
472 }
473 }
474
475 pub fn action_constraint(&self) -> &ActionConstraint {
477 self.template.action_constraint()
478 }
479
480 pub fn resource_constraint(&self) -> ResourceConstraint {
486 let constraint = self.template.resource_constraint().clone();
487 match self.values.get(&SlotId::resource()) {
488 None => constraint,
489 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
490 }
491 }
492
493 pub fn non_scope_constraints(&self) -> &Expr {
495 self.template.non_scope_constraints()
496 }
497
498 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
500 self.template.non_scope_constraints_arc()
501 }
502
503 pub fn condition(&self) -> Expr {
505 self.template.condition()
506 }
507
508 pub fn env(&self) -> &SlotEnv {
511 &self.values
512 }
513
514 pub fn id(&self) -> &PolicyID {
516 self.link.as_ref().unwrap_or_else(|| self.template.id())
517 }
518
519 pub fn new_id(&self, id: PolicyID) -> Self {
521 match self.link {
522 None => Policy {
523 template: Arc::new(self.template.new_id(id)),
524 link: None,
525 values: self.values.clone(),
526 },
527 Some(_) => Policy {
528 template: self.template.clone(),
529 link: Some(id),
530 values: self.values.clone(),
531 },
532 }
533 }
534
535 pub fn loc(&self) -> Option<&Loc> {
537 self.template.loc()
538 }
539
540 pub fn is_static(&self) -> bool {
542 self.link.is_none()
543 }
544}
545
546impl std::fmt::Display for Policy {
547 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 if self.is_static() {
549 write!(f, "{}", self.template())
550 } else {
551 write!(
552 f,
553 "Template Instance of {}, slots: [{}]",
554 self.template().id(),
555 display_slot_env(self.env())
556 )
557 }
558 }
559}
560
561pub type SlotEnv = HashMap<SlotId, EntityUID>;
563
564#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
568pub struct LiteralPolicy {
569 template_id: PolicyID,
571 link_id: Option<PolicyID>,
575 values: SlotEnv,
577}
578
579#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
581pub struct BorrowedLiteralPolicy<'a> {
582 template_id: &'a PolicyID,
584 link_id: Option<&'a PolicyID>,
588 values: &'a SlotEnv,
590}
591
592impl<'a> From<&'a Policy> for BorrowedLiteralPolicy<'a> {
593 fn from(p: &'a Policy) -> Self {
594 Self {
595 template_id: p.template.id(),
596 link_id: p.link.as_ref(),
597 values: &p.values,
598 }
599 }
600}
601
602impl LiteralPolicy {
603 pub fn static_policy(template_id: PolicyID) -> Self {
607 Self {
608 template_id,
609 link_id: None,
610 values: SlotEnv::new(),
611 }
612 }
613
614 pub fn template_linked_policy(
618 template_id: PolicyID,
619 link_id: PolicyID,
620 values: SlotEnv,
621 ) -> Self {
622 Self {
623 template_id,
624 link_id: Some(link_id),
625 values,
626 }
627 }
628
629 pub fn value(&self, slot: &SlotId) -> Option<&EntityUID> {
631 self.values.get(slot)
632 }
633}
634
635impl std::hash::Hash for LiteralPolicy {
638 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
639 self.template_id.hash(state);
640 let mut buf = self.values.iter().collect::<Vec<_>>();
642 buf.sort();
643 for (id, euid) in buf {
644 id.hash(state);
645 euid.hash(state);
646 }
647 }
648}
649
650#[cfg(test)]
652mod hashing_tests {
653 use std::{
654 collections::hash_map::DefaultHasher,
655 hash::{Hash, Hasher},
656 };
657
658 use super::*;
659
660 fn compute_hash(ir: &LiteralPolicy) -> u64 {
661 let mut s = DefaultHasher::new();
662 ir.hash(&mut s);
663 s.finish()
664 }
665
666 fn build_template_linked_policy() -> LiteralPolicy {
667 let mut map = HashMap::new();
668 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
669 LiteralPolicy {
670 template_id: PolicyID::from_string("template"),
671 link_id: Some(PolicyID::from_string("id")),
672 values: map,
673 }
674 }
675
676 #[test]
677 fn hash_property_instances() {
678 let a = build_template_linked_policy();
679 let b = build_template_linked_policy();
680 assert_eq!(a, b);
681 assert_eq!(compute_hash(&a), compute_hash(&b));
682 }
683}
684#[derive(Debug, Diagnostic, Error)]
692pub enum ReificationError {
693 #[error("the id linked to does not exist")]
695 NoSuchTemplate(PolicyID),
696 #[error(transparent)]
698 #[diagnostic(transparent)]
699 Linking(#[from] LinkingError),
700}
701
702impl LiteralPolicy {
703 pub fn reify(
708 self,
709 templates: &HashMap<PolicyID, Arc<Template>>,
710 ) -> Result<Policy, ReificationError> {
711 let template = templates
712 .get(&self.template_id)
713 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
714 Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
716 Ok(Policy::new(template.clone(), self.link_id, self.values))
717 }
718
719 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
721 self.values.get(id)
722 }
723
724 pub fn id(&self) -> &PolicyID {
726 self.link_id.as_ref().unwrap_or(&self.template_id)
727 }
728
729 pub fn template_id(&self) -> &PolicyID {
733 &self.template_id
734 }
735
736 pub fn is_static(&self) -> bool {
738 self.link_id.is_none()
739 }
740}
741
742fn display_slot_env(env: &SlotEnv) -> String {
743 env.iter()
744 .map(|(slot, value)| format!("{slot} -> {value}"))
745 .join(",")
746}
747
748impl std::fmt::Display for LiteralPolicy {
749 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
750 if self.is_static() {
751 write!(f, "Static policy w/ ID {}", self.template_id())
752 } else {
753 write!(
754 f,
755 "Template linked policy of {}, slots: [{}]",
756 self.template_id(),
757 display_slot_env(&self.values),
758 )
759 }
760 }
761}
762
763impl From<Policy> for LiteralPolicy {
764 fn from(p: Policy) -> Self {
765 Self {
766 template_id: p.template.id().clone(),
767 link_id: p.link,
768 values: p.values,
769 }
770 }
771}
772
773#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
777pub struct StaticPolicy(TemplateBody);
778
779impl StaticPolicy {
780 pub fn id(&self) -> &PolicyID {
782 self.0.id()
783 }
784
785 pub fn new_id(&self, id: PolicyID) -> Self {
787 StaticPolicy(self.0.new_id(id))
788 }
789
790 pub fn loc(&self) -> Option<&Loc> {
792 self.0.loc()
793 }
794
795 pub fn effect(&self) -> Effect {
797 self.0.effect()
798 }
799
800 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
802 self.0.annotation(key)
803 }
804
805 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
807 self.0.annotations()
808 }
809
810 pub fn principal_constraint(&self) -> &PrincipalConstraint {
812 self.0.principal_constraint()
813 }
814
815 pub fn principal_constraint_expr(&self) -> Expr {
819 self.0.principal_constraint_expr()
820 }
821
822 pub fn action_constraint(&self) -> &ActionConstraint {
824 self.0.action_constraint()
825 }
826
827 pub fn action_constraint_expr(&self) -> Expr {
831 self.0.action_constraint_expr()
832 }
833
834 pub fn resource_constraint(&self) -> &ResourceConstraint {
836 self.0.resource_constraint()
837 }
838
839 pub fn resource_constraint_expr(&self) -> Expr {
843 self.0.resource_constraint_expr()
844 }
845
846 pub fn non_scope_constraints(&self) -> &Expr {
851 self.0.non_scope_constraints()
852 }
853
854 pub fn condition(&self) -> Expr {
860 self.0.condition()
861 }
862
863 #[allow(clippy::too_many_arguments)]
865 pub fn new(
866 id: PolicyID,
867 loc: Option<Loc>,
868 annotations: Annotations,
869 effect: Effect,
870 principal_constraint: PrincipalConstraint,
871 action_constraint: ActionConstraint,
872 resource_constraint: ResourceConstraint,
873 non_scope_constraints: Expr,
874 ) -> Result<Self, UnexpectedSlotError> {
875 let body = TemplateBody::new(
876 id,
877 loc,
878 annotations,
879 effect,
880 principal_constraint,
881 action_constraint,
882 resource_constraint,
883 non_scope_constraints,
884 );
885 let first_slot = body.condition().slots().next();
886 match first_slot {
888 Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
889 None => Ok(Self(body)),
890 }
891 }
892}
893
894impl TryFrom<Template> for StaticPolicy {
895 type Error = UnexpectedSlotError;
896
897 fn try_from(value: Template) -> Result<Self, Self::Error> {
898 let o = value.slots().next().cloned();
900 match o {
901 Some(slot_id) => Err(Self::Error::FoundSlot(slot_id)),
902 None => Ok(Self(value.body)),
903 }
904 }
905}
906
907impl From<StaticPolicy> for Policy {
908 fn from(p: StaticPolicy) -> Policy {
909 let (_, policy) = Template::link_static_policy(p);
910 policy
911 }
912}
913
914impl From<StaticPolicy> for Arc<Template> {
915 fn from(p: StaticPolicy) -> Self {
916 let (t, _) = Template::link_static_policy(p);
917 t
918 }
919}
920
921#[derive(Educe, Serialize, Deserialize, Clone, Debug)]
924#[educe(PartialEq, Eq, Hash)]
925pub struct TemplateBody {
926 id: PolicyID,
928 #[educe(PartialEq(ignore))]
930 #[educe(Hash(ignore))]
931 loc: Option<Loc>,
932 annotations: Arc<Annotations>,
936 effect: Effect,
938 principal_constraint: PrincipalConstraint,
942 action_constraint: ActionConstraint,
946 resource_constraint: ResourceConstraint,
950 non_scope_constraints: Arc<Expr>,
955}
956
957impl TemplateBody {
958 pub fn id(&self) -> &PolicyID {
960 &self.id
961 }
962
963 pub fn loc(&self) -> Option<&Loc> {
965 self.loc.as_ref()
966 }
967
968 pub fn new_id(&self, id: PolicyID) -> Self {
970 let mut new = self.clone();
971 new.id = id;
972 new
973 }
974
975 pub fn effect(&self) -> Effect {
977 self.effect
978 }
979
980 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
982 self.annotations.get(key)
983 }
984
985 pub fn annotations_arc(&self) -> &Arc<Annotations> {
987 &self.annotations
988 }
989
990 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
992 self.annotations.iter()
993 }
994
995 pub fn principal_constraint(&self) -> &PrincipalConstraint {
997 &self.principal_constraint
998 }
999
1000 pub fn principal_constraint_expr(&self) -> Expr {
1004 self.principal_constraint.as_expr()
1005 }
1006
1007 pub fn action_constraint(&self) -> &ActionConstraint {
1009 &self.action_constraint
1010 }
1011
1012 pub fn action_constraint_expr(&self) -> Expr {
1016 self.action_constraint.as_expr()
1017 }
1018
1019 pub fn resource_constraint(&self) -> &ResourceConstraint {
1021 &self.resource_constraint
1022 }
1023
1024 pub fn resource_constraint_expr(&self) -> Expr {
1028 self.resource_constraint.as_expr()
1029 }
1030
1031 pub fn non_scope_constraints(&self) -> &Expr {
1036 &self.non_scope_constraints
1037 }
1038
1039 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
1041 &self.non_scope_constraints
1042 }
1043
1044 pub fn condition(&self) -> Expr {
1050 Expr::and(
1051 Expr::and(
1052 Expr::and(
1053 self.principal_constraint_expr(),
1054 self.action_constraint_expr(),
1055 )
1056 .with_maybe_source_loc(self.loc.clone()),
1057 self.resource_constraint_expr(),
1058 )
1059 .with_maybe_source_loc(self.loc.clone()),
1060 self.non_scope_constraints.as_ref().clone(),
1061 )
1062 .with_maybe_source_loc(self.loc.clone())
1063 }
1064
1065 #[allow(clippy::too_many_arguments)]
1067 pub fn new_shared(
1068 id: PolicyID,
1069 loc: Option<Loc>,
1070 annotations: Arc<Annotations>,
1071 effect: Effect,
1072 principal_constraint: PrincipalConstraint,
1073 action_constraint: ActionConstraint,
1074 resource_constraint: ResourceConstraint,
1075 non_scope_constraints: Arc<Expr>,
1076 ) -> Self {
1077 Self {
1078 id,
1079 loc,
1080 annotations,
1081 effect,
1082 principal_constraint,
1083 action_constraint,
1084 resource_constraint,
1085 non_scope_constraints,
1086 }
1087 }
1088
1089 #[allow(clippy::too_many_arguments)]
1091 pub fn new(
1092 id: PolicyID,
1093 loc: Option<Loc>,
1094 annotations: Annotations,
1095 effect: Effect,
1096 principal_constraint: PrincipalConstraint,
1097 action_constraint: ActionConstraint,
1098 resource_constraint: ResourceConstraint,
1099 non_scope_constraints: Expr,
1100 ) -> Self {
1101 Self {
1102 id,
1103 loc,
1104 annotations: Arc::new(annotations),
1105 effect,
1106 principal_constraint,
1107 action_constraint,
1108 resource_constraint,
1109 non_scope_constraints: Arc::new(non_scope_constraints),
1110 }
1111 }
1112}
1113
1114impl From<StaticPolicy> for TemplateBody {
1115 fn from(p: StaticPolicy) -> Self {
1116 p.0
1117 }
1118}
1119
1120impl std::fmt::Display for TemplateBody {
1121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1122 self.annotations.fmt(f)?;
1123 write!(
1124 f,
1125 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1126 self.effect(),
1127 self.principal_constraint(),
1128 self.action_constraint(),
1129 self.resource_constraint(),
1130 self.non_scope_constraints()
1131 )
1132 }
1133}
1134
1135#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1137pub struct PrincipalConstraint {
1138 pub(crate) constraint: PrincipalOrResourceConstraint,
1139}
1140
1141impl PrincipalConstraint {
1142 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1144 PrincipalConstraint { constraint }
1145 }
1146
1147 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1149 &self.constraint
1150 }
1151
1152 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1154 self.constraint
1155 }
1156
1157 pub fn as_expr(&self) -> Expr {
1159 self.constraint.as_expr(PrincipalOrResource::Principal)
1160 }
1161
1162 pub fn any() -> Self {
1164 PrincipalConstraint {
1165 constraint: PrincipalOrResourceConstraint::any(),
1166 }
1167 }
1168
1169 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1171 PrincipalConstraint {
1172 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1173 }
1174 }
1175
1176 pub fn is_eq_slot() -> Self {
1178 Self {
1179 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1180 }
1181 }
1182
1183 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1185 PrincipalConstraint {
1186 constraint: PrincipalOrResourceConstraint::is_in(euid),
1187 }
1188 }
1189
1190 pub fn is_in_slot() -> Self {
1192 Self {
1193 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1194 }
1195 }
1196
1197 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1199 Self {
1200 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1201 }
1202 }
1203
1204 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1206 Self {
1207 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1208 }
1209 }
1210
1211 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1213 Self {
1214 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1215 }
1216 }
1217
1218 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1220 match self.constraint {
1221 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1222 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1223 },
1224 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1225 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1226 },
1227 _ => self,
1228 }
1229 }
1230}
1231
1232impl std::fmt::Display for PrincipalConstraint {
1233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1234 write!(
1235 f,
1236 "{}",
1237 self.constraint.display(PrincipalOrResource::Principal)
1238 )
1239 }
1240}
1241
1242#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1244pub struct ResourceConstraint {
1245 pub(crate) constraint: PrincipalOrResourceConstraint,
1246}
1247
1248impl ResourceConstraint {
1249 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1251 ResourceConstraint { constraint }
1252 }
1253
1254 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1256 &self.constraint
1257 }
1258
1259 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1261 self.constraint
1262 }
1263
1264 pub fn as_expr(&self) -> Expr {
1266 self.constraint.as_expr(PrincipalOrResource::Resource)
1267 }
1268
1269 pub fn any() -> Self {
1271 ResourceConstraint {
1272 constraint: PrincipalOrResourceConstraint::any(),
1273 }
1274 }
1275
1276 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1278 ResourceConstraint {
1279 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1280 }
1281 }
1282
1283 pub fn is_eq_slot() -> Self {
1285 Self {
1286 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1287 }
1288 }
1289
1290 pub fn is_in_slot() -> Self {
1292 Self {
1293 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1294 }
1295 }
1296
1297 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1299 ResourceConstraint {
1300 constraint: PrincipalOrResourceConstraint::is_in(euid),
1301 }
1302 }
1303
1304 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1306 Self {
1307 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1308 }
1309 }
1310
1311 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1313 Self {
1314 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1315 }
1316 }
1317
1318 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1320 Self {
1321 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1322 }
1323 }
1324
1325 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1327 match self.constraint {
1328 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1329 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1330 },
1331 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1332 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1333 },
1334 _ => self,
1335 }
1336 }
1337}
1338
1339impl std::fmt::Display for ResourceConstraint {
1340 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1341 write!(
1342 f,
1343 "{}",
1344 self.as_inner().display(PrincipalOrResource::Resource)
1345 )
1346 }
1347}
1348
1349#[derive(Educe, Serialize, Deserialize, Clone, Debug, Eq)]
1351#[educe(Hash, PartialEq, PartialOrd, Ord)]
1352pub enum EntityReference {
1353 EUID(Arc<EntityUID>),
1355 Slot(
1357 #[serde(skip)]
1358 #[educe(PartialEq(ignore))]
1359 #[educe(PartialOrd(ignore))]
1360 #[educe(Hash(ignore))]
1361 Option<Loc>,
1362 ),
1363}
1364
1365impl EntityReference {
1366 pub fn euid(euid: Arc<EntityUID>) -> Self {
1368 Self::EUID(euid)
1369 }
1370
1371 pub fn into_expr(&self, slot: SlotId) -> Expr {
1377 match self {
1378 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1379 EntityReference::Slot(loc) => {
1380 Expr::slot(slot).with_maybe_source_loc(loc.as_ref().cloned())
1381 }
1382 }
1383 }
1384}
1385
1386#[derive(Debug, Clone, PartialEq, Eq, Error)]
1388pub enum UnexpectedSlotError {
1389 #[error("found slot `{}` where slots are not allowed", .0.id)]
1391 FoundSlot(Slot),
1392}
1393
1394impl Diagnostic for UnexpectedSlotError {
1395 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1396 match self {
1397 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1398 let label = miette::LabeledSpan::underline(loc.span);
1399 Box::new(std::iter::once(label)) as Box<dyn Iterator<Item = miette::LabeledSpan>>
1400 }),
1401 }
1402 }
1403
1404 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
1405 match self {
1406 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|l| l as &dyn miette::SourceCode),
1407 }
1408 }
1409}
1410
1411impl From<EntityUID> for EntityReference {
1412 fn from(euid: EntityUID) -> Self {
1413 Self::EUID(Arc::new(euid))
1414 }
1415}
1416
1417#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
1419#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1420pub enum PrincipalOrResource {
1421 Principal,
1423 Resource,
1425}
1426
1427impl std::fmt::Display for PrincipalOrResource {
1428 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1429 let v = Var::from(*self);
1430 write!(f, "{v}")
1431 }
1432}
1433
1434impl TryFrom<Var> for PrincipalOrResource {
1435 type Error = Var;
1436
1437 fn try_from(value: Var) -> Result<Self, Self::Error> {
1438 match value {
1439 Var::Principal => Ok(Self::Principal),
1440 Var::Action => Err(Var::Action),
1441 Var::Resource => Ok(Self::Resource),
1442 Var::Context => Err(Var::Context),
1443 }
1444 }
1445}
1446
1447#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1450pub enum PrincipalOrResourceConstraint {
1451 Any,
1453 In(EntityReference),
1455 Eq(EntityReference),
1457 Is(Arc<EntityType>),
1459 IsIn(Arc<EntityType>, EntityReference),
1461}
1462
1463impl PrincipalOrResourceConstraint {
1464 pub fn any() -> Self {
1466 PrincipalOrResourceConstraint::Any
1467 }
1468
1469 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1471 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1472 }
1473
1474 pub fn is_eq_slot() -> Self {
1476 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None))
1477 }
1478
1479 pub fn is_in_slot() -> Self {
1481 PrincipalOrResourceConstraint::In(EntityReference::Slot(None))
1482 }
1483
1484 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1486 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1487 }
1488
1489 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1491 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None))
1492 }
1493
1494 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1496 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1497 }
1498
1499 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1501 PrincipalOrResourceConstraint::Is(entity_type)
1502 }
1503
1504 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1508 match self {
1509 PrincipalOrResourceConstraint::Any => Expr::val(true),
1510 PrincipalOrResourceConstraint::Eq(euid) => {
1511 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1512 }
1513 PrincipalOrResourceConstraint::In(euid) => {
1514 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1515 }
1516 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1517 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1518 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1519 ),
1520 PrincipalOrResourceConstraint::Is(entity_type) => {
1521 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1522 }
1523 }
1524 }
1525
1526 pub fn display(&self, v: PrincipalOrResource) -> String {
1530 match self {
1531 PrincipalOrResourceConstraint::In(euid) => {
1532 format!("{} in {}", v, euid.into_expr(v.into()))
1533 }
1534 PrincipalOrResourceConstraint::Eq(euid) => {
1535 format!("{} == {}", v, euid.into_expr(v.into()))
1536 }
1537 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1538 format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1539 }
1540 PrincipalOrResourceConstraint::Is(entity_type) => {
1541 format!("{} is {}", v, entity_type)
1542 }
1543 PrincipalOrResourceConstraint::Any => format!("{}", v),
1544 }
1545 }
1546
1547 pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1549 match self {
1550 PrincipalOrResourceConstraint::Any => None,
1551 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1552 PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None,
1553 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1554 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None,
1555 PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1556 PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None,
1557 PrincipalOrResourceConstraint::Is(_) => None,
1558 }
1559 }
1560
1561 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1563 self.get_euid()
1564 .into_iter()
1565 .map(|euid| euid.entity_type())
1566 .chain(match self {
1567 PrincipalOrResourceConstraint::Is(entity_type)
1568 | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1569 _ => None,
1570 })
1571 }
1572}
1573
1574#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1577pub enum ActionConstraint {
1578 Any,
1580 In(Vec<Arc<EntityUID>>),
1582 Eq(Arc<EntityUID>),
1584}
1585
1586impl std::fmt::Display for ActionConstraint {
1587 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1588 let render_euids =
1589 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1590 match self {
1591 ActionConstraint::Any => write!(f, "action"),
1592 ActionConstraint::In(euids) => {
1593 write!(f, "action in [{}]", render_euids(euids))
1594 }
1595 ActionConstraint::Eq(euid) => write!(f, "action == {}", euid),
1596 }
1597 }
1598}
1599
1600impl ActionConstraint {
1601 pub fn any() -> Self {
1603 ActionConstraint::Any
1604 }
1605
1606 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1608 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1609 }
1610
1611 pub fn is_eq(euid: EntityUID) -> Self {
1613 ActionConstraint::Eq(Arc::new(euid))
1614 }
1615
1616 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1617 Expr::set(euids.into_iter().map(Expr::val))
1618 }
1619
1620 pub fn as_expr(&self) -> Expr {
1622 match self {
1623 ActionConstraint::Any => Expr::val(true),
1624 ActionConstraint::In(euids) => Expr::is_in(
1625 Expr::var(Var::Action),
1626 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1627 ),
1628 ActionConstraint::Eq(euid) => {
1629 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1630 }
1631 }
1632 }
1633
1634 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1636 match self {
1637 ActionConstraint::Any => EntityIterator::None,
1638 ActionConstraint::In(euids) => {
1639 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1640 }
1641 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1642 }
1643 }
1644
1645 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1647 self.iter_euids().map(|euid| euid.entity_type())
1648 }
1649
1650 pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1653 match self {
1654 ActionConstraint::Any => Ok(self),
1655 ActionConstraint::In(ref euids) => {
1656 if let Some(euids) =
1657 NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1658 {
1659 Err(euids)
1660 } else {
1661 Ok(self)
1662 }
1663 }
1664 ActionConstraint::Eq(ref euid) => {
1665 if euid.is_action() {
1666 Ok(self)
1667 } else {
1668 Err(nonempty![euid.clone()])
1669 }
1670 }
1671 }
1672 }
1673}
1674
1675impl std::fmt::Display for StaticPolicy {
1676 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1677 for (k, v) in self.0.annotations.iter() {
1678 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1679 }
1680 write!(
1681 f,
1682 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1683 self.effect(),
1684 self.principal_constraint(),
1685 self.action_constraint(),
1686 self.resource_constraint(),
1687 self.non_scope_constraints()
1688 )
1689 }
1690}
1691
1692#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1694pub struct PolicyID(SmolStr);
1695
1696impl PolicyID {
1697 pub fn from_string(id: impl AsRef<str>) -> Self {
1699 Self(SmolStr::from(id.as_ref()))
1700 }
1701
1702 pub fn from_smolstr(id: SmolStr) -> Self {
1704 Self(id)
1705 }
1706}
1707
1708impl std::fmt::Display for PolicyID {
1709 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1710 write!(f, "{}", self.0.escape_debug())
1711 }
1712}
1713
1714impl AsRef<str> for PolicyID {
1715 fn as_ref(&self) -> &str {
1716 &self.0
1717 }
1718}
1719
1720#[cfg(feature = "arbitrary")]
1721impl arbitrary::Arbitrary<'_> for PolicyID {
1722 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1723 let s: String = u.arbitrary()?;
1724 Ok(PolicyID::from_string(s))
1725 }
1726 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1727 <String as arbitrary::Arbitrary>::size_hint(depth)
1728 }
1729}
1730
1731#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1733#[serde(rename_all = "camelCase")]
1734#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1735#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1736#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1737pub enum Effect {
1738 Permit,
1740 Forbid,
1742}
1743
1744impl std::fmt::Display for Effect {
1745 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1746 match self {
1747 Self::Permit => write!(f, "permit"),
1748 Self::Forbid => write!(f, "forbid"),
1749 }
1750 }
1751}
1752
1753enum EntityIterator<'a> {
1754 None,
1755 One(&'a EntityUID),
1756 Bunch(Vec<&'a EntityUID>),
1757}
1758
1759impl<'a> Iterator for EntityIterator<'a> {
1760 type Item = &'a EntityUID;
1761
1762 fn next(&mut self) -> Option<Self::Item> {
1763 match self {
1764 EntityIterator::None => None,
1765 EntityIterator::One(euid) => {
1766 let eptr = *euid;
1767 let mut ptr = EntityIterator::None;
1768 std::mem::swap(self, &mut ptr);
1769 Some(eptr)
1770 }
1771 EntityIterator::Bunch(v) => v.pop(),
1772 }
1773 }
1774}
1775
1776#[cfg(test)]
1777pub(crate) mod test_generators {
1778 use super::*;
1779
1780 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
1781 let euid = Arc::new(EntityUID::with_eid("test"));
1782 let v = vec![
1783 PrincipalOrResourceConstraint::any(),
1784 PrincipalOrResourceConstraint::is_eq(euid.clone()),
1785 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)),
1786 PrincipalOrResourceConstraint::is_in(euid),
1787 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)),
1788 ];
1789
1790 v.into_iter()
1791 }
1792
1793 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
1794 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
1795 }
1796
1797 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
1798 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
1799 }
1800
1801 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
1802 let euid: EntityUID = "Action::\"test\""
1803 .parse()
1804 .expect("Invalid action constraint euid");
1805 let v = vec![
1806 ActionConstraint::any(),
1807 ActionConstraint::is_eq(euid.clone()),
1808 ActionConstraint::is_in([euid.clone()]),
1809 ActionConstraint::is_in([euid.clone(), euid]),
1810 ];
1811
1812 v.into_iter()
1813 }
1814
1815 pub fn all_templates() -> impl Iterator<Item = Template> {
1816 let mut buf = vec![];
1817 let permit = PolicyID::from_string("permit");
1818 let forbid = PolicyID::from_string("forbid");
1819 for principal in all_principal_constraints() {
1820 for action in all_actions_constraints() {
1821 for resource in all_resource_constraints() {
1822 let permit = Template::new(
1823 permit.clone(),
1824 None,
1825 Annotations::new(),
1826 Effect::Permit,
1827 principal.clone(),
1828 action.clone(),
1829 resource.clone(),
1830 Expr::val(true),
1831 );
1832 let forbid = Template::new(
1833 forbid.clone(),
1834 None,
1835 Annotations::new(),
1836 Effect::Forbid,
1837 principal.clone(),
1838 action.clone(),
1839 resource.clone(),
1840 Expr::val(true),
1841 );
1842 buf.push(permit);
1843 buf.push(forbid);
1844 }
1845 }
1846 }
1847 buf.into_iter()
1848 }
1849}
1850
1851#[cfg(test)]
1852#[allow(clippy::indexing_slicing)]
1854#[allow(clippy::panic)]
1856mod test {
1857 use cool_asserts::assert_matches;
1858 use std::collections::HashSet;
1859
1860 use super::{test_generators::*, *};
1861 use crate::{
1862 parser::{
1863 parse_policy,
1864 test_utils::{expect_exactly_one_error, expect_some_error_matches},
1865 },
1866 test_utils::ExpectedErrorMessageBuilder,
1867 };
1868
1869 #[test]
1870 fn literal_and_borrowed() {
1871 for template in all_templates() {
1872 let t = Arc::new(template);
1873 let env = t
1874 .slots()
1875 .map(|slot| (slot.id, EntityUID::with_eid("eid")))
1876 .collect();
1877 let p = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
1878
1879 let b_literal = BorrowedLiteralPolicy::from(&p);
1880 let src = serde_json::to_string(&b_literal).expect("ser error");
1881 let literal: LiteralPolicy = serde_json::from_str(&src).expect("de error");
1882
1883 assert_eq!(b_literal.template_id, &literal.template_id);
1884 assert_eq!(b_literal.link_id, literal.link_id.as_ref());
1885 assert_eq!(b_literal.values, &literal.values);
1886 }
1887 }
1888
1889 #[test]
1890 fn template_roundtrip() {
1891 for template in all_templates() {
1892 template.check_invariant();
1893 let json = serde_json::to_string(&template).expect("Serialization Failed");
1894 let t2 = serde_json::from_str::<Template>(&json).expect("Deserialization failed");
1895 t2.check_invariant();
1896 assert_eq!(template, t2);
1897 }
1898 }
1899
1900 #[test]
1901 fn test_template_rebuild() {
1902 for template in all_templates() {
1903 let id = template.id().clone();
1904 let effect = template.effect();
1905 let p = template.principal_constraint().clone();
1906 let a = template.action_constraint().clone();
1907 let r = template.resource_constraint().clone();
1908 let non_scope = template.non_scope_constraints().clone();
1909 let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
1910 assert_eq!(template, t2);
1911 }
1912 }
1913
1914 #[test]
1915 fn test_static_policy_rebuild() {
1916 for template in all_templates() {
1917 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
1918 let id = ip.id().clone();
1919 let e = ip.effect();
1920 let anno = ip
1921 .annotations()
1922 .map(|(k, v)| (k.clone(), v.clone()))
1923 .collect();
1924 let p = ip.principal_constraint().clone();
1925 let a = ip.action_constraint().clone();
1926 let r = ip.resource_constraint().clone();
1927 let non_scope = ip.non_scope_constraints().clone();
1928 let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope)
1929 .expect("Policy Creation Failed");
1930 assert_eq!(ip, ip2);
1931 let (t2, inst) = Template::link_static_policy(ip2);
1932 assert!(inst.is_static());
1933 assert_eq!(&template, t2.as_ref());
1934 }
1935 }
1936 }
1937
1938 #[test]
1939 fn ir_binding_too_many() {
1940 let tid = PolicyID::from_string("tid");
1941 let iid = PolicyID::from_string("iid");
1942 let t = Arc::new(Template::new(
1943 tid,
1944 None,
1945 Annotations::new(),
1946 Effect::Forbid,
1947 PrincipalConstraint::is_eq_slot(),
1948 ActionConstraint::Any,
1949 ResourceConstraint::any(),
1950 Expr::val(true),
1951 ));
1952 let mut m = HashMap::new();
1953 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
1954 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
1955 assert_eq!(unbound_values, vec![SlotId::principal()]);
1956 assert_eq!(extra_values, vec![SlotId::resource()]);
1957 });
1958 }
1959
1960 #[test]
1961 fn ir_binding_too_few() {
1962 let tid = PolicyID::from_string("tid");
1963 let iid = PolicyID::from_string("iid");
1964 let t = Arc::new(Template::new(
1965 tid,
1966 None,
1967 Annotations::new(),
1968 Effect::Forbid,
1969 PrincipalConstraint::is_eq_slot(),
1970 ActionConstraint::Any,
1971 ResourceConstraint::is_in_slot(),
1972 Expr::val(true),
1973 ));
1974 assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
1975 assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
1976 assert_eq!(extra_values, vec![]);
1977 });
1978 let mut m = HashMap::new();
1979 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
1980 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
1981 assert_eq!(unbound_values, vec![SlotId::resource()]);
1982 assert_eq!(extra_values, vec![]);
1983 });
1984 }
1985
1986 #[test]
1987 fn ir_binding() {
1988 let tid = PolicyID::from_string("template");
1989 let iid = PolicyID::from_string("linked");
1990 let t = Arc::new(Template::new(
1991 tid,
1992 None,
1993 Annotations::new(),
1994 Effect::Permit,
1995 PrincipalConstraint::is_in_slot(),
1996 ActionConstraint::any(),
1997 ResourceConstraint::is_eq_slot(),
1998 Expr::val(true),
1999 ));
2000
2001 let mut m = HashMap::new();
2002 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2003 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2004
2005 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2006 assert_eq!(r.id(), &iid);
2007 assert_eq!(
2008 r.env().get(&SlotId::principal()),
2009 Some(&EntityUID::with_eid("theprincipal"))
2010 );
2011 assert_eq!(
2012 r.env().get(&SlotId::resource()),
2013 Some(&EntityUID::with_eid("theresource"))
2014 );
2015 }
2016
2017 #[test]
2018 fn isnt_template_implies_from_succeeds() {
2019 for template in all_templates() {
2020 if template.slots().count() == 0 {
2021 StaticPolicy::try_from(template).expect("Should succeed");
2022 }
2023 }
2024 }
2025
2026 #[test]
2027 fn is_template_implies_from_fails() {
2028 for template in all_templates() {
2029 if template.slots().count() != 0 {
2030 assert!(
2031 StaticPolicy::try_from(template.clone()).is_err(),
2032 "Following template did convert {template}"
2033 );
2034 }
2035 }
2036 }
2037
2038 #[test]
2039 fn non_template_iso() {
2040 for template in all_templates() {
2041 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2042 let (t2, _) = Template::link_static_policy(p);
2043 assert_eq!(&template, t2.as_ref());
2044 }
2045 }
2046 }
2047
2048 #[test]
2049 fn template_into_expr() {
2050 for template in all_templates() {
2051 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2052 let t: Template = template;
2053 assert_eq!(p.condition(), t.condition());
2054 assert_eq!(p.effect(), t.effect());
2055 }
2056 }
2057 }
2058
2059 #[test]
2060 fn template_por_iter() {
2061 let e = Arc::new(EntityUID::with_eid("eid"));
2062 assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2063 assert_eq!(
2064 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2065 Some(&e)
2066 );
2067 assert_eq!(
2068 PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(),
2069 None
2070 );
2071 assert_eq!(
2072 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2073 Some(&e)
2074 );
2075 assert_eq!(
2076 PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(),
2077 None
2078 );
2079 assert_eq!(
2080 PrincipalOrResourceConstraint::IsIn(
2081 Arc::new("T".parse().unwrap()),
2082 EntityReference::EUID(e.clone())
2083 )
2084 .get_euid(),
2085 Some(&e)
2086 );
2087 assert_eq!(
2088 PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2089 None
2090 );
2091 assert_eq!(
2092 PrincipalOrResourceConstraint::IsIn(
2093 Arc::new("T".parse().unwrap()),
2094 EntityReference::Slot(None)
2095 )
2096 .get_euid(),
2097 None
2098 );
2099 }
2100
2101 #[test]
2102 fn action_iter() {
2103 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2104 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2105 let v = a.iter_euids().collect::<Vec<_>>();
2106 assert_eq!(vec![&EntityUID::with_eid("test")], v);
2107 let a =
2108 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2109 let set = a.iter_euids().collect::<HashSet<_>>();
2110 let e1 = EntityUID::with_eid("test1");
2111 let e2 = EntityUID::with_eid("test2");
2112 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2113 assert_eq!(set, correct);
2114 }
2115
2116 #[test]
2117 fn test_iter_none() {
2118 let mut i = EntityIterator::None;
2119 assert_eq!(i.next(), None);
2120 }
2121
2122 #[test]
2123 fn test_iter_once() {
2124 let id = EntityUID::from_components(
2125 name::Name::parse_unqualified_name("s").unwrap().into(),
2126 entity::Eid::new("eid"),
2127 None,
2128 );
2129 let mut i = EntityIterator::One(&id);
2130 assert_eq!(i.next(), Some(&id));
2131 assert_eq!(i.next(), None);
2132 }
2133
2134 #[test]
2135 fn test_iter_mult() {
2136 let id1 = EntityUID::from_components(
2137 name::Name::parse_unqualified_name("s").unwrap().into(),
2138 entity::Eid::new("eid1"),
2139 None,
2140 );
2141 let id2 = EntityUID::from_components(
2142 name::Name::parse_unqualified_name("s").unwrap().into(),
2143 entity::Eid::new("eid2"),
2144 None,
2145 );
2146 let v = vec![&id1, &id2];
2147 let mut i = EntityIterator::Bunch(v);
2148 assert_eq!(i.next(), Some(&id2));
2149 assert_eq!(i.next(), Some(&id1));
2150 assert_eq!(i.next(), None)
2151 }
2152
2153 #[test]
2154 fn euid_into_expr() {
2155 let e = EntityReference::Slot(None);
2156 assert_eq!(
2157 e.into_expr(SlotId::principal()),
2158 Expr::slot(SlotId::principal())
2159 );
2160 let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2161 assert_eq!(
2162 e.into_expr(SlotId::principal()),
2163 Expr::val(EntityUID::with_eid("eid"))
2164 );
2165 }
2166
2167 #[test]
2168 fn por_constraint_display() {
2169 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None));
2170 let s = t.display(PrincipalOrResource::Principal);
2171 assert_eq!(s, "principal == ?principal");
2172 let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2173 EntityUID::with_eid("test"),
2174 )));
2175 let s = t.display(PrincipalOrResource::Principal);
2176 assert_eq!(s, "principal == test_entity_type::\"test\"");
2177 }
2178
2179 #[test]
2180 fn unexpected_templates() {
2181 let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2182 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2183 expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2184 "expected a static policy, got a template containing the slot ?principal"
2185 )
2186 .help("try removing the template slot(s) from this policy")
2187 .exactly_one_underline("?principal")
2188 .build()
2189 );
2190 });
2191
2192 let policy_str =
2193 r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2194 assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2195 expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2196 "expected a static policy, got a template containing the slot ?principal"
2197 )
2198 .help("try removing the template slot(s) from this policy")
2199 .exactly_one_underline("?principal")
2200 .build()
2201 );
2202 assert_eq!(e.len(), 2);
2203 });
2204 }
2205}