1use crate::ast::*;
18use crate::parser::Loc;
19use itertools::Itertools;
20use miette::Diagnostic;
21use serde::{Deserialize, Serialize};
22use smol_str::SmolStr;
23use std::collections::BTreeMap;
24use std::{collections::HashMap, sync::Arc};
25use thiserror::Error;
26
27#[cfg(feature = "wasm")]
28extern crate tsify;
29
30#[derive(Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
35#[serde(from = "TemplateBody")]
36#[serde(into = "TemplateBody")]
37pub struct Template {
38 body: TemplateBody,
39 slots: Vec<Slot>,
44}
45
46impl From<Template> for TemplateBody {
47 fn from(val: Template) -> Self {
48 val.body
49 }
50}
51
52impl Template {
53 #[cfg(test)]
55 pub fn check_invariant(&self) {
56 for slot in self.body.condition().slots() {
57 assert!(self.slots.contains(&slot));
58 }
59 for slot in self.slots() {
60 assert!(self.body.condition().slots().contains(slot));
61 }
62 }
63 pub fn new(
71 id: PolicyID,
72 loc: Option<Loc>,
73 annotations: Annotations,
74 effect: Effect,
75 principal_constraint: PrincipalConstraint,
76 action_constraint: ActionConstraint,
77 resource_constraint: ResourceConstraint,
78 non_scope_constraint: Expr,
79 ) -> Self {
80 let body = TemplateBody::new(
81 id,
82 loc,
83 annotations,
84 effect,
85 principal_constraint,
86 action_constraint,
87 resource_constraint,
88 non_scope_constraint,
89 );
90 Template::from(body)
93 }
94
95 pub fn new_shared(
97 id: PolicyID,
98 loc: Option<Loc>,
99 annotations: Arc<Annotations>,
100 effect: Effect,
101 principal_constraint: PrincipalConstraint,
102 action_constraint: ActionConstraint,
103 resource_constraint: ResourceConstraint,
104 non_scope_constraint: Arc<Expr>,
105 ) -> Self {
106 let body = TemplateBody::new_shared(
107 id,
108 loc,
109 annotations,
110 effect,
111 principal_constraint,
112 action_constraint,
113 resource_constraint,
114 non_scope_constraint,
115 );
116 Template::from(body)
119 }
120
121 pub fn principal_constraint(&self) -> &PrincipalConstraint {
123 self.body.principal_constraint()
124 }
125
126 pub fn action_constraint(&self) -> &ActionConstraint {
128 self.body.action_constraint()
129 }
130
131 pub fn resource_constraint(&self) -> &ResourceConstraint {
133 self.body.resource_constraint()
134 }
135
136 pub fn non_scope_constraints(&self) -> &Expr {
138 self.body.non_scope_constraints()
139 }
140
141 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
143 self.body.non_scope_constraints_arc()
144 }
145
146 pub fn id(&self) -> &PolicyID {
148 self.body.id()
149 }
150
151 pub fn new_id(&self, id: PolicyID) -> Self {
153 Template {
154 body: self.body.new_id(id),
155 slots: self.slots.clone(),
156 }
157 }
158
159 pub fn loc(&self) -> &Option<Loc> {
161 self.body.loc()
162 }
163
164 pub fn effect(&self) -> Effect {
166 self.body.effect()
167 }
168
169 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
171 self.body.annotation(key)
172 }
173
174 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
176 self.body.annotations()
177 }
178
179 pub fn annotations_arc(&self) -> &Arc<Annotations> {
181 self.body.annotations_arc()
182 }
183
184 pub fn condition(&self) -> Expr {
190 self.body.condition()
191 }
192
193 pub fn slots(&self) -> impl Iterator<Item = &Slot> {
195 self.slots.iter()
196 }
197
198 pub fn is_static(&self) -> bool {
203 self.slots.is_empty()
204 }
205
206 pub fn check_binding(
210 template: &Template,
211 values: &HashMap<SlotId, EntityUID>,
212 ) -> Result<(), LinkingError> {
213 let unbound = template
215 .slots
216 .iter()
217 .filter(|slot| !values.contains_key(&slot.id))
218 .collect::<Vec<_>>();
219
220 let extra = values
221 .iter()
222 .filter_map(|(slot, _)| {
223 if !template
224 .slots
225 .iter()
226 .any(|template_slot| template_slot.id == *slot)
227 {
228 Some(slot)
229 } else {
230 None
231 }
232 })
233 .collect::<Vec<_>>();
234
235 if unbound.is_empty() && extra.is_empty() {
236 Ok(())
237 } else {
238 Err(LinkingError::from_unbound_and_extras(
239 unbound.into_iter().map(|slot| slot.id),
240 extra.into_iter().copied(),
241 ))
242 }
243 }
244
245 pub fn link(
249 template: Arc<Template>,
250 new_id: PolicyID,
251 values: HashMap<SlotId, EntityUID>,
252 ) -> Result<Policy, LinkingError> {
253 Template::check_binding(&template, &values)
255 .map(|_| Policy::new(template, Some(new_id), values))
256 }
257
258 pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
261 let body: TemplateBody = p.into();
262 let t = Arc::new(Self {
266 body,
267 slots: vec![],
268 });
269 #[cfg(test)]
270 {
271 t.check_invariant();
272 }
273 let p = Policy::new(Arc::clone(&t), None, HashMap::new());
279 (t, p)
280 }
281}
282
283impl From<TemplateBody> for Template {
284 fn from(body: TemplateBody) -> Self {
285 let slots = body.condition().slots().collect::<Vec<_>>();
288 Self { body, slots }
289 }
290}
291
292impl std::fmt::Display for Template {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 write!(f, "{}", self.body)
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
300pub enum LinkingError {
301 #[error("{}", describe_arity_error(.unbound_values, .extra_values))]
304 ArityError {
305 unbound_values: Vec<SlotId>,
307 extra_values: Vec<SlotId>,
309 },
310
311 #[error("failed to find a template with id `{id}`")]
313 NoSuchTemplate {
314 id: PolicyID,
316 },
317
318 #[error("template-linked policy id `{id}` conflicts with an existing policy id")]
320 PolicyIdConflict {
321 id: PolicyID,
323 },
324}
325
326impl LinkingError {
327 fn from_unbound_and_extras(
328 unbound: impl Iterator<Item = SlotId>,
329 extra: impl Iterator<Item = SlotId>,
330 ) -> Self {
331 Self::ArityError {
332 unbound_values: unbound.collect(),
333 extra_values: extra.collect(),
334 }
335 }
336}
337
338fn describe_arity_error(unbound_values: &[SlotId], extra_values: &[SlotId]) -> String {
339 match (unbound_values.len(), extra_values.len()) {
340 #[allow(clippy::unreachable)]
342 (0,0) => unreachable!(),
343 (_unbound, 0) => format!("the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
344 (0, _extra) => format!("the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
345 (_unbound, _extra) => format!("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(","))
346 }
347}
348
349#[derive(Debug, Clone, Eq, PartialEq)]
357pub struct Policy {
358 template: Arc<Template>,
360 link: Option<PolicyID>,
363 values: HashMap<SlotId, EntityUID>,
369}
370
371impl Policy {
372 fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
376 #[cfg(test)]
377 {
378 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
379 }
380 Self {
386 template,
387 link: link_id,
388 values,
389 }
390 }
391
392 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID, loc: Option<Loc>) -> Self {
394 Self::from_when_clause_annos(
395 effect,
396 Arc::new(when),
397 id,
398 loc,
399 Arc::new(Annotations::default()),
400 )
401 }
402
403 pub fn from_when_clause_annos(
405 effect: Effect,
406 when: Arc<Expr>,
407 id: PolicyID,
408 loc: Option<Loc>,
409 annotations: Arc<Annotations>,
410 ) -> Self {
411 let t = Template::new_shared(
412 id,
413 loc,
414 annotations,
415 effect,
416 PrincipalConstraint::any(),
417 ActionConstraint::any(),
418 ResourceConstraint::any(),
419 when,
420 );
421 Self::new(Arc::new(t), None, SlotEnv::new())
422 }
423
424 pub fn template(&self) -> &Template {
426 &self.template
427 }
428
429 pub(crate) fn template_arc(&self) -> Arc<Template> {
431 Arc::clone(&self.template)
432 }
433
434 pub fn effect(&self) -> Effect {
436 self.template.effect()
437 }
438
439 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
441 self.template.annotation(key)
442 }
443
444 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
446 self.template.annotations()
447 }
448
449 pub fn annotations_arc(&self) -> &Arc<Annotations> {
451 self.template.annotations_arc()
452 }
453
454 pub fn principal_constraint(&self) -> PrincipalConstraint {
460 let constraint = self.template.principal_constraint().clone();
461 match self.values.get(&SlotId::principal()) {
462 None => constraint,
463 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
464 }
465 }
466
467 pub fn action_constraint(&self) -> &ActionConstraint {
469 self.template.action_constraint()
470 }
471
472 pub fn resource_constraint(&self) -> ResourceConstraint {
478 let constraint = self.template.resource_constraint().clone();
479 match self.values.get(&SlotId::resource()) {
480 None => constraint,
481 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
482 }
483 }
484
485 pub fn non_scope_constraints(&self) -> &Expr {
487 self.template.non_scope_constraints()
488 }
489
490 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
492 self.template.non_scope_constraints_arc()
493 }
494
495 pub fn condition(&self) -> Expr {
497 self.template.condition()
498 }
499
500 pub fn env(&self) -> &SlotEnv {
503 &self.values
504 }
505
506 pub fn id(&self) -> &PolicyID {
508 self.link.as_ref().unwrap_or_else(|| self.template.id())
509 }
510
511 pub fn new_id(&self, id: PolicyID) -> Self {
513 match self.link {
514 None => Policy {
515 template: Arc::new(self.template.new_id(id)),
516 link: None,
517 values: self.values.clone(),
518 },
519 Some(_) => Policy {
520 template: self.template.clone(),
521 link: Some(id),
522 values: self.values.clone(),
523 },
524 }
525 }
526
527 pub fn loc(&self) -> &Option<Loc> {
529 self.template.loc()
530 }
531
532 pub fn is_static(&self) -> bool {
534 self.link.is_none()
535 }
536}
537
538impl std::fmt::Display for Policy {
539 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
540 if self.is_static() {
541 write!(f, "{}", self.template())
542 } else {
543 write!(
544 f,
545 "Template Instance of {}, slots: [{}]",
546 self.template().id(),
547 display_slot_env(self.env())
548 )
549 }
550 }
551}
552
553pub type SlotEnv = HashMap<SlotId, EntityUID>;
555
556#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
559pub struct LiteralPolicy {
560 template_id: PolicyID,
562 link_id: Option<PolicyID>,
566 values: SlotEnv,
568}
569
570#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
572pub struct BorrowedLiteralPolicy<'a> {
573 template_id: &'a PolicyID,
575 link_id: Option<&'a PolicyID>,
579 values: &'a SlotEnv,
581}
582
583impl<'a> From<&'a Policy> for BorrowedLiteralPolicy<'a> {
584 fn from(p: &'a Policy) -> Self {
585 Self {
586 template_id: p.template.id(),
587 link_id: p.link.as_ref(),
588 values: &p.values,
589 }
590 }
591}
592
593impl std::hash::Hash for LiteralPolicy {
596 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
597 self.template_id.hash(state);
598 let mut buf = self.values.iter().collect::<Vec<_>>();
600 buf.sort();
601 for (id, euid) in buf {
602 id.hash(state);
603 euid.hash(state);
604 }
605 }
606}
607
608impl std::cmp::PartialEq for LiteralPolicy {
609 fn eq(&self, other: &Self) -> bool {
610 self.template_id() == other.template_id()
611 && self.link_id == other.link_id
612 && self.values == other.values
613 }
614}
615
616#[cfg(test)]
618mod hashing_tests {
619 use std::{
620 collections::hash_map::DefaultHasher,
621 hash::{Hash, Hasher},
622 };
623
624 use super::*;
625
626 fn compute_hash(ir: LiteralPolicy) -> u64 {
627 let mut s = DefaultHasher::new();
628 ir.hash(&mut s);
629 s.finish()
630 }
631
632 fn build_template_linked_policy() -> LiteralPolicy {
633 let mut map = HashMap::new();
634 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
635 LiteralPolicy {
636 template_id: PolicyID::from_string("template"),
637 link_id: Some(PolicyID::from_string("id")),
638 values: map,
639 }
640 }
641
642 #[test]
643 fn hash_property_instances() {
644 let a = build_template_linked_policy();
645 let b = build_template_linked_policy();
646 assert_eq!(a, b);
647 assert_eq!(compute_hash(a), compute_hash(b));
648 }
649}
650#[derive(Debug, Diagnostic, Error)]
658pub enum ReificationError {
659 #[error("the id linked to does not exist")]
661 NoSuchTemplate(PolicyID),
662 #[error(transparent)]
664 #[diagnostic(transparent)]
665 Instantiation(#[from] LinkingError),
666}
667
668impl LiteralPolicy {
669 pub fn reify(
674 self,
675 templates: &HashMap<PolicyID, Arc<Template>>,
676 ) -> Result<Policy, ReificationError> {
677 let template = templates
678 .get(&self.template_id)
679 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
680 Template::check_binding(template, &self.values).map_err(ReificationError::Instantiation)?;
682 Ok(Policy::new(template.clone(), self.link_id, self.values))
683 }
684
685 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
687 self.values.get(id)
688 }
689
690 pub fn id(&self) -> &PolicyID {
693 self.link_id.as_ref().unwrap_or(&self.template_id)
694 }
695
696 pub fn template_id(&self) -> &PolicyID {
698 &self.template_id
699 }
700
701 pub fn is_static(&self) -> bool {
703 self.link_id.is_none()
704 }
705}
706
707fn display_slot_env(env: &SlotEnv) -> String {
708 env.iter()
709 .map(|(slot, value)| format!("{slot} -> {value}"))
710 .join(",")
711}
712
713impl std::fmt::Display for LiteralPolicy {
714 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
715 if self.is_static() {
716 write!(f, "Static policy w/ ID {}", self.template_id())
717 } else {
718 write!(
719 f,
720 "Template linked policy of {}, slots: [{}]",
721 self.template_id(),
722 display_slot_env(&self.values),
723 )
724 }
725 }
726}
727
728impl From<Policy> for LiteralPolicy {
729 fn from(p: Policy) -> Self {
730 Self {
731 template_id: p.template.id().clone(),
732 link_id: p.link,
733 values: p.values,
734 }
735 }
736}
737
738#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
742pub struct StaticPolicy(TemplateBody);
743
744impl StaticPolicy {
745 pub fn id(&self) -> &PolicyID {
747 self.0.id()
748 }
749
750 pub fn new_id(&self, id: PolicyID) -> Self {
752 StaticPolicy(self.0.new_id(id))
753 }
754
755 pub fn effect(&self) -> Effect {
757 self.0.effect()
758 }
759
760 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
762 self.0.annotation(key)
763 }
764
765 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
767 self.0.annotations()
768 }
769
770 pub fn principal_constraint(&self) -> &PrincipalConstraint {
772 self.0.principal_constraint()
773 }
774
775 pub fn principal_constraint_expr(&self) -> Expr {
779 self.0.principal_constraint_expr()
780 }
781
782 pub fn action_constraint(&self) -> &ActionConstraint {
784 self.0.action_constraint()
785 }
786
787 pub fn action_constraint_expr(&self) -> Expr {
791 self.0.action_constraint_expr()
792 }
793
794 pub fn resource_constraint(&self) -> &ResourceConstraint {
796 self.0.resource_constraint()
797 }
798
799 pub fn resource_constraint_expr(&self) -> Expr {
803 self.0.resource_constraint_expr()
804 }
805
806 pub fn non_scope_constraints(&self) -> &Expr {
811 self.0.non_scope_constraints()
812 }
813
814 pub fn condition(&self) -> Expr {
820 self.0.condition()
821 }
822
823 pub fn new(
825 id: PolicyID,
826 loc: Option<Loc>,
827 annotations: Annotations,
828 effect: Effect,
829 principal_constraint: PrincipalConstraint,
830 action_constraint: ActionConstraint,
831 resource_constraint: ResourceConstraint,
832 non_scope_constraints: Expr,
833 ) -> Result<Self, UnexpectedSlotError> {
834 let body = TemplateBody::new(
835 id,
836 loc,
837 annotations,
838 effect,
839 principal_constraint,
840 action_constraint,
841 resource_constraint,
842 non_scope_constraints,
843 );
844 let first_slot = body.condition().slots().next();
845 match first_slot {
847 Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
848 None => Ok(Self(body)),
849 }
850 }
851}
852
853impl TryFrom<Template> for StaticPolicy {
854 type Error = UnexpectedSlotError;
855
856 fn try_from(value: Template) -> Result<Self, Self::Error> {
857 let o = value.slots().next().cloned();
859 match o {
860 Some(slot_id) => Err(Self::Error::FoundSlot(slot_id)),
861 None => Ok(Self(value.body)),
862 }
863 }
864}
865
866impl From<StaticPolicy> for Policy {
867 fn from(inline: StaticPolicy) -> Policy {
868 let (_, policy) = Template::link_static_policy(inline);
869 policy
870 }
871}
872
873impl From<StaticPolicy> for Arc<Template> {
874 fn from(p: StaticPolicy) -> Self {
875 let (t, _) = Template::link_static_policy(p);
876 t
877 }
878}
879
880#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
883pub struct TemplateBody {
884 id: PolicyID,
886 loc: Option<Loc>,
888 annotations: Arc<Annotations>,
892 effect: Effect,
894 principal_constraint: PrincipalConstraint,
898 action_constraint: ActionConstraint,
902 resource_constraint: ResourceConstraint,
906 non_scope_constraints: Arc<Expr>,
911}
912
913impl TemplateBody {
914 pub fn id(&self) -> &PolicyID {
916 &self.id
917 }
918
919 pub fn loc(&self) -> &Option<Loc> {
921 &self.loc
922 }
923
924 pub fn new_id(&self, id: PolicyID) -> Self {
926 let mut new = self.clone();
927 new.id = id;
928 new
929 }
930
931 pub fn effect(&self) -> Effect {
933 self.effect
934 }
935
936 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
938 self.annotations.get(key)
939 }
940
941 pub fn annotations_arc(&self) -> &Arc<Annotations> {
943 &self.annotations
944 }
945
946 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
948 self.annotations.iter()
949 }
950
951 pub fn principal_constraint(&self) -> &PrincipalConstraint {
953 &self.principal_constraint
954 }
955
956 pub fn principal_constraint_expr(&self) -> Expr {
960 self.principal_constraint.as_expr()
961 }
962
963 pub fn action_constraint(&self) -> &ActionConstraint {
965 &self.action_constraint
966 }
967
968 pub fn action_constraint_expr(&self) -> Expr {
972 self.action_constraint.as_expr()
973 }
974
975 pub fn resource_constraint(&self) -> &ResourceConstraint {
977 &self.resource_constraint
978 }
979
980 pub fn resource_constraint_expr(&self) -> Expr {
984 self.resource_constraint.as_expr()
985 }
986
987 pub fn non_scope_constraints(&self) -> &Expr {
992 &self.non_scope_constraints
993 }
994
995 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
997 &self.non_scope_constraints
998 }
999
1000 pub fn condition(&self) -> Expr {
1006 Expr::and(
1007 Expr::and(
1008 Expr::and(
1009 self.principal_constraint_expr(),
1010 self.action_constraint_expr(),
1011 )
1012 .with_maybe_source_loc(self.loc.clone()),
1013 self.resource_constraint_expr(),
1014 )
1015 .with_maybe_source_loc(self.loc.clone()),
1016 self.non_scope_constraints.as_ref().clone(),
1017 )
1018 .with_maybe_source_loc(self.loc.clone())
1019 }
1020
1021 pub fn new_shared(
1023 id: PolicyID,
1024 loc: Option<Loc>,
1025 annotations: Arc<Annotations>,
1026 effect: Effect,
1027 principal_constraint: PrincipalConstraint,
1028 action_constraint: ActionConstraint,
1029 resource_constraint: ResourceConstraint,
1030 non_scope_constraints: Arc<Expr>,
1031 ) -> Self {
1032 Self {
1033 id,
1034 loc,
1035 annotations,
1036 effect,
1037 principal_constraint,
1038 action_constraint,
1039 resource_constraint,
1040 non_scope_constraints,
1041 }
1042 }
1043
1044 pub fn new(
1046 id: PolicyID,
1047 loc: Option<Loc>,
1048 annotations: Annotations,
1049 effect: Effect,
1050 principal_constraint: PrincipalConstraint,
1051 action_constraint: ActionConstraint,
1052 resource_constraint: ResourceConstraint,
1053 non_scope_constraints: Expr,
1054 ) -> Self {
1055 Self {
1056 id,
1057 loc,
1058 annotations: Arc::new(annotations),
1059 effect,
1060 principal_constraint,
1061 action_constraint,
1062 resource_constraint,
1063 non_scope_constraints: Arc::new(non_scope_constraints),
1064 }
1065 }
1066}
1067
1068impl From<StaticPolicy> for TemplateBody {
1069 fn from(p: StaticPolicy) -> Self {
1070 p.0
1071 }
1072}
1073
1074impl std::fmt::Display for TemplateBody {
1075 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1076 for (k, v) in self.annotations.iter() {
1077 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1078 }
1079 write!(
1080 f,
1081 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1082 self.effect(),
1083 self.principal_constraint(),
1084 self.action_constraint(),
1085 self.resource_constraint(),
1086 self.non_scope_constraints()
1087 )
1088 }
1089}
1090
1091#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1093pub struct Annotations(BTreeMap<AnyId, Annotation>);
1094
1095impl Annotations {
1096 pub fn new() -> Self {
1098 Self(BTreeMap::new())
1099 }
1100
1101 pub fn get(&self, key: &AnyId) -> Option<&Annotation> {
1103 self.0.get(key)
1104 }
1105
1106 pub fn iter(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1108 self.0.iter()
1109 }
1110}
1111
1112#[derive(Debug)]
1114pub struct IntoIter(std::collections::btree_map::IntoIter<AnyId, Annotation>);
1115
1116impl Iterator for IntoIter {
1117 type Item = (AnyId, Annotation);
1118
1119 fn next(&mut self) -> Option<Self::Item> {
1120 self.0.next()
1121 }
1122}
1123
1124impl IntoIterator for Annotations {
1125 type Item = (AnyId, Annotation);
1126
1127 type IntoIter = IntoIter;
1128
1129 fn into_iter(self) -> Self::IntoIter {
1130 IntoIter(self.0.into_iter())
1131 }
1132}
1133
1134impl Default for Annotations {
1135 fn default() -> Self {
1136 Self::new()
1137 }
1138}
1139
1140impl FromIterator<(AnyId, Annotation)> for Annotations {
1141 fn from_iter<T: IntoIterator<Item = (AnyId, Annotation)>>(iter: T) -> Self {
1142 Self(BTreeMap::from_iter(iter))
1143 }
1144}
1145
1146impl From<BTreeMap<AnyId, Annotation>> for Annotations {
1147 fn from(value: BTreeMap<AnyId, Annotation>) -> Self {
1148 Self(value)
1149 }
1150}
1151
1152#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug, PartialOrd, Ord)]
1154pub struct Annotation {
1155 pub val: SmolStr,
1157 pub loc: Option<Loc>,
1160}
1161
1162impl AsRef<str> for Annotation {
1163 fn as_ref(&self) -> &str {
1164 &self.val
1165 }
1166}
1167
1168#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1170pub struct PrincipalConstraint {
1171 pub(crate) constraint: PrincipalOrResourceConstraint,
1172}
1173
1174impl PrincipalConstraint {
1175 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1177 PrincipalConstraint { constraint }
1178 }
1179
1180 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1182 &self.constraint
1183 }
1184
1185 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1187 self.constraint
1188 }
1189
1190 pub fn as_expr(&self) -> Expr {
1192 self.constraint.as_expr(PrincipalOrResource::Principal)
1193 }
1194
1195 pub fn any() -> Self {
1197 PrincipalConstraint {
1198 constraint: PrincipalOrResourceConstraint::any(),
1199 }
1200 }
1201
1202 pub fn is_eq(euid: EntityUID) -> Self {
1204 PrincipalConstraint {
1205 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1206 }
1207 }
1208
1209 pub fn is_eq_slot() -> Self {
1211 Self {
1212 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1213 }
1214 }
1215
1216 pub fn is_in(euid: EntityUID) -> Self {
1218 PrincipalConstraint {
1219 constraint: PrincipalOrResourceConstraint::is_in(euid),
1220 }
1221 }
1222
1223 pub fn is_in_slot() -> Self {
1225 Self {
1226 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1227 }
1228 }
1229
1230 pub fn is_entity_type_in_slot(entity_type: Name) -> Self {
1232 Self {
1233 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1234 }
1235 }
1236
1237 pub fn is_entity_type_in(entity_type: Name, in_entity: EntityUID) -> Self {
1239 Self {
1240 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1241 }
1242 }
1243
1244 pub fn is_entity_type(entity_type: Name) -> Self {
1246 Self {
1247 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1248 }
1249 }
1250
1251 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1253 match self.constraint {
1254 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1255 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1256 },
1257 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1258 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1259 },
1260 _ => self,
1261 }
1262 }
1263}
1264
1265impl std::fmt::Display for PrincipalConstraint {
1266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1267 write!(
1268 f,
1269 "{}",
1270 self.constraint.display(PrincipalOrResource::Principal)
1271 )
1272 }
1273}
1274
1275#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1277pub struct ResourceConstraint {
1278 pub(crate) constraint: PrincipalOrResourceConstraint,
1279}
1280
1281impl ResourceConstraint {
1282 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1284 ResourceConstraint { constraint }
1285 }
1286
1287 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1289 &self.constraint
1290 }
1291
1292 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1294 self.constraint
1295 }
1296
1297 pub fn as_expr(&self) -> Expr {
1299 self.constraint.as_expr(PrincipalOrResource::Resource)
1300 }
1301
1302 pub fn any() -> Self {
1304 ResourceConstraint {
1305 constraint: PrincipalOrResourceConstraint::any(),
1306 }
1307 }
1308
1309 pub fn is_eq(euid: EntityUID) -> Self {
1311 ResourceConstraint {
1312 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1313 }
1314 }
1315
1316 pub fn is_eq_slot() -> Self {
1318 Self {
1319 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1320 }
1321 }
1322
1323 pub fn is_in_slot() -> Self {
1325 Self {
1326 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1327 }
1328 }
1329
1330 pub fn is_in(euid: EntityUID) -> Self {
1332 ResourceConstraint {
1333 constraint: PrincipalOrResourceConstraint::is_in(euid),
1334 }
1335 }
1336
1337 pub fn is_entity_type_in_slot(entity_type: Name) -> Self {
1339 Self {
1340 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1341 }
1342 }
1343
1344 pub fn is_entity_type_in(entity_type: Name, in_entity: EntityUID) -> Self {
1346 Self {
1347 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1348 }
1349 }
1350
1351 pub fn is_entity_type(entity_type: Name) -> Self {
1353 Self {
1354 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1355 }
1356 }
1357
1358 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1360 match self.constraint {
1361 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1362 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1363 },
1364 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1365 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1366 },
1367 _ => self,
1368 }
1369 }
1370}
1371
1372impl std::fmt::Display for ResourceConstraint {
1373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1374 write!(
1375 f,
1376 "{}",
1377 self.as_inner().display(PrincipalOrResource::Resource)
1378 )
1379 }
1380}
1381
1382#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1384pub enum EntityReference {
1385 EUID(Arc<EntityUID>),
1387 Slot,
1389}
1390
1391impl EntityReference {
1392 pub fn euid(euid: EntityUID) -> Self {
1394 Self::EUID(Arc::new(euid))
1395 }
1396
1397 pub fn into_expr(&self, slot: SlotId) -> Expr {
1403 match self {
1404 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1405 EntityReference::Slot => Expr::slot(slot),
1406 }
1407 }
1408}
1409
1410#[derive(Debug, Clone, PartialEq, Error)]
1412pub enum UnexpectedSlotError {
1413 #[error("found slot `{}` where slots are not allowed", .0.id)]
1415 FoundSlot(Slot),
1416}
1417
1418impl Diagnostic for UnexpectedSlotError {
1419 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1420 match self {
1421 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1422 let label = miette::LabeledSpan::underline(loc.span);
1423 Box::new(std::iter::once(label)) as Box<dyn Iterator<Item = miette::LabeledSpan>>
1424 }),
1425 }
1426 }
1427}
1428
1429impl From<EntityUID> for EntityReference {
1430 fn from(euid: EntityUID) -> Self {
1431 Self::EUID(Arc::new(euid))
1432 }
1433}
1434
1435#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
1437#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1438pub enum PrincipalOrResource {
1439 Principal,
1441 Resource,
1443}
1444
1445impl std::fmt::Display for PrincipalOrResource {
1446 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1447 let v = Var::from(*self);
1448 write!(f, "{v}")
1449 }
1450}
1451
1452impl TryFrom<Var> for PrincipalOrResource {
1453 type Error = Var;
1454
1455 fn try_from(value: Var) -> Result<Self, Self::Error> {
1456 match value {
1457 Var::Principal => Ok(Self::Principal),
1458 Var::Action => Err(Var::Action),
1459 Var::Resource => Ok(Self::Resource),
1460 Var::Context => Err(Var::Context),
1461 }
1462 }
1463}
1464
1465#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1468pub enum PrincipalOrResourceConstraint {
1469 Any,
1471 In(EntityReference),
1473 Eq(EntityReference),
1475 Is(Name),
1477 IsIn(Name, EntityReference),
1479}
1480
1481impl PrincipalOrResourceConstraint {
1482 pub fn any() -> Self {
1484 PrincipalOrResourceConstraint::Any
1485 }
1486
1487 pub fn is_eq(euid: EntityUID) -> Self {
1489 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1490 }
1491
1492 pub fn is_eq_slot() -> Self {
1494 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
1495 }
1496
1497 pub fn is_in_slot() -> Self {
1499 PrincipalOrResourceConstraint::In(EntityReference::Slot)
1500 }
1501
1502 pub fn is_in(euid: EntityUID) -> Self {
1504 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1505 }
1506
1507 pub fn is_entity_type_in_slot(entity_type: Name) -> Self {
1509 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot)
1510 }
1511
1512 pub fn is_entity_type_in(entity_type: Name, in_entity: EntityUID) -> Self {
1514 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1515 }
1516
1517 pub fn is_entity_type(entity_type: Name) -> Self {
1519 PrincipalOrResourceConstraint::Is(entity_type)
1520 }
1521
1522 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1526 match self {
1527 PrincipalOrResourceConstraint::Any => Expr::val(true),
1528 PrincipalOrResourceConstraint::Eq(euid) => {
1529 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1530 }
1531 PrincipalOrResourceConstraint::In(euid) => {
1532 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1533 }
1534 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1535 Expr::is_entity_type(Expr::var(v.into()), entity_type.clone()),
1536 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1537 ),
1538 PrincipalOrResourceConstraint::Is(entity_type) => {
1539 Expr::is_entity_type(Expr::var(v.into()), entity_type.clone())
1540 }
1541 }
1542 }
1543
1544 pub fn display(&self, v: PrincipalOrResource) -> String {
1548 match self {
1549 PrincipalOrResourceConstraint::In(euid) => {
1550 format!("{} in {}", v, euid.into_expr(v.into()))
1551 }
1552 PrincipalOrResourceConstraint::Eq(euid) => {
1553 format!("{} == {}", v, euid.into_expr(v.into()))
1554 }
1555 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1556 format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1557 }
1558 PrincipalOrResourceConstraint::Is(entity_type) => {
1559 format!("{} is {}", v, entity_type)
1560 }
1561 PrincipalOrResourceConstraint::Any => format!("{}", v),
1562 }
1563 }
1564
1565 pub fn iter_euids(&'_ self) -> impl Iterator<Item = &'_ EntityUID> {
1567 match self {
1568 PrincipalOrResourceConstraint::Any => EntityIterator::None,
1569 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => {
1570 EntityIterator::One(euid)
1571 }
1572 PrincipalOrResourceConstraint::In(EntityReference::Slot) => EntityIterator::None,
1573 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => {
1574 EntityIterator::One(euid)
1575 }
1576 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => EntityIterator::None,
1577 PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => {
1578 EntityIterator::One(euid)
1579 }
1580 PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot) => EntityIterator::None,
1581 PrincipalOrResourceConstraint::Is(_) => EntityIterator::None,
1582 }
1583 }
1584
1585 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ Name> {
1589 self.iter_euids()
1590 .filter_map(|euid| match euid.entity_type() {
1591 EntityType::Specified(name) => Some(name),
1592 EntityType::Unspecified => None,
1593 })
1594 .chain(match self {
1595 PrincipalOrResourceConstraint::Is(entity_type)
1596 | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type),
1597 _ => None,
1598 })
1599 }
1600}
1601
1602#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1605pub enum ActionConstraint {
1606 Any,
1608 In(Vec<Arc<EntityUID>>),
1610 Eq(Arc<EntityUID>),
1612}
1613
1614impl std::fmt::Display for ActionConstraint {
1615 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1616 let render_euids =
1617 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1618 match self {
1619 ActionConstraint::Any => write!(f, "action"),
1620 ActionConstraint::In(euids) => {
1621 write!(f, "action in [{}]", render_euids(euids))
1622 }
1623 ActionConstraint::Eq(euid) => write!(f, "action == {}", euid),
1624 }
1625 }
1626}
1627
1628impl ActionConstraint {
1629 pub fn any() -> Self {
1631 ActionConstraint::Any
1632 }
1633
1634 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1636 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1637 }
1638
1639 pub fn is_eq(euid: EntityUID) -> Self {
1641 ActionConstraint::Eq(Arc::new(euid))
1642 }
1643
1644 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1645 Expr::set(euids.into_iter().map(Expr::val))
1646 }
1647
1648 pub fn as_expr(&self) -> Expr {
1650 match self {
1651 ActionConstraint::Any => Expr::val(true),
1652 ActionConstraint::In(euids) => Expr::is_in(
1653 Expr::var(Var::Action),
1654 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1655 ),
1656 ActionConstraint::Eq(euid) => {
1657 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1658 }
1659 }
1660 }
1661
1662 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1664 match self {
1665 ActionConstraint::Any => EntityIterator::None,
1666 ActionConstraint::In(euids) => {
1667 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1668 }
1669 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1670 }
1671 }
1672
1673 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ Name> {
1677 self.iter_euids()
1678 .filter_map(|euid| match euid.entity_type() {
1679 EntityType::Specified(name) => Some(name),
1680 EntityType::Unspecified => None,
1681 })
1682 }
1683}
1684
1685impl std::fmt::Display for StaticPolicy {
1686 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1687 for (k, v) in self.0.annotations.iter() {
1688 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1689 }
1690 write!(
1691 f,
1692 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1693 self.effect(),
1694 self.principal_constraint(),
1695 self.action_constraint(),
1696 self.resource_constraint(),
1697 self.non_scope_constraints()
1698 )
1699 }
1700}
1701
1702#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1704pub struct PolicyID(SmolStr);
1705
1706impl PolicyID {
1707 pub fn from_string(id: impl AsRef<str>) -> Self {
1709 Self(SmolStr::from(id.as_ref()))
1710 }
1711
1712 pub fn from_smolstr(id: SmolStr) -> Self {
1714 Self(id)
1715 }
1716}
1717
1718impl std::fmt::Display for PolicyID {
1719 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1720 write!(f, "{}", self.0.escape_debug())
1721 }
1722}
1723
1724impl AsRef<str> for PolicyID {
1725 fn as_ref(&self) -> &str {
1726 &self.0
1727 }
1728}
1729
1730#[cfg(feature = "arbitrary")]
1731impl<'u> arbitrary::Arbitrary<'u> for PolicyID {
1732 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1733 let s: String = u.arbitrary()?;
1734 Ok(PolicyID::from_string(s))
1735 }
1736 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1737 <String as arbitrary::Arbitrary>::size_hint(depth)
1738 }
1739}
1740
1741#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1743#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1744#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1745#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1746pub enum Effect {
1747 #[serde(rename = "permit")]
1749 Permit,
1750 #[serde(rename = "forbid")]
1752 Forbid,
1753}
1754
1755impl std::fmt::Display for Effect {
1756 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1757 match self {
1758 Self::Permit => write!(f, "permit"),
1759 Self::Forbid => write!(f, "forbid"),
1760 }
1761 }
1762}
1763
1764enum EntityIterator<'a> {
1765 None,
1766 One(&'a EntityUID),
1767 Bunch(Vec<&'a EntityUID>),
1768}
1769
1770impl<'a> Iterator for EntityIterator<'a> {
1771 type Item = &'a EntityUID;
1772
1773 fn next(&mut self) -> Option<Self::Item> {
1774 match self {
1775 EntityIterator::None => None,
1776 EntityIterator::One(euid) => {
1777 let eptr = *euid;
1778 let mut ptr = EntityIterator::None;
1779 std::mem::swap(self, &mut ptr);
1780 Some(eptr)
1781 }
1782 EntityIterator::Bunch(v) => v.pop(),
1783 }
1784 }
1785}
1786
1787#[cfg(test)]
1788pub mod test_generators {
1789 use super::*;
1790
1791 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
1792 let euid = EntityUID::with_eid("test");
1793 let v = vec![
1794 PrincipalOrResourceConstraint::any(),
1795 PrincipalOrResourceConstraint::is_eq(euid.clone()),
1796 PrincipalOrResourceConstraint::Eq(EntityReference::Slot),
1797 PrincipalOrResourceConstraint::is_in(euid),
1798 PrincipalOrResourceConstraint::In(EntityReference::Slot),
1799 ];
1800
1801 v.into_iter()
1802 }
1803
1804 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
1805 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
1806 }
1807
1808 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
1809 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
1810 }
1811
1812 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
1813 let euid: EntityUID = "Action::\"test\""
1814 .parse()
1815 .expect("Invalid action constraint euid");
1816 let v = vec![
1817 ActionConstraint::any(),
1818 ActionConstraint::is_eq(euid.clone()),
1819 ActionConstraint::is_in([euid.clone()]),
1820 ActionConstraint::is_in([euid.clone(), euid]),
1821 ];
1822
1823 v.into_iter()
1824 }
1825
1826 pub fn all_templates() -> impl Iterator<Item = Template> {
1827 let mut buf = vec![];
1828 let permit = PolicyID::from_string("permit");
1829 let forbid = PolicyID::from_string("forbid");
1830 for principal in all_principal_constraints() {
1831 for action in all_actions_constraints() {
1832 for resource in all_resource_constraints() {
1833 let permit = Template::new(
1834 permit.clone(),
1835 None,
1836 Annotations::new(),
1837 Effect::Permit,
1838 principal.clone(),
1839 action.clone(),
1840 resource.clone(),
1841 Expr::val(true),
1842 );
1843 let forbid = Template::new(
1844 forbid.clone(),
1845 None,
1846 Annotations::new(),
1847 Effect::Forbid,
1848 principal.clone(),
1849 action.clone(),
1850 resource.clone(),
1851 Expr::val(true),
1852 );
1853 buf.push(permit);
1854 buf.push(forbid);
1855 }
1856 }
1857 }
1858 buf.into_iter()
1859 }
1860}
1861
1862#[cfg(test)]
1863#[allow(clippy::indexing_slicing)]
1865#[allow(clippy::panic)]
1867mod test {
1868 use cool_asserts::assert_matches;
1869 use std::collections::HashSet;
1870
1871 use super::{test_generators::*, *};
1872 use crate::{
1873 parser::{
1874 parse_policy,
1875 test_utils::{expect_exactly_one_error, expect_some_error_matches},
1876 },
1877 test_utils::ExpectedErrorMessageBuilder,
1878 };
1879
1880 #[test]
1881 fn literal_and_borrowed() {
1882 for template in all_templates() {
1883 let t = Arc::new(template);
1884 let env = t
1885 .slots()
1886 .map(|slot| (slot.id, EntityUID::with_eid("eid")))
1887 .collect();
1888 let p =
1889 Template::link(t, PolicyID::from_string("id"), env).expect("Instantiation Failed");
1890
1891 let b_literal = BorrowedLiteralPolicy::from(&p);
1892 let src = serde_json::to_string(&b_literal).expect("ser error");
1893 let literal: LiteralPolicy = serde_json::from_str(&src).expect("de error");
1894
1895 assert_eq!(b_literal.template_id, &literal.template_id);
1896 assert_eq!(b_literal.link_id, literal.link_id.as_ref());
1897 assert_eq!(b_literal.values, &literal.values);
1898 }
1899 }
1900
1901 #[test]
1902 fn template_roundtrip() {
1903 for template in all_templates() {
1904 template.check_invariant();
1905 let json = serde_json::to_string(&template).expect("Serialization Failed");
1906 let t2 = serde_json::from_str::<Template>(&json).expect("Deserialization failed");
1907 t2.check_invariant();
1908 assert_eq!(template, t2);
1909 }
1910 }
1911
1912 #[test]
1913 fn test_template_rebuild() {
1914 for template in all_templates() {
1915 let id = template.id().clone();
1916 let effect = template.effect();
1917 let p = template.principal_constraint().clone();
1918 let a = template.action_constraint().clone();
1919 let r = template.resource_constraint().clone();
1920 let non_scope = template.non_scope_constraints().clone();
1921 let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
1922 assert_eq!(template, t2);
1923 }
1924 }
1925
1926 #[test]
1927 fn test_inline_policy_rebuild() {
1928 for template in all_templates() {
1929 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
1930 let id = ip.id().clone();
1931 let e = ip.effect();
1932 let anno = ip
1933 .annotations()
1934 .map(|(k, v)| (k.clone(), v.clone()))
1935 .collect();
1936 let p = ip.principal_constraint().clone();
1937 let a = ip.action_constraint().clone();
1938 let r = ip.resource_constraint().clone();
1939 let non_scope = ip.non_scope_constraints().clone();
1940 let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope)
1941 .expect("Policy Creation Failed");
1942 assert_eq!(ip, ip2);
1943 let (t2, inst) = Template::link_static_policy(ip2);
1944 assert!(inst.is_static());
1945 assert_eq!(&template, t2.as_ref());
1946 }
1947 }
1948 }
1949
1950 #[test]
1951 fn ir_binding_too_many() {
1952 let tid = PolicyID::from_string("tid");
1953 let iid = PolicyID::from_string("iid");
1954 let t = Arc::new(Template::new(
1955 tid,
1956 None,
1957 Annotations::new(),
1958 Effect::Forbid,
1959 PrincipalConstraint::is_eq_slot(),
1960 ActionConstraint::Any,
1961 ResourceConstraint::any(),
1962 Expr::val(true),
1963 ));
1964 let mut m = HashMap::new();
1965 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
1966 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
1967 assert_eq!(unbound_values, vec![SlotId::principal()]);
1968 assert_eq!(extra_values, vec![SlotId::resource()]);
1969 });
1970 }
1971
1972 #[test]
1973 fn ir_binding_too_few() {
1974 let tid = PolicyID::from_string("tid");
1975 let iid = PolicyID::from_string("iid");
1976 let t = Arc::new(Template::new(
1977 tid,
1978 None,
1979 Annotations::new(),
1980 Effect::Forbid,
1981 PrincipalConstraint::is_eq_slot(),
1982 ActionConstraint::Any,
1983 ResourceConstraint::is_in_slot(),
1984 Expr::val(true),
1985 ));
1986 assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
1987 assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
1988 assert_eq!(extra_values, vec![]);
1989 });
1990 let mut m = HashMap::new();
1991 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
1992 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
1993 assert_eq!(unbound_values, vec![SlotId::resource()]);
1994 assert_eq!(extra_values, vec![]);
1995 });
1996 }
1997
1998 #[test]
1999 fn ir_binding() {
2000 let tid = PolicyID::from_string("template");
2001 let iid = PolicyID::from_string("linked");
2002 let t = Arc::new(Template::new(
2003 tid,
2004 None,
2005 Annotations::new(),
2006 Effect::Permit,
2007 PrincipalConstraint::is_in_slot(),
2008 ActionConstraint::any(),
2009 ResourceConstraint::is_eq_slot(),
2010 Expr::val(true),
2011 ));
2012
2013 let mut m = HashMap::new();
2014 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2015 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2016
2017 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2018 assert_eq!(r.id(), &iid);
2019 assert_eq!(
2020 r.env().get(&SlotId::principal()),
2021 Some(&EntityUID::with_eid("theprincipal"))
2022 );
2023 assert_eq!(
2024 r.env().get(&SlotId::resource()),
2025 Some(&EntityUID::with_eid("theresource"))
2026 );
2027 }
2028
2029 #[test]
2030 fn isnt_template_implies_from_succeeds() {
2031 for template in all_templates() {
2032 if template.slots().count() == 0 {
2033 StaticPolicy::try_from(template).expect("Should succeed");
2034 }
2035 }
2036 }
2037
2038 #[test]
2039 fn is_template_implies_from_fails() {
2040 for template in all_templates() {
2041 if template.slots().count() != 0 {
2042 assert!(
2043 StaticPolicy::try_from(template.clone()).is_err(),
2044 "Following template did convert {template}"
2045 );
2046 }
2047 }
2048 }
2049
2050 #[test]
2051 fn non_template_iso() {
2052 for template in all_templates() {
2053 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2054 let (t2, _) = Template::link_static_policy(p);
2055 assert_eq!(&template, t2.as_ref());
2056 }
2057 }
2058 }
2059
2060 #[test]
2061 fn template_into_expr() {
2062 for template in all_templates() {
2063 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2064 let t: Template = template;
2065 assert_eq!(p.condition(), t.condition());
2066 assert_eq!(p.effect(), t.effect());
2067 }
2068 }
2069 }
2070
2071 #[test]
2072 fn template_por_iter() {
2073 let e = Arc::new(EntityUID::with_eid("eid"));
2074 assert_eq!(PrincipalOrResourceConstraint::Any.iter_euids().count(), 0);
2075 assert_eq!(
2076 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone()))
2077 .iter_euids()
2078 .count(),
2079 1
2080 );
2081 assert_eq!(
2082 PrincipalOrResourceConstraint::In(EntityReference::Slot)
2083 .iter_euids()
2084 .count(),
2085 0
2086 );
2087 assert_eq!(
2088 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone()))
2089 .iter_euids()
2090 .count(),
2091 1
2092 );
2093 assert_eq!(
2094 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
2095 .iter_euids()
2096 .count(),
2097 0
2098 );
2099 assert_eq!(
2100 PrincipalOrResourceConstraint::IsIn("T".parse().unwrap(), EntityReference::EUID(e))
2101 .iter_euids()
2102 .count(),
2103 1
2104 );
2105 assert_eq!(
2106 PrincipalOrResourceConstraint::Is("T".parse().unwrap())
2107 .iter_euids()
2108 .count(),
2109 0
2110 );
2111 assert_eq!(
2112 PrincipalOrResourceConstraint::IsIn("T".parse().unwrap(), EntityReference::Slot)
2113 .iter_euids()
2114 .count(),
2115 0
2116 );
2117 }
2118
2119 #[test]
2120 fn action_iter() {
2121 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2122 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2123 let v = a.iter_euids().collect::<Vec<_>>();
2124 assert_eq!(vec![&EntityUID::with_eid("test")], v);
2125 let a =
2126 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2127 let set = a.iter_euids().collect::<HashSet<_>>();
2128 let e1 = EntityUID::with_eid("test1");
2129 let e2 = EntityUID::with_eid("test2");
2130 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2131 assert_eq!(set, correct);
2132 }
2133
2134 #[test]
2135 fn test_iter_none() {
2136 let mut i = EntityIterator::None;
2137 assert_eq!(i.next(), None);
2138 }
2139
2140 #[test]
2141 fn test_iter_once() {
2142 let id = EntityUID::from_components(
2143 name::Name::unqualified_name(id::Id::new_unchecked("s")),
2144 entity::Eid::new("eid"),
2145 None,
2146 );
2147 let mut i = EntityIterator::One(&id);
2148 assert_eq!(i.next(), Some(&id));
2149 assert_eq!(i.next(), None);
2150 }
2151
2152 #[test]
2153 fn test_iter_mult() {
2154 let id1 = EntityUID::from_components(
2155 name::Name::unqualified_name(id::Id::new_unchecked("s")),
2156 entity::Eid::new("eid1"),
2157 None,
2158 );
2159 let id2 = EntityUID::from_components(
2160 name::Name::unqualified_name(id::Id::new_unchecked("s")),
2161 entity::Eid::new("eid2"),
2162 None,
2163 );
2164 let v = vec![&id1, &id2];
2165 let mut i = EntityIterator::Bunch(v);
2166 assert_eq!(i.next(), Some(&id2));
2167 assert_eq!(i.next(), Some(&id1));
2168 assert_eq!(i.next(), None)
2169 }
2170
2171 #[test]
2172 fn euid_into_expr() {
2173 let e = EntityReference::Slot;
2174 assert_eq!(
2175 e.into_expr(SlotId::principal()),
2176 Expr::slot(SlotId::principal())
2177 );
2178 let e = EntityReference::euid(EntityUID::with_eid("eid"));
2179 assert_eq!(
2180 e.into_expr(SlotId::principal()),
2181 Expr::val(EntityUID::with_eid("eid"))
2182 );
2183 }
2184
2185 #[test]
2186 fn por_constraint_display() {
2187 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot);
2188 let s = t.display(PrincipalOrResource::Principal);
2189 assert_eq!(s, "principal == ?principal");
2190 let t =
2191 PrincipalOrResourceConstraint::Eq(EntityReference::euid(EntityUID::with_eid("test")));
2192 let s = t.display(PrincipalOrResource::Principal);
2193 assert_eq!(s, "principal == test_entity_type::\"test\"");
2194 }
2195
2196 #[test]
2197 fn unexpected_templates() {
2198 let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2199 assert_matches!(parse_policy(Some("id".into()), policy_str), Err(e) => {
2200 expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2201 "expected a static policy, got a template containing the slot ?principal"
2202 )
2203 .help("try removing the template slot(s) from this policy")
2204 .exactly_one_underline("permit(principal == ?principal, action, resource);")
2205 .build()
2206 );
2207 });
2208
2209 let policy_str =
2210 r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2211 assert_matches!(parse_policy(Some("id".into()), policy_str), Err(e) => {
2212 expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2213 "expected a static policy, got a template containing the slot ?principal"
2214 )
2215 .help("try removing the template slot(s) from this policy")
2216 .exactly_one_underline("?principal")
2217 .build()
2218 );
2219 assert_eq!(e.len(), 2);
2220 });
2221 }
2222}