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, ToSmolStr};
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 #[allow(clippy::too_many_arguments)]
72 pub fn new(
73 id: PolicyID,
74 loc: Option<Loc>,
75 annotations: Annotations,
76 effect: Effect,
77 principal_constraint: PrincipalConstraint,
78 action_constraint: ActionConstraint,
79 resource_constraint: ResourceConstraint,
80 non_scope_constraint: Expr,
81 ) -> Self {
82 let body = TemplateBody::new(
83 id,
84 loc,
85 annotations,
86 effect,
87 principal_constraint,
88 action_constraint,
89 resource_constraint,
90 non_scope_constraint,
91 );
92 Template::from(body)
95 }
96
97 #[allow(clippy::too_many_arguments)]
99 pub fn new_shared(
100 id: PolicyID,
101 loc: Option<Loc>,
102 annotations: Arc<Annotations>,
103 effect: Effect,
104 principal_constraint: PrincipalConstraint,
105 action_constraint: ActionConstraint,
106 resource_constraint: ResourceConstraint,
107 non_scope_constraint: Arc<Expr>,
108 ) -> Self {
109 let body = TemplateBody::new_shared(
110 id,
111 loc,
112 annotations,
113 effect,
114 principal_constraint,
115 action_constraint,
116 resource_constraint,
117 non_scope_constraint,
118 );
119 Template::from(body)
122 }
123
124 pub fn principal_constraint(&self) -> &PrincipalConstraint {
126 self.body.principal_constraint()
127 }
128
129 pub fn action_constraint(&self) -> &ActionConstraint {
131 self.body.action_constraint()
132 }
133
134 pub fn resource_constraint(&self) -> &ResourceConstraint {
136 self.body.resource_constraint()
137 }
138
139 pub fn non_scope_constraints(&self) -> &Expr {
141 self.body.non_scope_constraints()
142 }
143
144 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
146 self.body.non_scope_constraints_arc()
147 }
148
149 pub fn id(&self) -> &PolicyID {
151 self.body.id()
152 }
153
154 pub fn new_id(&self, id: PolicyID) -> Self {
156 Template {
157 body: self.body.new_id(id),
158 slots: self.slots.clone(),
159 }
160 }
161
162 pub fn loc(&self) -> Option<&Loc> {
164 self.body.loc()
165 }
166
167 pub fn effect(&self) -> Effect {
169 self.body.effect()
170 }
171
172 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
174 self.body.annotation(key)
175 }
176
177 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
179 self.body.annotations()
180 }
181
182 pub fn annotations_arc(&self) -> &Arc<Annotations> {
184 self.body.annotations_arc()
185 }
186
187 pub fn condition(&self) -> Expr {
193 self.body.condition()
194 }
195
196 pub fn slots(&self) -> impl Iterator<Item = &Slot> {
198 self.slots.iter()
199 }
200
201 pub fn is_static(&self) -> bool {
206 self.slots.is_empty()
207 }
208
209 pub fn check_binding(
213 template: &Template,
214 values: &HashMap<SlotId, EntityUID>,
215 ) -> Result<(), LinkingError> {
216 let unbound = template
218 .slots
219 .iter()
220 .filter(|slot| !values.contains_key(&slot.id))
221 .collect::<Vec<_>>();
222
223 let extra = values
224 .iter()
225 .filter_map(|(slot, _)| {
226 if !template
227 .slots
228 .iter()
229 .any(|template_slot| template_slot.id == *slot)
230 {
231 Some(slot)
232 } else {
233 None
234 }
235 })
236 .collect::<Vec<_>>();
237
238 if unbound.is_empty() && extra.is_empty() {
239 Ok(())
240 } else {
241 Err(LinkingError::from_unbound_and_extras(
242 unbound.into_iter().map(|slot| slot.id),
243 extra.into_iter().copied(),
244 ))
245 }
246 }
247
248 pub fn link(
252 template: Arc<Template>,
253 new_id: PolicyID,
254 values: HashMap<SlotId, EntityUID>,
255 ) -> Result<Policy, LinkingError> {
256 Template::check_binding(&template, &values)
258 .map(|_| Policy::new(template, Some(new_id), values))
259 }
260
261 pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
264 let body: TemplateBody = p.into();
265 let t = Arc::new(Self {
269 body,
270 slots: vec![],
271 });
272 #[cfg(test)]
273 {
274 t.check_invariant();
275 }
276 let p = Policy::new(Arc::clone(&t), None, HashMap::new());
282 (t, p)
283 }
284}
285
286impl From<TemplateBody> for Template {
287 fn from(body: TemplateBody) -> Self {
288 let slots = body.condition().slots().collect::<Vec<_>>();
291 Self { body, slots }
292 }
293}
294
295impl std::fmt::Display for Template {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 write!(f, "{}", self.body)
298 }
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
303pub enum LinkingError {
304 #[error("{}", describe_arity_error(.unbound_values, .extra_values))]
307 ArityError {
308 unbound_values: Vec<SlotId>,
310 extra_values: Vec<SlotId>,
312 },
313
314 #[error("failed to find a template with id `{id}`")]
316 NoSuchTemplate {
317 id: PolicyID,
319 },
320
321 #[error("template-linked policy id `{id}` conflicts with an existing policy id")]
323 PolicyIdConflict {
324 id: PolicyID,
326 },
327}
328
329impl LinkingError {
330 fn from_unbound_and_extras(
331 unbound: impl Iterator<Item = SlotId>,
332 extra: impl Iterator<Item = SlotId>,
333 ) -> Self {
334 Self::ArityError {
335 unbound_values: unbound.collect(),
336 extra_values: extra.collect(),
337 }
338 }
339}
340
341fn describe_arity_error(unbound_values: &[SlotId], extra_values: &[SlotId]) -> String {
342 match (unbound_values.len(), extra_values.len()) {
343 #[allow(clippy::unreachable)]
345 (0,0) => unreachable!(),
346 (_unbound, 0) => format!("the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
347 (0, _extra) => format!("the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
348 (_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(","))
349 }
350}
351
352#[derive(Debug, Clone, Eq, PartialEq)]
360pub struct Policy {
361 template: Arc<Template>,
363 link: Option<PolicyID>,
366 values: HashMap<SlotId, EntityUID>,
372}
373
374impl Policy {
375 fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
379 #[cfg(test)]
380 {
381 Template::check_binding(&template, &values).expect("(values total map) does not hold!");
382 }
383 Self {
389 template,
390 link: link_id,
391 values,
392 }
393 }
394
395 pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID, loc: Option<Loc>) -> Self {
397 Self::from_when_clause_annos(
398 effect,
399 Arc::new(when),
400 id,
401 loc,
402 Arc::new(Annotations::default()),
403 )
404 }
405
406 pub fn from_when_clause_annos(
408 effect: Effect,
409 when: Arc<Expr>,
410 id: PolicyID,
411 loc: Option<Loc>,
412 annotations: Arc<Annotations>,
413 ) -> Self {
414 let t = Template::new_shared(
415 id,
416 loc,
417 annotations,
418 effect,
419 PrincipalConstraint::any(),
420 ActionConstraint::any(),
421 ResourceConstraint::any(),
422 when,
423 );
424 Self::new(Arc::new(t), None, SlotEnv::new())
425 }
426
427 pub fn template(&self) -> &Template {
429 &self.template
430 }
431
432 pub(crate) fn template_arc(&self) -> Arc<Template> {
434 Arc::clone(&self.template)
435 }
436
437 pub fn effect(&self) -> Effect {
439 self.template.effect()
440 }
441
442 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
444 self.template.annotation(key)
445 }
446
447 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
449 self.template.annotations()
450 }
451
452 pub fn annotations_arc(&self) -> &Arc<Annotations> {
454 self.template.annotations_arc()
455 }
456
457 pub fn principal_constraint(&self) -> PrincipalConstraint {
463 let constraint = self.template.principal_constraint().clone();
464 match self.values.get(&SlotId::principal()) {
465 None => constraint,
466 Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
467 }
468 }
469
470 pub fn action_constraint(&self) -> &ActionConstraint {
472 self.template.action_constraint()
473 }
474
475 pub fn resource_constraint(&self) -> ResourceConstraint {
481 let constraint = self.template.resource_constraint().clone();
482 match self.values.get(&SlotId::resource()) {
483 None => constraint,
484 Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
485 }
486 }
487
488 pub fn non_scope_constraints(&self) -> &Expr {
490 self.template.non_scope_constraints()
491 }
492
493 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
495 self.template.non_scope_constraints_arc()
496 }
497
498 pub fn condition(&self) -> Expr {
500 self.template.condition()
501 }
502
503 pub fn env(&self) -> &SlotEnv {
506 &self.values
507 }
508
509 pub fn id(&self) -> &PolicyID {
511 self.link.as_ref().unwrap_or_else(|| self.template.id())
512 }
513
514 pub fn new_id(&self, id: PolicyID) -> Self {
516 match self.link {
517 None => Policy {
518 template: Arc::new(self.template.new_id(id)),
519 link: None,
520 values: self.values.clone(),
521 },
522 Some(_) => Policy {
523 template: self.template.clone(),
524 link: Some(id),
525 values: self.values.clone(),
526 },
527 }
528 }
529
530 pub fn loc(&self) -> Option<&Loc> {
532 self.template.loc()
533 }
534
535 pub fn is_static(&self) -> bool {
537 self.link.is_none()
538 }
539}
540
541impl std::fmt::Display for Policy {
542 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
543 if self.is_static() {
544 write!(f, "{}", self.template())
545 } else {
546 write!(
547 f,
548 "Template Instance of {}, slots: [{}]",
549 self.template().id(),
550 display_slot_env(self.env())
551 )
552 }
553 }
554}
555
556pub type SlotEnv = HashMap<SlotId, EntityUID>;
558
559#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
562pub struct LiteralPolicy {
563 template_id: PolicyID,
565 link_id: Option<PolicyID>,
569 values: SlotEnv,
571}
572
573#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
575pub struct BorrowedLiteralPolicy<'a> {
576 template_id: &'a PolicyID,
578 link_id: Option<&'a PolicyID>,
582 values: &'a SlotEnv,
584}
585
586impl<'a> From<&'a Policy> for BorrowedLiteralPolicy<'a> {
587 fn from(p: &'a Policy) -> Self {
588 Self {
589 template_id: p.template.id(),
590 link_id: p.link.as_ref(),
591 values: &p.values,
592 }
593 }
594}
595
596impl std::hash::Hash for LiteralPolicy {
599 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
600 self.template_id.hash(state);
601 let mut buf = self.values.iter().collect::<Vec<_>>();
603 buf.sort();
604 for (id, euid) in buf {
605 id.hash(state);
606 euid.hash(state);
607 }
608 }
609}
610
611impl std::cmp::PartialEq for LiteralPolicy {
612 fn eq(&self, other: &Self) -> bool {
613 self.template_id() == other.template_id()
614 && self.link_id == other.link_id
615 && self.values == other.values
616 }
617}
618
619#[cfg(test)]
621mod hashing_tests {
622 use std::{
623 collections::hash_map::DefaultHasher,
624 hash::{Hash, Hasher},
625 };
626
627 use super::*;
628
629 fn compute_hash(ir: LiteralPolicy) -> u64 {
630 let mut s = DefaultHasher::new();
631 ir.hash(&mut s);
632 s.finish()
633 }
634
635 fn build_template_linked_policy() -> LiteralPolicy {
636 let mut map = HashMap::new();
637 map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
638 LiteralPolicy {
639 template_id: PolicyID::from_string("template"),
640 link_id: Some(PolicyID::from_string("id")),
641 values: map,
642 }
643 }
644
645 #[test]
646 fn hash_property_instances() {
647 let a = build_template_linked_policy();
648 let b = build_template_linked_policy();
649 assert_eq!(a, b);
650 assert_eq!(compute_hash(a), compute_hash(b));
651 }
652}
653#[derive(Debug, Diagnostic, Error)]
661pub enum ReificationError {
662 #[error("the id linked to does not exist")]
664 NoSuchTemplate(PolicyID),
665 #[error(transparent)]
667 #[diagnostic(transparent)]
668 Linking(#[from] LinkingError),
669}
670
671impl LiteralPolicy {
672 pub fn reify(
677 self,
678 templates: &HashMap<PolicyID, Arc<Template>>,
679 ) -> Result<Policy, ReificationError> {
680 let template = templates
681 .get(&self.template_id)
682 .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
683 Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
685 Ok(Policy::new(template.clone(), self.link_id, self.values))
686 }
687
688 pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
690 self.values.get(id)
691 }
692
693 pub fn id(&self) -> &PolicyID {
696 self.link_id.as_ref().unwrap_or(&self.template_id)
697 }
698
699 pub fn template_id(&self) -> &PolicyID {
701 &self.template_id
702 }
703
704 pub fn is_static(&self) -> bool {
706 self.link_id.is_none()
707 }
708}
709
710fn display_slot_env(env: &SlotEnv) -> String {
711 env.iter()
712 .map(|(slot, value)| format!("{slot} -> {value}"))
713 .join(",")
714}
715
716impl std::fmt::Display for LiteralPolicy {
717 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718 if self.is_static() {
719 write!(f, "Static policy w/ ID {}", self.template_id())
720 } else {
721 write!(
722 f,
723 "Template linked policy of {}, slots: [{}]",
724 self.template_id(),
725 display_slot_env(&self.values),
726 )
727 }
728 }
729}
730
731impl From<Policy> for LiteralPolicy {
732 fn from(p: Policy) -> Self {
733 Self {
734 template_id: p.template.id().clone(),
735 link_id: p.link,
736 values: p.values,
737 }
738 }
739}
740
741#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
745pub struct StaticPolicy(TemplateBody);
746
747impl StaticPolicy {
748 pub fn id(&self) -> &PolicyID {
750 self.0.id()
751 }
752
753 pub fn new_id(&self, id: PolicyID) -> Self {
755 StaticPolicy(self.0.new_id(id))
756 }
757
758 pub fn loc(&self) -> Option<&Loc> {
760 self.0.loc()
761 }
762
763 pub fn effect(&self) -> Effect {
765 self.0.effect()
766 }
767
768 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
770 self.0.annotation(key)
771 }
772
773 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
775 self.0.annotations()
776 }
777
778 pub fn principal_constraint(&self) -> &PrincipalConstraint {
780 self.0.principal_constraint()
781 }
782
783 pub fn principal_constraint_expr(&self) -> Expr {
787 self.0.principal_constraint_expr()
788 }
789
790 pub fn action_constraint(&self) -> &ActionConstraint {
792 self.0.action_constraint()
793 }
794
795 pub fn action_constraint_expr(&self) -> Expr {
799 self.0.action_constraint_expr()
800 }
801
802 pub fn resource_constraint(&self) -> &ResourceConstraint {
804 self.0.resource_constraint()
805 }
806
807 pub fn resource_constraint_expr(&self) -> Expr {
811 self.0.resource_constraint_expr()
812 }
813
814 pub fn non_scope_constraints(&self) -> &Expr {
819 self.0.non_scope_constraints()
820 }
821
822 pub fn condition(&self) -> Expr {
828 self.0.condition()
829 }
830
831 #[allow(clippy::too_many_arguments)]
833 pub fn new(
834 id: PolicyID,
835 loc: Option<Loc>,
836 annotations: Annotations,
837 effect: Effect,
838 principal_constraint: PrincipalConstraint,
839 action_constraint: ActionConstraint,
840 resource_constraint: ResourceConstraint,
841 non_scope_constraints: Expr,
842 ) -> Result<Self, UnexpectedSlotError> {
843 let body = TemplateBody::new(
844 id,
845 loc,
846 annotations,
847 effect,
848 principal_constraint,
849 action_constraint,
850 resource_constraint,
851 non_scope_constraints,
852 );
853 let first_slot = body.condition().slots().next();
854 match first_slot {
856 Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
857 None => Ok(Self(body)),
858 }
859 }
860}
861
862impl TryFrom<Template> for StaticPolicy {
863 type Error = UnexpectedSlotError;
864
865 fn try_from(value: Template) -> Result<Self, Self::Error> {
866 let o = value.slots().next().cloned();
868 match o {
869 Some(slot_id) => Err(Self::Error::FoundSlot(slot_id)),
870 None => Ok(Self(value.body)),
871 }
872 }
873}
874
875impl From<StaticPolicy> for Policy {
876 fn from(inline: StaticPolicy) -> Policy {
877 let (_, policy) = Template::link_static_policy(inline);
878 policy
879 }
880}
881
882impl From<StaticPolicy> for Arc<Template> {
883 fn from(p: StaticPolicy) -> Self {
884 let (t, _) = Template::link_static_policy(p);
885 t
886 }
887}
888
889#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
892pub struct TemplateBody {
893 id: PolicyID,
895 loc: Option<Loc>,
897 annotations: Arc<Annotations>,
901 effect: Effect,
903 principal_constraint: PrincipalConstraint,
907 action_constraint: ActionConstraint,
911 resource_constraint: ResourceConstraint,
915 non_scope_constraints: Arc<Expr>,
920}
921
922impl TemplateBody {
923 pub fn id(&self) -> &PolicyID {
925 &self.id
926 }
927
928 pub fn loc(&self) -> Option<&Loc> {
930 self.loc.as_ref()
931 }
932
933 pub fn new_id(&self, id: PolicyID) -> Self {
935 let mut new = self.clone();
936 new.id = id;
937 new
938 }
939
940 pub fn effect(&self) -> Effect {
942 self.effect
943 }
944
945 pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
947 self.annotations.get(key)
948 }
949
950 pub fn annotations_arc(&self) -> &Arc<Annotations> {
952 &self.annotations
953 }
954
955 pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
957 self.annotations.iter()
958 }
959
960 pub fn principal_constraint(&self) -> &PrincipalConstraint {
962 &self.principal_constraint
963 }
964
965 pub fn principal_constraint_expr(&self) -> Expr {
969 self.principal_constraint.as_expr()
970 }
971
972 pub fn action_constraint(&self) -> &ActionConstraint {
974 &self.action_constraint
975 }
976
977 pub fn action_constraint_expr(&self) -> Expr {
981 self.action_constraint.as_expr()
982 }
983
984 pub fn resource_constraint(&self) -> &ResourceConstraint {
986 &self.resource_constraint
987 }
988
989 pub fn resource_constraint_expr(&self) -> Expr {
993 self.resource_constraint.as_expr()
994 }
995
996 pub fn non_scope_constraints(&self) -> &Expr {
1001 &self.non_scope_constraints
1002 }
1003
1004 pub fn non_scope_constraints_arc(&self) -> &Arc<Expr> {
1006 &self.non_scope_constraints
1007 }
1008
1009 pub fn condition(&self) -> Expr {
1015 Expr::and(
1016 Expr::and(
1017 Expr::and(
1018 self.principal_constraint_expr(),
1019 self.action_constraint_expr(),
1020 )
1021 .with_maybe_source_loc(self.loc.clone()),
1022 self.resource_constraint_expr(),
1023 )
1024 .with_maybe_source_loc(self.loc.clone()),
1025 self.non_scope_constraints.as_ref().clone(),
1026 )
1027 .with_maybe_source_loc(self.loc.clone())
1028 }
1029
1030 #[allow(clippy::too_many_arguments)]
1032 pub fn new_shared(
1033 id: PolicyID,
1034 loc: Option<Loc>,
1035 annotations: Arc<Annotations>,
1036 effect: Effect,
1037 principal_constraint: PrincipalConstraint,
1038 action_constraint: ActionConstraint,
1039 resource_constraint: ResourceConstraint,
1040 non_scope_constraints: Arc<Expr>,
1041 ) -> Self {
1042 Self {
1043 id,
1044 loc,
1045 annotations,
1046 effect,
1047 principal_constraint,
1048 action_constraint,
1049 resource_constraint,
1050 non_scope_constraints,
1051 }
1052 }
1053
1054 #[allow(clippy::too_many_arguments)]
1056 pub fn new(
1057 id: PolicyID,
1058 loc: Option<Loc>,
1059 annotations: Annotations,
1060 effect: Effect,
1061 principal_constraint: PrincipalConstraint,
1062 action_constraint: ActionConstraint,
1063 resource_constraint: ResourceConstraint,
1064 non_scope_constraints: Expr,
1065 ) -> Self {
1066 Self {
1067 id,
1068 loc,
1069 annotations: Arc::new(annotations),
1070 effect,
1071 principal_constraint,
1072 action_constraint,
1073 resource_constraint,
1074 non_scope_constraints: Arc::new(non_scope_constraints),
1075 }
1076 }
1077}
1078
1079impl From<StaticPolicy> for TemplateBody {
1080 fn from(p: StaticPolicy) -> Self {
1081 p.0
1082 }
1083}
1084
1085impl std::fmt::Display for TemplateBody {
1086 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1087 for (k, v) in self.annotations.iter() {
1088 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1089 }
1090 write!(
1091 f,
1092 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1093 self.effect(),
1094 self.principal_constraint(),
1095 self.action_constraint(),
1096 self.resource_constraint(),
1097 self.non_scope_constraints()
1098 )
1099 }
1100}
1101
1102#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1104pub struct Annotations(BTreeMap<AnyId, Annotation>);
1105
1106impl Annotations {
1107 pub fn new() -> Self {
1109 Self(BTreeMap::new())
1110 }
1111
1112 pub fn get(&self, key: &AnyId) -> Option<&Annotation> {
1114 self.0.get(key)
1115 }
1116
1117 pub fn iter(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1119 self.0.iter()
1120 }
1121}
1122
1123#[derive(Debug)]
1125pub struct IntoIter(std::collections::btree_map::IntoIter<AnyId, Annotation>);
1126
1127impl Iterator for IntoIter {
1128 type Item = (AnyId, Annotation);
1129
1130 fn next(&mut self) -> Option<Self::Item> {
1131 self.0.next()
1132 }
1133}
1134
1135impl IntoIterator for Annotations {
1136 type Item = (AnyId, Annotation);
1137
1138 type IntoIter = IntoIter;
1139
1140 fn into_iter(self) -> Self::IntoIter {
1141 IntoIter(self.0.into_iter())
1142 }
1143}
1144
1145impl Default for Annotations {
1146 fn default() -> Self {
1147 Self::new()
1148 }
1149}
1150
1151impl FromIterator<(AnyId, Annotation)> for Annotations {
1152 fn from_iter<T: IntoIterator<Item = (AnyId, Annotation)>>(iter: T) -> Self {
1153 Self(BTreeMap::from_iter(iter))
1154 }
1155}
1156
1157impl From<BTreeMap<AnyId, Annotation>> for Annotations {
1158 fn from(value: BTreeMap<AnyId, Annotation>) -> Self {
1159 Self(value)
1160 }
1161}
1162
1163#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug, PartialOrd, Ord)]
1165pub struct Annotation {
1166 pub val: SmolStr,
1168 pub loc: Option<Loc>,
1171}
1172
1173impl Annotation {
1174 pub fn with_optional_value(val: Option<SmolStr>, loc: Option<Loc>) -> Self {
1179 Self {
1180 val: val.unwrap_or_else(|| "".to_smolstr()),
1181 loc,
1182 }
1183 }
1184}
1185
1186impl AsRef<str> for Annotation {
1187 fn as_ref(&self) -> &str {
1188 &self.val
1189 }
1190}
1191
1192#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1194pub struct PrincipalConstraint {
1195 pub(crate) constraint: PrincipalOrResourceConstraint,
1196}
1197
1198impl PrincipalConstraint {
1199 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1201 PrincipalConstraint { constraint }
1202 }
1203
1204 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1206 &self.constraint
1207 }
1208
1209 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1211 self.constraint
1212 }
1213
1214 pub fn as_expr(&self) -> Expr {
1216 self.constraint.as_expr(PrincipalOrResource::Principal)
1217 }
1218
1219 pub fn any() -> Self {
1221 PrincipalConstraint {
1222 constraint: PrincipalOrResourceConstraint::any(),
1223 }
1224 }
1225
1226 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1228 PrincipalConstraint {
1229 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1230 }
1231 }
1232
1233 pub fn is_eq_slot() -> Self {
1235 Self {
1236 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1237 }
1238 }
1239
1240 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1242 PrincipalConstraint {
1243 constraint: PrincipalOrResourceConstraint::is_in(euid),
1244 }
1245 }
1246
1247 pub fn is_in_slot() -> Self {
1249 Self {
1250 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1251 }
1252 }
1253
1254 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1256 Self {
1257 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1258 }
1259 }
1260
1261 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1263 Self {
1264 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1265 }
1266 }
1267
1268 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1270 Self {
1271 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1272 }
1273 }
1274
1275 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1277 match self.constraint {
1278 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1279 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1280 },
1281 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1282 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1283 },
1284 _ => self,
1285 }
1286 }
1287}
1288
1289impl std::fmt::Display for PrincipalConstraint {
1290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1291 write!(
1292 f,
1293 "{}",
1294 self.constraint.display(PrincipalOrResource::Principal)
1295 )
1296 }
1297}
1298
1299#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1301pub struct ResourceConstraint {
1302 pub(crate) constraint: PrincipalOrResourceConstraint,
1303}
1304
1305impl ResourceConstraint {
1306 pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1308 ResourceConstraint { constraint }
1309 }
1310
1311 pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1313 &self.constraint
1314 }
1315
1316 pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1318 self.constraint
1319 }
1320
1321 pub fn as_expr(&self) -> Expr {
1323 self.constraint.as_expr(PrincipalOrResource::Resource)
1324 }
1325
1326 pub fn any() -> Self {
1328 ResourceConstraint {
1329 constraint: PrincipalOrResourceConstraint::any(),
1330 }
1331 }
1332
1333 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1335 ResourceConstraint {
1336 constraint: PrincipalOrResourceConstraint::is_eq(euid),
1337 }
1338 }
1339
1340 pub fn is_eq_slot() -> Self {
1342 Self {
1343 constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1344 }
1345 }
1346
1347 pub fn is_in_slot() -> Self {
1349 Self {
1350 constraint: PrincipalOrResourceConstraint::is_in_slot(),
1351 }
1352 }
1353
1354 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1356 ResourceConstraint {
1357 constraint: PrincipalOrResourceConstraint::is_in(euid),
1358 }
1359 }
1360
1361 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1363 Self {
1364 constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1365 }
1366 }
1367
1368 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1370 Self {
1371 constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1372 }
1373 }
1374
1375 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1377 Self {
1378 constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1379 }
1380 }
1381
1382 pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1384 match self.constraint {
1385 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => Self {
1386 constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1387 },
1388 PrincipalOrResourceConstraint::In(EntityReference::Slot) => Self {
1389 constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1390 },
1391 _ => self,
1392 }
1393 }
1394}
1395
1396impl std::fmt::Display for ResourceConstraint {
1397 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1398 write!(
1399 f,
1400 "{}",
1401 self.as_inner().display(PrincipalOrResource::Resource)
1402 )
1403 }
1404}
1405
1406#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1408pub enum EntityReference {
1409 EUID(Arc<EntityUID>),
1411 Slot,
1413}
1414
1415impl EntityReference {
1416 pub fn euid(euid: Arc<EntityUID>) -> Self {
1418 Self::EUID(euid)
1419 }
1420
1421 pub fn into_expr(&self, slot: SlotId) -> Expr {
1427 match self {
1428 EntityReference::EUID(euid) => Expr::val(euid.clone()),
1429 EntityReference::Slot => Expr::slot(slot),
1430 }
1431 }
1432}
1433
1434#[derive(Debug, Clone, PartialEq, Error)]
1436pub enum UnexpectedSlotError {
1437 #[error("found slot `{}` where slots are not allowed", .0.id)]
1439 FoundSlot(Slot),
1440}
1441
1442impl Diagnostic for UnexpectedSlotError {
1443 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1444 match self {
1445 Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1446 let label = miette::LabeledSpan::underline(loc.span);
1447 Box::new(std::iter::once(label)) as Box<dyn Iterator<Item = miette::LabeledSpan>>
1448 }),
1449 }
1450 }
1451}
1452
1453impl From<EntityUID> for EntityReference {
1454 fn from(euid: EntityUID) -> Self {
1455 Self::EUID(Arc::new(euid))
1456 }
1457}
1458
1459#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
1461#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1462pub enum PrincipalOrResource {
1463 Principal,
1465 Resource,
1467}
1468
1469impl std::fmt::Display for PrincipalOrResource {
1470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1471 let v = Var::from(*self);
1472 write!(f, "{v}")
1473 }
1474}
1475
1476impl TryFrom<Var> for PrincipalOrResource {
1477 type Error = Var;
1478
1479 fn try_from(value: Var) -> Result<Self, Self::Error> {
1480 match value {
1481 Var::Principal => Ok(Self::Principal),
1482 Var::Action => Err(Var::Action),
1483 Var::Resource => Ok(Self::Resource),
1484 Var::Context => Err(Var::Context),
1485 }
1486 }
1487}
1488
1489#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1492pub enum PrincipalOrResourceConstraint {
1493 Any,
1495 In(EntityReference),
1497 Eq(EntityReference),
1499 Is(Arc<EntityType>),
1501 IsIn(Arc<EntityType>, EntityReference),
1503}
1504
1505impl PrincipalOrResourceConstraint {
1506 pub fn any() -> Self {
1508 PrincipalOrResourceConstraint::Any
1509 }
1510
1511 pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1513 PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1514 }
1515
1516 pub fn is_eq_slot() -> Self {
1518 PrincipalOrResourceConstraint::Eq(EntityReference::Slot)
1519 }
1520
1521 pub fn is_in_slot() -> Self {
1523 PrincipalOrResourceConstraint::In(EntityReference::Slot)
1524 }
1525
1526 pub fn is_in(euid: Arc<EntityUID>) -> Self {
1528 PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1529 }
1530
1531 pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1533 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot)
1534 }
1535
1536 pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1538 PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1539 }
1540
1541 pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1543 PrincipalOrResourceConstraint::Is(entity_type)
1544 }
1545
1546 pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1550 match self {
1551 PrincipalOrResourceConstraint::Any => Expr::val(true),
1552 PrincipalOrResourceConstraint::Eq(euid) => {
1553 Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1554 }
1555 PrincipalOrResourceConstraint::In(euid) => {
1556 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1557 }
1558 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1559 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1560 Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1561 ),
1562 PrincipalOrResourceConstraint::Is(entity_type) => {
1563 Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1564 }
1565 }
1566 }
1567
1568 pub fn display(&self, v: PrincipalOrResource) -> String {
1572 match self {
1573 PrincipalOrResourceConstraint::In(euid) => {
1574 format!("{} in {}", v, euid.into_expr(v.into()))
1575 }
1576 PrincipalOrResourceConstraint::Eq(euid) => {
1577 format!("{} == {}", v, euid.into_expr(v.into()))
1578 }
1579 PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1580 format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1581 }
1582 PrincipalOrResourceConstraint::Is(entity_type) => {
1583 format!("{} is {}", v, entity_type)
1584 }
1585 PrincipalOrResourceConstraint::Any => format!("{}", v),
1586 }
1587 }
1588
1589 pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1591 match self {
1592 PrincipalOrResourceConstraint::Any => None,
1593 PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1594 PrincipalOrResourceConstraint::In(EntityReference::Slot) => None,
1595 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1596 PrincipalOrResourceConstraint::Eq(EntityReference::Slot) => None,
1597 PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1598 PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot) => None,
1599 PrincipalOrResourceConstraint::Is(_) => None,
1600 }
1601 }
1602
1603 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1605 self.get_euid()
1606 .into_iter()
1607 .map(|euid| euid.entity_type())
1608 .chain(match self {
1609 PrincipalOrResourceConstraint::Is(entity_type)
1610 | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1611 _ => None,
1612 })
1613 }
1614}
1615
1616#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1619pub enum ActionConstraint {
1620 Any,
1622 In(Vec<Arc<EntityUID>>),
1624 Eq(Arc<EntityUID>),
1626}
1627
1628impl std::fmt::Display for ActionConstraint {
1629 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1630 let render_euids =
1631 |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1632 match self {
1633 ActionConstraint::Any => write!(f, "action"),
1634 ActionConstraint::In(euids) => {
1635 write!(f, "action in [{}]", render_euids(euids))
1636 }
1637 ActionConstraint::Eq(euid) => write!(f, "action == {}", euid),
1638 }
1639 }
1640}
1641
1642impl ActionConstraint {
1643 pub fn any() -> Self {
1645 ActionConstraint::Any
1646 }
1647
1648 pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1650 ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1651 }
1652
1653 pub fn is_eq(euid: EntityUID) -> Self {
1655 ActionConstraint::Eq(Arc::new(euid))
1656 }
1657
1658 fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1659 Expr::set(euids.into_iter().map(Expr::val))
1660 }
1661
1662 pub fn as_expr(&self) -> Expr {
1664 match self {
1665 ActionConstraint::Any => Expr::val(true),
1666 ActionConstraint::In(euids) => Expr::is_in(
1667 Expr::var(Var::Action),
1668 ActionConstraint::euids_into_expr(euids.iter().cloned()),
1669 ),
1670 ActionConstraint::Eq(euid) => {
1671 Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1672 }
1673 }
1674 }
1675
1676 pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1678 match self {
1679 ActionConstraint::Any => EntityIterator::None,
1680 ActionConstraint::In(euids) => {
1681 EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1682 }
1683 ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1684 }
1685 }
1686
1687 pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1689 self.iter_euids().map(|euid| euid.entity_type())
1690 }
1691
1692 pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1695 match self {
1696 ActionConstraint::Any => Ok(self),
1697 ActionConstraint::In(ref euids) => {
1698 if let Some(euids) =
1699 NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1700 {
1701 Err(euids)
1702 } else {
1703 Ok(self)
1704 }
1705 }
1706 ActionConstraint::Eq(ref euid) => {
1707 if euid.is_action() {
1708 Ok(self)
1709 } else {
1710 Err(nonempty![euid.clone()])
1711 }
1712 }
1713 }
1714 }
1715}
1716
1717impl std::fmt::Display for StaticPolicy {
1718 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1719 for (k, v) in self.0.annotations.iter() {
1720 writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1721 }
1722 write!(
1723 f,
1724 "{}(\n {},\n {},\n {}\n) when {{\n {}\n}};",
1725 self.effect(),
1726 self.principal_constraint(),
1727 self.action_constraint(),
1728 self.resource_constraint(),
1729 self.non_scope_constraints()
1730 )
1731 }
1732}
1733
1734#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1736pub struct PolicyID(SmolStr);
1737
1738impl PolicyID {
1739 pub fn from_string(id: impl AsRef<str>) -> Self {
1741 Self(SmolStr::from(id.as_ref()))
1742 }
1743
1744 pub fn from_smolstr(id: SmolStr) -> Self {
1746 Self(id)
1747 }
1748}
1749
1750impl std::fmt::Display for PolicyID {
1751 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1752 write!(f, "{}", self.0.escape_debug())
1753 }
1754}
1755
1756impl AsRef<str> for PolicyID {
1757 fn as_ref(&self) -> &str {
1758 &self.0
1759 }
1760}
1761
1762#[cfg(feature = "arbitrary")]
1763impl<'u> arbitrary::Arbitrary<'u> for PolicyID {
1764 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1765 let s: String = u.arbitrary()?;
1766 Ok(PolicyID::from_string(s))
1767 }
1768 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1769 <String as arbitrary::Arbitrary>::size_hint(depth)
1770 }
1771}
1772
1773#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1775#[serde(rename_all = "camelCase")]
1776#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1777#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1778#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1779pub enum Effect {
1780 Permit,
1782 Forbid,
1784}
1785
1786impl std::fmt::Display for Effect {
1787 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1788 match self {
1789 Self::Permit => write!(f, "permit"),
1790 Self::Forbid => write!(f, "forbid"),
1791 }
1792 }
1793}
1794
1795enum EntityIterator<'a> {
1796 None,
1797 One(&'a EntityUID),
1798 Bunch(Vec<&'a EntityUID>),
1799}
1800
1801impl<'a> Iterator for EntityIterator<'a> {
1802 type Item = &'a EntityUID;
1803
1804 fn next(&mut self) -> Option<Self::Item> {
1805 match self {
1806 EntityIterator::None => None,
1807 EntityIterator::One(euid) => {
1808 let eptr = *euid;
1809 let mut ptr = EntityIterator::None;
1810 std::mem::swap(self, &mut ptr);
1811 Some(eptr)
1812 }
1813 EntityIterator::Bunch(v) => v.pop(),
1814 }
1815 }
1816}
1817
1818#[cfg(test)]
1819pub(crate) mod test_generators {
1820 use super::*;
1821
1822 pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
1823 let euid = Arc::new(EntityUID::with_eid("test"));
1824 let v = vec![
1825 PrincipalOrResourceConstraint::any(),
1826 PrincipalOrResourceConstraint::is_eq(euid.clone()),
1827 PrincipalOrResourceConstraint::Eq(EntityReference::Slot),
1828 PrincipalOrResourceConstraint::is_in(euid),
1829 PrincipalOrResourceConstraint::In(EntityReference::Slot),
1830 ];
1831
1832 v.into_iter()
1833 }
1834
1835 pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
1836 all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
1837 }
1838
1839 pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
1840 all_por_constraints().map(|constraint| ResourceConstraint { constraint })
1841 }
1842
1843 pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
1844 let euid: EntityUID = "Action::\"test\""
1845 .parse()
1846 .expect("Invalid action constraint euid");
1847 let v = vec![
1848 ActionConstraint::any(),
1849 ActionConstraint::is_eq(euid.clone()),
1850 ActionConstraint::is_in([euid.clone()]),
1851 ActionConstraint::is_in([euid.clone(), euid]),
1852 ];
1853
1854 v.into_iter()
1855 }
1856
1857 pub fn all_templates() -> impl Iterator<Item = Template> {
1858 let mut buf = vec![];
1859 let permit = PolicyID::from_string("permit");
1860 let forbid = PolicyID::from_string("forbid");
1861 for principal in all_principal_constraints() {
1862 for action in all_actions_constraints() {
1863 for resource in all_resource_constraints() {
1864 let permit = Template::new(
1865 permit.clone(),
1866 None,
1867 Annotations::new(),
1868 Effect::Permit,
1869 principal.clone(),
1870 action.clone(),
1871 resource.clone(),
1872 Expr::val(true),
1873 );
1874 let forbid = Template::new(
1875 forbid.clone(),
1876 None,
1877 Annotations::new(),
1878 Effect::Forbid,
1879 principal.clone(),
1880 action.clone(),
1881 resource.clone(),
1882 Expr::val(true),
1883 );
1884 buf.push(permit);
1885 buf.push(forbid);
1886 }
1887 }
1888 }
1889 buf.into_iter()
1890 }
1891}
1892
1893#[cfg(test)]
1894#[allow(clippy::indexing_slicing)]
1896#[allow(clippy::panic)]
1898mod test {
1899 use cool_asserts::assert_matches;
1900 use std::collections::HashSet;
1901
1902 use super::{test_generators::*, *};
1903 use crate::{
1904 parser::{
1905 parse_policy,
1906 test_utils::{expect_exactly_one_error, expect_some_error_matches},
1907 },
1908 test_utils::ExpectedErrorMessageBuilder,
1909 };
1910
1911 #[test]
1912 fn literal_and_borrowed() {
1913 for template in all_templates() {
1914 let t = Arc::new(template);
1915 let env = t
1916 .slots()
1917 .map(|slot| (slot.id, EntityUID::with_eid("eid")))
1918 .collect();
1919 let p = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
1920
1921 let b_literal = BorrowedLiteralPolicy::from(&p);
1922 let src = serde_json::to_string(&b_literal).expect("ser error");
1923 let literal: LiteralPolicy = serde_json::from_str(&src).expect("de error");
1924
1925 assert_eq!(b_literal.template_id, &literal.template_id);
1926 assert_eq!(b_literal.link_id, literal.link_id.as_ref());
1927 assert_eq!(b_literal.values, &literal.values);
1928 }
1929 }
1930
1931 #[test]
1932 fn template_roundtrip() {
1933 for template in all_templates() {
1934 template.check_invariant();
1935 let json = serde_json::to_string(&template).expect("Serialization Failed");
1936 let t2 = serde_json::from_str::<Template>(&json).expect("Deserialization failed");
1937 t2.check_invariant();
1938 assert_eq!(template, t2);
1939 }
1940 }
1941
1942 #[test]
1943 fn test_template_rebuild() {
1944 for template in all_templates() {
1945 let id = template.id().clone();
1946 let effect = template.effect();
1947 let p = template.principal_constraint().clone();
1948 let a = template.action_constraint().clone();
1949 let r = template.resource_constraint().clone();
1950 let non_scope = template.non_scope_constraints().clone();
1951 let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
1952 assert_eq!(template, t2);
1953 }
1954 }
1955
1956 #[test]
1957 fn test_inline_policy_rebuild() {
1958 for template in all_templates() {
1959 if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
1960 let id = ip.id().clone();
1961 let e = ip.effect();
1962 let anno = ip
1963 .annotations()
1964 .map(|(k, v)| (k.clone(), v.clone()))
1965 .collect();
1966 let p = ip.principal_constraint().clone();
1967 let a = ip.action_constraint().clone();
1968 let r = ip.resource_constraint().clone();
1969 let non_scope = ip.non_scope_constraints().clone();
1970 let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope)
1971 .expect("Policy Creation Failed");
1972 assert_eq!(ip, ip2);
1973 let (t2, inst) = Template::link_static_policy(ip2);
1974 assert!(inst.is_static());
1975 assert_eq!(&template, t2.as_ref());
1976 }
1977 }
1978 }
1979
1980 #[test]
1981 fn ir_binding_too_many() {
1982 let tid = PolicyID::from_string("tid");
1983 let iid = PolicyID::from_string("iid");
1984 let t = Arc::new(Template::new(
1985 tid,
1986 None,
1987 Annotations::new(),
1988 Effect::Forbid,
1989 PrincipalConstraint::is_eq_slot(),
1990 ActionConstraint::Any,
1991 ResourceConstraint::any(),
1992 Expr::val(true),
1993 ));
1994 let mut m = HashMap::new();
1995 m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
1996 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
1997 assert_eq!(unbound_values, vec![SlotId::principal()]);
1998 assert_eq!(extra_values, vec![SlotId::resource()]);
1999 });
2000 }
2001
2002 #[test]
2003 fn ir_binding_too_few() {
2004 let tid = PolicyID::from_string("tid");
2005 let iid = PolicyID::from_string("iid");
2006 let t = Arc::new(Template::new(
2007 tid,
2008 None,
2009 Annotations::new(),
2010 Effect::Forbid,
2011 PrincipalConstraint::is_eq_slot(),
2012 ActionConstraint::Any,
2013 ResourceConstraint::is_in_slot(),
2014 Expr::val(true),
2015 ));
2016 assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2017 assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
2018 assert_eq!(extra_values, vec![]);
2019 });
2020 let mut m = HashMap::new();
2021 m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
2022 assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2023 assert_eq!(unbound_values, vec![SlotId::resource()]);
2024 assert_eq!(extra_values, vec![]);
2025 });
2026 }
2027
2028 #[test]
2029 fn ir_binding() {
2030 let tid = PolicyID::from_string("template");
2031 let iid = PolicyID::from_string("linked");
2032 let t = Arc::new(Template::new(
2033 tid,
2034 None,
2035 Annotations::new(),
2036 Effect::Permit,
2037 PrincipalConstraint::is_in_slot(),
2038 ActionConstraint::any(),
2039 ResourceConstraint::is_eq_slot(),
2040 Expr::val(true),
2041 ));
2042
2043 let mut m = HashMap::new();
2044 m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2045 m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2046
2047 let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2048 assert_eq!(r.id(), &iid);
2049 assert_eq!(
2050 r.env().get(&SlotId::principal()),
2051 Some(&EntityUID::with_eid("theprincipal"))
2052 );
2053 assert_eq!(
2054 r.env().get(&SlotId::resource()),
2055 Some(&EntityUID::with_eid("theresource"))
2056 );
2057 }
2058
2059 #[test]
2060 fn isnt_template_implies_from_succeeds() {
2061 for template in all_templates() {
2062 if template.slots().count() == 0 {
2063 StaticPolicy::try_from(template).expect("Should succeed");
2064 }
2065 }
2066 }
2067
2068 #[test]
2069 fn is_template_implies_from_fails() {
2070 for template in all_templates() {
2071 if template.slots().count() != 0 {
2072 assert!(
2073 StaticPolicy::try_from(template.clone()).is_err(),
2074 "Following template did convert {template}"
2075 );
2076 }
2077 }
2078 }
2079
2080 #[test]
2081 fn non_template_iso() {
2082 for template in all_templates() {
2083 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2084 let (t2, _) = Template::link_static_policy(p);
2085 assert_eq!(&template, t2.as_ref());
2086 }
2087 }
2088 }
2089
2090 #[test]
2091 fn template_into_expr() {
2092 for template in all_templates() {
2093 if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2094 let t: Template = template;
2095 assert_eq!(p.condition(), t.condition());
2096 assert_eq!(p.effect(), t.effect());
2097 }
2098 }
2099 }
2100
2101 #[test]
2102 fn template_por_iter() {
2103 let e = Arc::new(EntityUID::with_eid("eid"));
2104 assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2105 assert_eq!(
2106 PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2107 Some(&e)
2108 );
2109 assert_eq!(
2110 PrincipalOrResourceConstraint::In(EntityReference::Slot).get_euid(),
2111 None
2112 );
2113 assert_eq!(
2114 PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2115 Some(&e)
2116 );
2117 assert_eq!(
2118 PrincipalOrResourceConstraint::Eq(EntityReference::Slot).get_euid(),
2119 None
2120 );
2121 assert_eq!(
2122 PrincipalOrResourceConstraint::IsIn(
2123 Arc::new("T".parse().unwrap()),
2124 EntityReference::EUID(e.clone())
2125 )
2126 .get_euid(),
2127 Some(&e)
2128 );
2129 assert_eq!(
2130 PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2131 None
2132 );
2133 assert_eq!(
2134 PrincipalOrResourceConstraint::IsIn(
2135 Arc::new("T".parse().unwrap()),
2136 EntityReference::Slot
2137 )
2138 .get_euid(),
2139 None
2140 );
2141 }
2142
2143 #[test]
2144 fn action_iter() {
2145 assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2146 let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2147 let v = a.iter_euids().collect::<Vec<_>>();
2148 assert_eq!(vec![&EntityUID::with_eid("test")], v);
2149 let a =
2150 ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2151 let set = a.iter_euids().collect::<HashSet<_>>();
2152 let e1 = EntityUID::with_eid("test1");
2153 let e2 = EntityUID::with_eid("test2");
2154 let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2155 assert_eq!(set, correct);
2156 }
2157
2158 #[test]
2159 fn test_iter_none() {
2160 let mut i = EntityIterator::None;
2161 assert_eq!(i.next(), None);
2162 }
2163
2164 #[test]
2165 fn test_iter_once() {
2166 let id = EntityUID::from_components(
2167 name::Name::parse_unqualified_name("s").unwrap().into(),
2168 entity::Eid::new("eid"),
2169 None,
2170 );
2171 let mut i = EntityIterator::One(&id);
2172 assert_eq!(i.next(), Some(&id));
2173 assert_eq!(i.next(), None);
2174 }
2175
2176 #[test]
2177 fn test_iter_mult() {
2178 let id1 = EntityUID::from_components(
2179 name::Name::parse_unqualified_name("s").unwrap().into(),
2180 entity::Eid::new("eid1"),
2181 None,
2182 );
2183 let id2 = EntityUID::from_components(
2184 name::Name::parse_unqualified_name("s").unwrap().into(),
2185 entity::Eid::new("eid2"),
2186 None,
2187 );
2188 let v = vec![&id1, &id2];
2189 let mut i = EntityIterator::Bunch(v);
2190 assert_eq!(i.next(), Some(&id2));
2191 assert_eq!(i.next(), Some(&id1));
2192 assert_eq!(i.next(), None)
2193 }
2194
2195 #[test]
2196 fn euid_into_expr() {
2197 let e = EntityReference::Slot;
2198 assert_eq!(
2199 e.into_expr(SlotId::principal()),
2200 Expr::slot(SlotId::principal())
2201 );
2202 let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2203 assert_eq!(
2204 e.into_expr(SlotId::principal()),
2205 Expr::val(EntityUID::with_eid("eid"))
2206 );
2207 }
2208
2209 #[test]
2210 fn por_constraint_display() {
2211 let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot);
2212 let s = t.display(PrincipalOrResource::Principal);
2213 assert_eq!(s, "principal == ?principal");
2214 let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2215 EntityUID::with_eid("test"),
2216 )));
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(PolicyID::from_string("id")), 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(PolicyID::from_string("id")), 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}