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