cedar_policy_core/ast/
policy.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::ast::*;
18use crate::parser::Loc;
19use annotation::{Annotation, Annotations};
20use educe::Educe;
21use itertools::Itertools;
22use linked_hash_map::LinkedHashMap;
23use miette::Diagnostic;
24use nonempty::{nonempty, NonEmpty};
25use serde::{Deserialize, Serialize};
26use smol_str::SmolStr;
27use std::{
28    collections::{HashMap, HashSet},
29    str::FromStr,
30    sync::Arc,
31};
32use thiserror::Error;
33
34#[cfg(feature = "wasm")]
35extern crate tsify;
36
37macro_rules! cfg_tolerant_ast {
38    ($($item:item)*) => {
39        $(
40            #[cfg(feature = "tolerant-ast")]
41            $item
42        )*
43    };
44}
45
46cfg_tolerant_ast! {
47    use super::expr_allows_errors::AstExprErrorKind;
48    use crate::ast::expr_allows_errors::ExprWithErrsBuilder;
49    use crate::expr_builder::ExprBuilder;
50    use crate::parser::err::ParseErrors;
51    use crate::parser::err::ToASTError;
52    use crate::parser::err::ToASTErrorKind;
53
54    static DEFAULT_ANNOTATIONS: std::sync::LazyLock<Arc<Annotations>> =
55        std::sync::LazyLock::new(|| Arc::new(Annotations::default()));
56
57    static DEFAULT_PRINCIPAL_CONSTRAINT: std::sync::LazyLock<PrincipalConstraint> =
58        std::sync::LazyLock::new(PrincipalConstraint::any);
59
60    static DEFAULT_RESOURCE_CONSTRAINT: std::sync::LazyLock<ResourceConstraint> =
61        std::sync::LazyLock::new(ResourceConstraint::any);
62
63    static DEFAULT_ACTION_CONSTRAINT: std::sync::LazyLock<ActionConstraint> =
64        std::sync::LazyLock::new(ActionConstraint::any);
65
66    static DEFAULT_ERROR_EXPR: std::sync::LazyLock<Arc<Expr>> = std::sync::LazyLock::new(|| {
67        // Non scope constraint expression of an Error policy should also be an error
68        // This const represents an error expression that is part of an Error policy
69        // PANIC SAFETY: Infallible error type - can never fail
70        #[allow(clippy::unwrap_used)]
71        Arc::new(
72            <ExprWithErrsBuilder as ExprBuilder>::new()
73                .error(ParseErrors::singleton(ToASTError::new(
74                    ToASTErrorKind::ASTErrorNode,
75                    Some(Loc::new(0..1, "ASTErrorNode".into())),
76                )))
77                .unwrap(),
78        )
79    });
80}
81
82/// Top level structure for a policy template.
83/// Contains both the AST for template, and the list of open slots in the template.
84///
85/// Note that this "template" may have no slots, in which case this `Template` represents a static policy
86#[derive(Clone, Hash, Eq, PartialEq, Debug)]
87pub struct Template {
88    body: TemplateBody,
89    /// INVARIANT (slot cache correctness): This Vec must contain _all_ of the open slots in `body`
90    /// This is maintained by the only public constructors: `new()`, `new_shared()`, and `link_static_policy()`
91    ///
92    /// Note that `slots` may be empty, in which case this `Template` represents a static policy
93    slots: Vec<Slot>,
94}
95
96impl From<Template> for TemplateBody {
97    fn from(val: Template) -> Self {
98        val.body
99    }
100}
101
102impl Template {
103    /// Checks the invariant (slot cache correctness)
104    ///
105    /// This function is a no-op in release builds, but checks the invariant (and panics if it fails) in debug builds.
106    pub fn check_invariant(&self) {
107        #[cfg(debug_assertions)]
108        {
109            for slot in self.body.condition().slots() {
110                assert!(self.slots.contains(&slot));
111            }
112            for slot in self.slots() {
113                assert!(self.body.condition().slots().contains(slot));
114            }
115        }
116    }
117
118    /// Construct a `Template` from its components
119    #[allow(clippy::too_many_arguments)]
120    pub fn new(
121        id: PolicyID,
122        loc: Option<Loc>,
123        annotations: Annotations,
124        effect: Effect,
125        principal_constraint: PrincipalConstraint,
126        action_constraint: ActionConstraint,
127        resource_constraint: ResourceConstraint,
128        non_scope_constraint: Option<Expr>,
129    ) -> Self {
130        let body = TemplateBody::new(
131            id,
132            loc,
133            annotations,
134            effect,
135            principal_constraint,
136            action_constraint,
137            resource_constraint,
138            non_scope_constraint,
139        );
140        // INVARIANT (slot cache correctness)
141        // This invariant is maintained in the body of the From impl
142        Template::from(body)
143    }
144
145    #[cfg(feature = "tolerant-ast")]
146    /// Generate a template representing a policy that is unparsable
147    pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
148        let body = TemplateBody::error(id, loc);
149        Template::from(body)
150    }
151
152    /// Construct a template from an expression/annotations that are already [`std::sync::Arc`] allocated
153    #[allow(clippy::too_many_arguments)]
154    pub fn new_shared(
155        id: PolicyID,
156        loc: Option<Loc>,
157        annotations: Arc<Annotations>,
158        effect: Effect,
159        principal_constraint: PrincipalConstraint,
160        action_constraint: ActionConstraint,
161        resource_constraint: ResourceConstraint,
162        non_scope_constraint: Option<Arc<Expr>>,
163    ) -> Self {
164        let body = TemplateBody::new_shared(
165            id,
166            loc,
167            annotations,
168            effect,
169            principal_constraint,
170            action_constraint,
171            resource_constraint,
172            non_scope_constraint,
173        );
174        // INVARIANT (slot cache correctness)
175        // This invariant is maintained in the body of the From impl
176        Template::from(body)
177    }
178
179    /// Get the principal constraint on the body
180    pub fn principal_constraint(&self) -> &PrincipalConstraint {
181        self.body.principal_constraint()
182    }
183
184    /// Get the action constraint on the body
185    pub fn action_constraint(&self) -> &ActionConstraint {
186        self.body.action_constraint()
187    }
188
189    /// Get the resource constraint on the body
190    pub fn resource_constraint(&self) -> &ResourceConstraint {
191        self.body.resource_constraint()
192    }
193
194    /// Get the non-scope constraint on the body
195    pub fn non_scope_constraints(&self) -> Option<&Expr> {
196        self.body.non_scope_constraints()
197    }
198
199    /// Get Arc to non-scope constraint on the body
200    pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
201        self.body.non_scope_constraints_arc()
202    }
203
204    /// Get the PolicyID of this template
205    pub fn id(&self) -> &PolicyID {
206        self.body.id()
207    }
208
209    /// Clone this Policy with a new ID
210    pub fn new_id(&self, id: PolicyID) -> Self {
211        Template {
212            body: self.body.new_id(id),
213            slots: self.slots.clone(),
214        }
215    }
216
217    /// Get the location of this policy
218    pub fn loc(&self) -> Option<&Loc> {
219        self.body.loc()
220    }
221
222    /// Get the `Effect` (`Permit` or `Deny`) of this template
223    pub fn effect(&self) -> Effect {
224        self.body.effect()
225    }
226
227    /// Get data from an annotation.
228    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
229        self.body.annotation(key)
230    }
231
232    /// Get all annotation data.
233    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
234        self.body.annotations()
235    }
236
237    /// Get [`Arc`] owning the annotation data.
238    pub fn annotations_arc(&self) -> &Arc<Annotations> {
239        self.body.annotations_arc()
240    }
241
242    /// Get the condition expression of this template.
243    ///
244    /// This will be a conjunction of the template's scope constraints (on
245    /// principal, resource, and action); the template's "when" conditions; and
246    /// the negation of each of the template's "unless" conditions.
247    pub fn condition(&self) -> Expr {
248        self.body.condition()
249    }
250
251    /// List of open slots in this template
252    pub fn slots(&self) -> impl Iterator<Item = &Slot> {
253        self.slots.iter()
254    }
255
256    /// Check if this template is a static policy
257    ///
258    /// Static policies can be linked without any slots,
259    /// and all links will be identical.
260    pub fn is_static(&self) -> bool {
261        self.slots.is_empty()
262    }
263
264    /// Ensure that every slot in the template is bound by values,
265    /// and that no extra values are bound in values
266    /// This upholds invariant (values total map)
267    pub fn check_binding(
268        template: &Template,
269        values: &HashMap<SlotId, EntityUID>,
270    ) -> Result<(), LinkingError> {
271        // Verify all slots bound
272        let unbound = template
273            .slots
274            .iter()
275            .filter(|slot| !values.contains_key(&slot.id))
276            .collect::<Vec<_>>();
277
278        let extra = values
279            .iter()
280            .filter_map(|(slot, _)| {
281                if !template
282                    .slots
283                    .iter()
284                    .any(|template_slot| template_slot.id == *slot)
285                {
286                    Some(slot)
287                } else {
288                    None
289                }
290            })
291            .collect::<Vec<_>>();
292
293        if unbound.is_empty() && extra.is_empty() {
294            Ok(())
295        } else {
296            Err(LinkingError::from_unbound_and_extras(
297                unbound.into_iter().map(|slot| slot.id),
298                extra.into_iter().copied(),
299            ))
300        }
301    }
302
303    /// Attempt to create a template-linked policy from this template.
304    /// This will fail if values for all open slots are not given.
305    /// `new_instance_id` is the `PolicyId` for the created template-linked policy.
306    pub fn link(
307        template: Arc<Template>,
308        new_id: PolicyID,
309        values: HashMap<SlotId, EntityUID>,
310    ) -> Result<Policy, LinkingError> {
311        // INVARIANT (policy total map) Relies on check_binding to uphold the invariant
312        Template::check_binding(&template, &values)
313            .map(|_| Policy::new(template, Some(new_id), values))
314    }
315
316    /// Take a static policy and create a template and a template-linked policy for it.
317    /// They will share the same ID
318    pub fn link_static_policy(p: StaticPolicy) -> (Arc<Template>, Policy) {
319        let body: TemplateBody = p.into();
320        // INVARIANT (slot cache correctness):
321        // StaticPolicy by invariant (inline policy correctness)
322        // can have no slots, so it is safe to make `slots` the empty vec
323        let t = Arc::new(Self {
324            body,
325            slots: vec![],
326        });
327        t.check_invariant();
328        let p = Policy::new(Arc::clone(&t), None, HashMap::new());
329        (t, p)
330    }
331}
332
333impl From<TemplateBody> for Template {
334    fn from(body: TemplateBody) -> Self {
335        // INVARIANT: (slot cache correctness)
336        // Pull all the slots out of the template body's condition.
337        let slots = body.condition().slots().collect::<Vec<_>>();
338        Self { body, slots }
339    }
340}
341
342impl std::fmt::Display for Template {
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        write!(f, "{}", self.body)
345    }
346}
347
348/// Errors linking templates
349#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, Error)]
350pub enum LinkingError {
351    /// An error with the slot arguments provided
352    // INVARIANT: `unbound_values` and `extra_values` can't both be empty
353    #[error(fmt = describe_arity_error)]
354    ArityError {
355        /// Error for when some Slots were not provided values
356        unbound_values: Vec<SlotId>,
357        /// Error for when more values than Slots are provided
358        extra_values: Vec<SlotId>,
359    },
360
361    /// The attempted linking failed as the template did not exist.
362    #[error("failed to find a template with id `{id}`")]
363    NoSuchTemplate {
364        /// [`PolicyID`] of the template we failed to find
365        id: PolicyID,
366    },
367
368    /// The new instance conflicts with an existing [`PolicyID`].
369    #[error("template-linked policy id `{id}` conflicts with an existing policy id")]
370    PolicyIdConflict {
371        /// [`PolicyID`] where the conflict exists
372        id: PolicyID,
373    },
374}
375
376impl LinkingError {
377    fn from_unbound_and_extras(
378        unbound: impl Iterator<Item = SlotId>,
379        extra: impl Iterator<Item = SlotId>,
380    ) -> Self {
381        Self::ArityError {
382            unbound_values: unbound.collect(),
383            extra_values: extra.collect(),
384        }
385    }
386}
387
388fn describe_arity_error(
389    unbound_values: &[SlotId],
390    extra_values: &[SlotId],
391    fmt: &mut std::fmt::Formatter<'_>,
392) -> std::fmt::Result {
393    match (unbound_values.len(), extra_values.len()) {
394        // PANIC SAFETY 0,0 case is not an error
395        #[allow(clippy::unreachable)]
396        (0,0) => unreachable!(),
397        (_unbound, 0) => write!(fmt, "the following slots were not provided as arguments: {}", unbound_values.iter().join(",")),
398        (0, _extra) => write!(fmt, "the following slots were provided as arguments, but did not exist in the template: {}", extra_values.iter().join(",")),
399        (_unbound, _extra) => write!(fmt, "the following slots were not provided as arguments: {}. The following slots were provided as arguments, but did not exist in the template: {}", unbound_values.iter().join(","), extra_values.iter().join(",")),
400    }
401}
402
403/// A Policy that contains:
404///   - a pointer to its template
405///   - a link ID (unless it's a static policy)
406///   - the bound values for slots in the template
407///
408/// Policies are not serializable (due to the pointer), and can be serialized
409/// by converting to/from LiteralPolicy
410#[derive(Debug, Clone, Eq, PartialEq)]
411pub struct Policy {
412    /// Reference to the template
413    template: Arc<Template>,
414    /// Id of this link
415    ///
416    /// None in the case that this is an instance of a Static Policy
417    link: Option<PolicyID>,
418    // INVARIANT (values total map)
419    // All of the slots in `template` MUST be bound by `values`
420    //
421    /// values the slots are bound to.
422    /// The constructor `new` is only visible in this module,
423    /// so it is the responsibility of callers to maintain
424    values: HashMap<SlotId, EntityUID>,
425}
426
427impl Policy {
428    /// Link a policy to its template
429    /// INVARIANT (values total map):
430    /// `values` must bind every open slot in `template`
431    fn new(template: Arc<Template>, link_id: Option<PolicyID>, values: SlotEnv) -> Self {
432        #[cfg(debug_assertions)]
433        {
434            // PANIC SAFETY: asserts (value total map invariant) which is justified at call sites
435            #[allow(clippy::expect_used)]
436            Template::check_binding(&template, &values).expect("(values total map) does not hold!");
437        }
438        Self {
439            template,
440            link: link_id,
441            values,
442        }
443    }
444
445    /// Build a policy with a given effect, given when clause, and unconstrained scope variables
446    pub fn from_when_clause(effect: Effect, when: Expr, id: PolicyID, loc: Option<Loc>) -> Self {
447        Self::from_when_clause_annos(
448            effect,
449            Arc::new(when),
450            id,
451            loc,
452            Arc::new(Annotations::default()),
453        )
454    }
455
456    /// Build a policy with a given effect, given when clause, and unconstrained scope variables
457    pub fn from_when_clause_annos(
458        effect: Effect,
459        when: Arc<Expr>,
460        id: PolicyID,
461        loc: Option<Loc>,
462        annotations: Arc<Annotations>,
463    ) -> Self {
464        let t = Template::new_shared(
465            id,
466            loc,
467            annotations,
468            effect,
469            PrincipalConstraint::any(),
470            ActionConstraint::any(),
471            ResourceConstraint::any(),
472            Some(when),
473        );
474        Self::new(Arc::new(t), None, SlotEnv::new())
475    }
476
477    /// Get pointer to the template for this policy
478    pub fn template(&self) -> &Template {
479        &self.template
480    }
481
482    /// Get pointer to the template for this policy, as an `Arc`
483    pub(crate) fn template_arc(&self) -> Arc<Template> {
484        Arc::clone(&self.template)
485    }
486
487    /// Get the effect (forbid or permit) of this policy.
488    pub fn effect(&self) -> Effect {
489        self.template.effect()
490    }
491
492    /// Get data from an annotation.
493    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
494        self.template.annotation(key)
495    }
496
497    /// Get all annotation data.
498    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
499        self.template.annotations()
500    }
501
502    /// Get [`Arc`] owning annotation data.
503    pub fn annotations_arc(&self) -> &Arc<Annotations> {
504        self.template.annotations_arc()
505    }
506
507    /// Get the principal constraint for this policy.
508    ///
509    /// By the invariant, this principal constraint will not contain
510    /// (unresolved) slots, so you will not get `EntityReference::Slot` anywhere
511    /// in it.
512    pub fn principal_constraint(&self) -> PrincipalConstraint {
513        let constraint = self.template.principal_constraint().clone();
514        match self.values.get(&SlotId::principal()) {
515            None => constraint,
516            Some(principal) => constraint.with_filled_slot(Arc::new(principal.clone())),
517        }
518    }
519
520    /// Get the action constraint for this policy.
521    pub fn action_constraint(&self) -> &ActionConstraint {
522        self.template.action_constraint()
523    }
524
525    /// Get the resource constraint for this policy.
526    ///
527    /// By the invariant, this resource constraint will not contain
528    /// (unresolved) slots, so you will not get `EntityReference::Slot` anywhere
529    /// in it.
530    pub fn resource_constraint(&self) -> ResourceConstraint {
531        let constraint = self.template.resource_constraint().clone();
532        match self.values.get(&SlotId::resource()) {
533            None => constraint,
534            Some(resource) => constraint.with_filled_slot(Arc::new(resource.clone())),
535        }
536    }
537
538    /// Get the non-scope constraints for the policy
539    pub fn non_scope_constraints(&self) -> Option<&Expr> {
540        self.template.non_scope_constraints()
541    }
542
543    /// Get the [`Arc`] owning non-scope constraints for the policy
544    pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
545        self.template.non_scope_constraints_arc()
546    }
547
548    /// Get the expression that represents this policy.
549    pub fn condition(&self) -> Expr {
550        self.template.condition()
551    }
552
553    /// Get the mapping from SlotIds to EntityUIDs for this policy. (This will
554    /// be empty for inline policies.)
555    pub fn env(&self) -> &SlotEnv {
556        &self.values
557    }
558
559    /// Get the ID of this policy.
560    pub fn id(&self) -> &PolicyID {
561        self.link.as_ref().unwrap_or_else(|| self.template.id())
562    }
563
564    /// Clone this policy or instance with a new ID
565    pub fn new_id(&self, id: PolicyID) -> Self {
566        match self.link {
567            None => Policy {
568                template: Arc::new(self.template.new_id(id)),
569                link: None,
570                values: self.values.clone(),
571            },
572            Some(_) => Policy {
573                template: self.template.clone(),
574                link: Some(id),
575                values: self.values.clone(),
576            },
577        }
578    }
579
580    /// Clone this template instance with a new ID for the template.
581    /// Returns `None` if this is a static policy.
582    pub(crate) fn new_template_id(self, id: PolicyID) -> Option<Self> {
583        self.link.map(|link| Policy {
584            template: Arc::new(self.template.new_id(id)),
585            link: Some(link),
586            values: self.values,
587        })
588    }
589
590    /// Get the location of this policy
591    pub fn loc(&self) -> Option<&Loc> {
592        self.template.loc()
593    }
594
595    /// Returns true if this policy is an inline policy
596    pub fn is_static(&self) -> bool {
597        self.link.is_none()
598    }
599
600    /// Returns all the unknown entities in the policy during evaluation
601    pub fn unknown_entities(&self) -> HashSet<EntityUID> {
602        self.condition()
603            .unknowns()
604            .filter_map(
605                |Unknown {
606                     name,
607                     type_annotation,
608                 }| {
609                    if matches!(type_annotation, Some(Type::Entity { .. })) {
610                        EntityUID::from_str(name.as_str()).ok()
611                    } else {
612                        None
613                    }
614                },
615            )
616            .collect()
617    }
618}
619
620impl std::fmt::Display for Policy {
621    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622        if self.is_static() {
623            write!(f, "{}", self.template())
624        } else {
625            write!(
626                f,
627                "Template Instance of {}, slots: [{}]",
628                self.template().id(),
629                display_slot_env(self.env())
630            )
631        }
632    }
633}
634
635/// Map from Slot Ids to Entity UIDs which fill the slots
636pub type SlotEnv = HashMap<SlotId, EntityUID>;
637
638/// Represents either a static policy or a template linked policy.
639///
640/// Contains less rich information than `Policy`. In particular, this form is
641/// easier to convert to/from the Protobuf representation of a `Policy`, because
642/// it simply refers to the `Template` by its Id and does not contain a
643/// reference to the `Template` itself.
644#[derive(Debug, Clone, PartialEq, Eq)]
645pub struct LiteralPolicy {
646    /// ID of the template this policy is an instance of
647    template_id: PolicyID,
648    /// ID of this link.
649    /// This is `None` for static policies, and the static policy ID is defined
650    /// as the `template_id`
651    link_id: Option<PolicyID>,
652    /// Values of the slots
653    values: SlotEnv,
654}
655
656impl LiteralPolicy {
657    /// Create a `LiteralPolicy` representing a static policy with the given ID.
658    ///
659    /// The policy set should also contain a (zero-slot) `Template` with the given ID.
660    pub fn static_policy(template_id: PolicyID) -> Self {
661        Self {
662            template_id,
663            link_id: None,
664            values: SlotEnv::new(),
665        }
666    }
667
668    /// Create a `LiteralPolicy` representing a template-linked policy.
669    ///
670    /// The policy set should also contain the associated `Template`.
671    pub fn template_linked_policy(
672        template_id: PolicyID,
673        link_id: PolicyID,
674        values: SlotEnv,
675    ) -> Self {
676        Self {
677            template_id,
678            link_id: Some(link_id),
679            values,
680        }
681    }
682
683    /// Get the `EntityUID` associated with the given `SlotId`, if it exists
684    pub fn value(&self, slot: &SlotId) -> Option<&EntityUID> {
685        self.values.get(slot)
686    }
687}
688
689// Can we verify the hash property?
690
691impl std::hash::Hash for LiteralPolicy {
692    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
693        self.template_id.hash(state);
694        // this shouldn't be a performance issue as these vectors should be small
695        let mut buf = self.values.iter().collect::<Vec<_>>();
696        buf.sort();
697        for (id, euid) in buf {
698            id.hash(state);
699            euid.hash(state);
700        }
701    }
702}
703
704// These would be great as property tests
705#[cfg(test)]
706mod hashing_tests {
707    use std::{
708        collections::hash_map::DefaultHasher,
709        hash::{Hash, Hasher},
710    };
711
712    use super::*;
713
714    fn compute_hash(ir: &LiteralPolicy) -> u64 {
715        let mut s = DefaultHasher::new();
716        ir.hash(&mut s);
717        s.finish()
718    }
719
720    fn build_template_linked_policy() -> LiteralPolicy {
721        let mut map = HashMap::new();
722        map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
723        LiteralPolicy {
724            template_id: PolicyID::from_string("template"),
725            link_id: Some(PolicyID::from_string("id")),
726            values: map,
727        }
728    }
729
730    #[test]
731    fn hash_property_instances() {
732        let a = build_template_linked_policy();
733        let b = build_template_linked_policy();
734        assert_eq!(a, b);
735        assert_eq!(compute_hash(&a), compute_hash(&b));
736    }
737}
738
739/// Errors that can happen during policy reification
740#[derive(Debug, Diagnostic, Error)]
741pub enum ReificationError {
742    /// The [`PolicyID`] linked to did not exist
743    #[error("the id linked to does not exist")]
744    NoSuchTemplate(PolicyID),
745    /// Error linking the policy
746    #[error(transparent)]
747    #[diagnostic(transparent)]
748    Linking(#[from] LinkingError),
749}
750
751impl LiteralPolicy {
752    /// Attempt to reify this template linked policy.
753    /// Ensures the linked template actually exists, replaces the id with a reference to the underlying template.
754    /// Fails if the template does not exist.
755    /// Consumes the policy.
756    pub fn reify(
757        self,
758        templates: &LinkedHashMap<PolicyID, Arc<Template>>,
759    ) -> Result<Policy, ReificationError> {
760        let template = templates
761            .get(&self.template_id)
762            .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
763        // INVARIANT (values total map)
764        Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
765        Ok(Policy::new(template.clone(), self.link_id, self.values))
766    }
767
768    /// Lookup the euid bound by a SlotId
769    pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
770        self.values.get(id)
771    }
772
773    /// Get the [`PolicyID`] of this static or template-linked policy.
774    pub fn id(&self) -> &PolicyID {
775        self.link_id.as_ref().unwrap_or(&self.template_id)
776    }
777
778    /// Get the [`PolicyID`] of the template associated with this policy.
779    ///
780    /// For static policies, this is just the static policy ID.
781    pub fn template_id(&self) -> &PolicyID {
782        &self.template_id
783    }
784
785    /// Is this a static policy
786    pub fn is_static(&self) -> bool {
787        self.link_id.is_none()
788    }
789}
790
791fn display_slot_env(env: &SlotEnv) -> String {
792    env.iter()
793        .map(|(slot, value)| format!("{slot} -> {value}"))
794        .join(",")
795}
796
797impl std::fmt::Display for LiteralPolicy {
798    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
799        if self.is_static() {
800            write!(f, "Static policy w/ ID {}", self.template_id())
801        } else {
802            write!(
803                f,
804                "Template linked policy of {}, slots: [{}]",
805                self.template_id(),
806                display_slot_env(&self.values),
807            )
808        }
809    }
810}
811
812impl From<Policy> for LiteralPolicy {
813    fn from(p: Policy) -> Self {
814        Self {
815            template_id: p.template.id().clone(),
816            link_id: p.link,
817            values: p.values,
818        }
819    }
820}
821
822/// Static Policies are policy that do not come from templates.
823/// They have the same structure as a template definition, but cannot contain slots
824// INVARIANT: (Static Policy Correctness): A Static Policy TemplateBody must have zero slots
825#[derive(Clone, Hash, Eq, PartialEq, Debug)]
826pub struct StaticPolicy(TemplateBody);
827
828impl StaticPolicy {
829    /// Get the `Id` of this policy.
830    pub fn id(&self) -> &PolicyID {
831        self.0.id()
832    }
833
834    /// Clone this policy with a new `Id`.
835    pub fn new_id(&self, id: PolicyID) -> Self {
836        StaticPolicy(self.0.new_id(id))
837    }
838
839    /// Get the location of this policy
840    pub fn loc(&self) -> Option<&Loc> {
841        self.0.loc()
842    }
843
844    /// Get the `Effect` of this policy.
845    pub fn effect(&self) -> Effect {
846        self.0.effect()
847    }
848
849    /// Get data from an annotation.
850    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
851        self.0.annotation(key)
852    }
853
854    /// Get all annotation data.
855    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
856        self.0.annotations()
857    }
858
859    /// Get the `principal` scope constraint of this policy.
860    pub fn principal_constraint(&self) -> &PrincipalConstraint {
861        self.0.principal_constraint()
862    }
863
864    /// Get the `principal` scope constraint as an expression.
865    /// This will be a boolean-valued expression: either `true` (if the policy
866    /// just has `principal,`), or an equality or hierarchy constraint
867    pub fn principal_constraint_expr(&self) -> Expr {
868        self.0.principal_constraint_expr()
869    }
870
871    /// Get the `action` scope constraint of this policy.
872    pub fn action_constraint(&self) -> &ActionConstraint {
873        self.0.action_constraint()
874    }
875
876    /// Get the `action` scope constraint of this policy as an expression.
877    /// This will be a boolean-valued expression: either `true` (if the policy
878    /// just has `action,`), or an equality or hierarchy constraint
879    pub fn action_constraint_expr(&self) -> Expr {
880        self.0.action_constraint_expr()
881    }
882
883    /// Get the `resource` scope constraint of this policy.
884    pub fn resource_constraint(&self) -> &ResourceConstraint {
885        self.0.resource_constraint()
886    }
887
888    /// Get the `resource` scope constraint of this policy as an expression.
889    /// This will be a boolean-valued expression: either `true` (if the policy
890    /// just has `resource,`), or an equality or hierarchy constraint
891    pub fn resource_constraint_expr(&self) -> Expr {
892        self.0.resource_constraint_expr()
893    }
894
895    /// Get the non-scope constraints of this policy.
896    ///
897    /// This will be a conjunction of the policy's `when` conditions and the
898    /// negation of each of the policy's `unless` conditions.
899    pub fn non_scope_constraints(&self) -> Option<&Expr> {
900        self.0.non_scope_constraints()
901    }
902
903    /// Get the condition expression of this policy.
904    ///
905    /// This will be a conjunction of the policy's scope constraints (on
906    /// principal, resource, and action); the policy's "when" conditions; and
907    /// the negation of each of the policy's "unless" conditions.
908    pub fn condition(&self) -> Expr {
909        self.0.condition()
910    }
911
912    /// Construct a `StaticPolicy` from its components
913    #[allow(clippy::too_many_arguments)]
914    pub fn new(
915        id: PolicyID,
916        loc: Option<Loc>,
917        annotations: Annotations,
918        effect: Effect,
919        principal_constraint: PrincipalConstraint,
920        action_constraint: ActionConstraint,
921        resource_constraint: ResourceConstraint,
922        non_scope_constraints: Option<Expr>,
923    ) -> Result<Self, UnexpectedSlotError> {
924        let body = TemplateBody::new(
925            id,
926            loc,
927            annotations,
928            effect,
929            principal_constraint,
930            action_constraint,
931            resource_constraint,
932            non_scope_constraints,
933        );
934        let first_slot = body.condition().slots().next();
935        // INVARIANT (static policy correctness), checks that no slots exists
936        match first_slot {
937            Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
938            None => Ok(Self(body)),
939        }
940    }
941}
942
943impl TryFrom<Template> for StaticPolicy {
944    type Error = UnexpectedSlotError;
945
946    fn try_from(value: Template) -> Result<Self, Self::Error> {
947        // INVARIANT (Static policy correctness): Must ensure StaticPolicy contains no slots
948        let o = value.slots().next();
949        match o {
950            Some(slot_id) => Err(Self::Error::FoundSlot(slot_id.clone())),
951            None => Ok(Self(value.body)),
952        }
953    }
954}
955
956impl From<StaticPolicy> for Policy {
957    fn from(p: StaticPolicy) -> Policy {
958        let (_, policy) = Template::link_static_policy(p);
959        policy
960    }
961}
962
963impl From<StaticPolicy> for Arc<Template> {
964    fn from(p: StaticPolicy) -> Self {
965        let (t, _) = Template::link_static_policy(p);
966        t
967    }
968}
969
970/// Policy datatype. This is used for both templates (in which case it contains
971/// slots) and static policies (in which case it contains zero slots).
972#[derive(Educe, Clone, Debug)]
973#[educe(PartialEq, Eq, Hash)]
974pub struct TemplateBodyImpl {
975    /// ID of this policy
976    id: PolicyID,
977    /// Source location spanning the entire policy
978    #[educe(PartialEq(ignore))]
979    #[educe(Hash(ignore))]
980    loc: Option<Loc>,
981    /// Annotations available for external applications, as key-value store.
982    /// Note that the keys are `AnyId`, so Cedar reserved words like `if` and `has`
983    /// are explicitly allowed as annotations.
984    annotations: Arc<Annotations>,
985    /// `Effect` of this policy
986    effect: Effect,
987    /// Scope constraint for principal. This will be a boolean-valued expression:
988    /// either `true` (if the policy just has `principal,`), or an equality or
989    /// hierarchy constraint
990    principal_constraint: PrincipalConstraint,
991    /// Scope constraint for action. This will be a boolean-valued expression:
992    /// either `true` (if the policy just has `action,`), or an equality or
993    /// hierarchy constraint
994    action_constraint: ActionConstraint,
995    /// Scope constraint for resource. This will be a boolean-valued expression:
996    /// either `true` (if the policy just has `resource,`), or an equality or
997    /// hierarchy constraint
998    resource_constraint: ResourceConstraint,
999    /// Conjunction of all of the non-scope constraints in the policy.
1000    ///
1001    /// This will be a conjunction of the policy's `when` conditions and the
1002    /// negation of each of the policy's `unless` conditions.
1003    non_scope_constraints: Option<Arc<Expr>>,
1004}
1005
1006/// Policy datatype. This is used for both templates (in which case it contains
1007/// slots) and static policies (in which case it contains zero slots).
1008#[derive(Clone, Hash, Eq, PartialEq, Debug)]
1009pub enum TemplateBody {
1010    /// Represents a valid template body
1011    TemplateBody(TemplateBodyImpl),
1012    #[cfg(feature = "tolerant-ast")]
1013    /// Represents a policy that failed to parse
1014    TemplateBodyError(PolicyID, Option<Loc>),
1015}
1016
1017impl TemplateBody {
1018    /// Get the `Id` of this policy.
1019    pub fn id(&self) -> &PolicyID {
1020        match self {
1021            TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => id,
1022            #[cfg(feature = "tolerant-ast")]
1023            TemplateBody::TemplateBodyError(id, _) => id,
1024        }
1025    }
1026
1027    /// Get the location of this policy
1028    pub fn loc(&self) -> Option<&Loc> {
1029        match self {
1030            TemplateBody::TemplateBody(TemplateBodyImpl { loc, .. }) => loc.as_ref(),
1031            #[cfg(feature = "tolerant-ast")]
1032            TemplateBody::TemplateBodyError(_, loc) => loc.as_ref(),
1033        }
1034    }
1035
1036    /// Clone this policy with a new `Id`.
1037    pub fn new_id(&self, id: PolicyID) -> Self {
1038        match self {
1039            TemplateBody::TemplateBody(t) => {
1040                let mut new = t.clone();
1041                new.id = id;
1042                TemplateBody::TemplateBody(new)
1043            }
1044            #[cfg(feature = "tolerant-ast")]
1045            TemplateBody::TemplateBodyError(_, loc) => {
1046                TemplateBody::TemplateBodyError(id, loc.clone())
1047            }
1048        }
1049    }
1050
1051    #[cfg(feature = "tolerant-ast")]
1052    /// Create a template body representing a policy that failed to parse
1053    pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
1054        TemplateBody::TemplateBodyError(id, loc)
1055    }
1056
1057    /// Get the `Effect` of this policy.
1058    pub fn effect(&self) -> Effect {
1059        match self {
1060            TemplateBody::TemplateBody(TemplateBodyImpl { effect, .. }) => *effect,
1061            #[cfg(feature = "tolerant-ast")]
1062            TemplateBody::TemplateBodyError(_, _) => Effect::Forbid,
1063        }
1064    }
1065
1066    /// Get data from an annotation.
1067    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
1068        match self {
1069            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => {
1070                annotations.get(key)
1071            }
1072            #[cfg(feature = "tolerant-ast")]
1073            TemplateBody::TemplateBodyError(_, _) => None,
1074        }
1075    }
1076
1077    /// Get shared ref to annotations
1078    pub fn annotations_arc(&self) -> &Arc<Annotations> {
1079        match self {
1080            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations,
1081            #[cfg(feature = "tolerant-ast")]
1082            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ANNOTATIONS,
1083        }
1084    }
1085
1086    /// Get all annotation data.
1087    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1088        match self {
1089            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations.iter(),
1090            #[cfg(feature = "tolerant-ast")]
1091            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ANNOTATIONS.iter(),
1092        }
1093    }
1094
1095    /// Get the `principal` scope constraint of this policy.
1096    pub fn principal_constraint(&self) -> &PrincipalConstraint {
1097        match self {
1098            TemplateBody::TemplateBody(TemplateBodyImpl {
1099                principal_constraint,
1100                ..
1101            }) => principal_constraint,
1102            #[cfg(feature = "tolerant-ast")]
1103            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_PRINCIPAL_CONSTRAINT,
1104        }
1105
1106        // &self.principal_constraint
1107    }
1108
1109    /// Get the `principal` scope constraint as an expression.
1110    /// This will be a boolean-valued expression: either `true` (if the policy
1111    /// just has `principal,`), or an equality or hierarchy constraint
1112    pub fn principal_constraint_expr(&self) -> Expr {
1113        match self {
1114            TemplateBody::TemplateBody(TemplateBodyImpl {
1115                principal_constraint,
1116                ..
1117            }) => principal_constraint.as_expr(),
1118            #[cfg(feature = "tolerant-ast")]
1119            TemplateBody::TemplateBodyError(_, _) => DEFAULT_PRINCIPAL_CONSTRAINT.as_expr(),
1120        }
1121    }
1122
1123    /// Get the `action` scope constraint of this policy.
1124    pub fn action_constraint(&self) -> &ActionConstraint {
1125        match self {
1126            TemplateBody::TemplateBody(TemplateBodyImpl {
1127                action_constraint, ..
1128            }) => action_constraint,
1129            #[cfg(feature = "tolerant-ast")]
1130            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ACTION_CONSTRAINT,
1131        }
1132    }
1133
1134    /// Get the `action` scope constraint of this policy as an expression.
1135    /// This will be a boolean-valued expression: either `true` (if the policy
1136    /// just has `action,`), or an equality or hierarchy constraint
1137    pub fn action_constraint_expr(&self) -> Expr {
1138        match self {
1139            TemplateBody::TemplateBody(TemplateBodyImpl {
1140                action_constraint, ..
1141            }) => action_constraint.as_expr(),
1142            #[cfg(feature = "tolerant-ast")]
1143            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ACTION_CONSTRAINT.as_expr(),
1144        }
1145    }
1146
1147    /// Get the `resource` scope constraint of this policy.
1148    pub fn resource_constraint(&self) -> &ResourceConstraint {
1149        match self {
1150            TemplateBody::TemplateBody(TemplateBodyImpl {
1151                resource_constraint,
1152                ..
1153            }) => resource_constraint,
1154            #[cfg(feature = "tolerant-ast")]
1155            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_RESOURCE_CONSTRAINT,
1156        }
1157    }
1158
1159    /// Get the `resource` scope constraint of this policy as an expression.
1160    /// This will be a boolean-valued expression: either `true` (if the policy
1161    /// just has `resource,`), or an equality or hierarchy constraint
1162    pub fn resource_constraint_expr(&self) -> Expr {
1163        match self {
1164            TemplateBody::TemplateBody(TemplateBodyImpl {
1165                resource_constraint,
1166                ..
1167            }) => resource_constraint.as_expr(),
1168            #[cfg(feature = "tolerant-ast")]
1169            TemplateBody::TemplateBodyError(_, _) => DEFAULT_RESOURCE_CONSTRAINT.as_expr(),
1170        }
1171    }
1172
1173    /// Get the non-scope constraints of this policy.
1174    ///
1175    /// This will be a conjunction of the policy's `when` conditions and the
1176    /// negation of each of the policy's `unless` conditions.
1177    pub fn non_scope_constraints(&self) -> Option<&Expr> {
1178        match self {
1179            TemplateBody::TemplateBody(TemplateBodyImpl {
1180                non_scope_constraints,
1181                ..
1182            }) => non_scope_constraints.as_ref().map(|e| e.as_ref()),
1183            #[cfg(feature = "tolerant-ast")]
1184            TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1185        }
1186    }
1187
1188    /// Get the Arc owning the non scope constraints
1189    pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
1190        match self {
1191            TemplateBody::TemplateBody(TemplateBodyImpl {
1192                non_scope_constraints,
1193                ..
1194            }) => non_scope_constraints.as_ref(),
1195            #[cfg(feature = "tolerant-ast")]
1196            TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1197        }
1198    }
1199
1200    /// Get the condition expression of this policy.
1201    ///
1202    /// This will be a conjunction of the policy's scope constraints (on
1203    /// principal, resource, and action); the policy's "when" conditions; and
1204    /// the negation of each of the policy's "unless" conditions.
1205    pub fn condition(&self) -> Expr {
1206        match self {
1207            TemplateBody::TemplateBody(TemplateBodyImpl { .. }) => {
1208                let loc = self.loc().cloned();
1209                Expr::and(
1210                    self.principal_constraint_expr(),
1211                    Expr::and(
1212                        self.action_constraint_expr(),
1213                        Expr::and(
1214                            self.resource_constraint_expr(),
1215                            self.non_scope_constraints()
1216                                .cloned()
1217                                .unwrap_or_else(|| Expr::val(true)),
1218                        )
1219                        .with_maybe_source_loc(loc.clone()),
1220                    )
1221                    .with_maybe_source_loc(loc.clone()),
1222                )
1223                .with_maybe_source_loc(loc)
1224            }
1225            #[cfg(feature = "tolerant-ast")]
1226            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ERROR_EXPR.as_ref().clone(),
1227        }
1228    }
1229
1230    /// Construct a `Policy` from components that are already [`std::sync::Arc`] allocated
1231    #[allow(clippy::too_many_arguments)]
1232    pub fn new_shared(
1233        id: PolicyID,
1234        loc: Option<Loc>,
1235        annotations: Arc<Annotations>,
1236        effect: Effect,
1237        principal_constraint: PrincipalConstraint,
1238        action_constraint: ActionConstraint,
1239        resource_constraint: ResourceConstraint,
1240        non_scope_constraints: Option<Arc<Expr>>,
1241    ) -> Self {
1242        Self::TemplateBody(TemplateBodyImpl {
1243            id,
1244            loc,
1245            annotations,
1246            effect,
1247            principal_constraint,
1248            action_constraint,
1249            resource_constraint,
1250            non_scope_constraints,
1251        })
1252    }
1253
1254    /// Construct a `Policy` from its components
1255    #[allow(clippy::too_many_arguments)]
1256    pub fn new(
1257        id: PolicyID,
1258        loc: Option<Loc>,
1259        annotations: Annotations,
1260        effect: Effect,
1261        principal_constraint: PrincipalConstraint,
1262        action_constraint: ActionConstraint,
1263        resource_constraint: ResourceConstraint,
1264        non_scope_constraints: Option<Expr>,
1265    ) -> Self {
1266        Self::TemplateBody(TemplateBodyImpl {
1267            id,
1268            loc,
1269            annotations: Arc::new(annotations),
1270            effect,
1271            principal_constraint,
1272            action_constraint,
1273            resource_constraint,
1274            non_scope_constraints: non_scope_constraints.map(Arc::new),
1275        })
1276    }
1277}
1278
1279impl From<StaticPolicy> for TemplateBody {
1280    fn from(p: StaticPolicy) -> Self {
1281        p.0
1282    }
1283}
1284
1285impl std::fmt::Display for TemplateBody {
1286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1287        match self {
1288            TemplateBody::TemplateBody(template_body_impl) => {
1289                template_body_impl.annotations.fmt(f)?;
1290                write!(
1291                    f,
1292                    "{}(\n  {},\n  {},\n  {}\n)",
1293                    self.effect(),
1294                    self.principal_constraint(),
1295                    self.action_constraint(),
1296                    self.resource_constraint(),
1297                )?;
1298                if let Some(non_scope_constraints) = self.non_scope_constraints() {
1299                    write!(f, " when {{\n  {non_scope_constraints}\n}};")
1300                } else {
1301                    write!(f, ";")
1302                }
1303            }
1304            #[cfg(feature = "tolerant-ast")]
1305            TemplateBody::TemplateBodyError(policy_id, _) => {
1306                write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1307            }
1308        }
1309    }
1310}
1311
1312/// Template constraint on principal scope variables
1313#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1314pub struct PrincipalConstraint {
1315    pub(crate) constraint: PrincipalOrResourceConstraint,
1316}
1317
1318impl PrincipalConstraint {
1319    /// Construct a principal constraint
1320    pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1321        PrincipalConstraint { constraint }
1322    }
1323
1324    /// Get constraint as ref
1325    pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1326        &self.constraint
1327    }
1328
1329    /// Get constraint by value
1330    pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1331        self.constraint
1332    }
1333
1334    /// Get the constraint as raw AST
1335    pub fn as_expr(&self) -> Expr {
1336        self.constraint.as_expr(PrincipalOrResource::Principal)
1337    }
1338
1339    /// Unconstrained.
1340    pub fn any() -> Self {
1341        PrincipalConstraint {
1342            constraint: PrincipalOrResourceConstraint::any(),
1343        }
1344    }
1345
1346    /// Constrained to equal a specific euid.
1347    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1348        PrincipalConstraint {
1349            constraint: PrincipalOrResourceConstraint::is_eq(euid),
1350        }
1351    }
1352
1353    /// Constrained to be equal to a slot
1354    pub fn is_eq_slot() -> Self {
1355        Self {
1356            constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1357        }
1358    }
1359
1360    /// Hierarchical constraint.
1361    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1362        PrincipalConstraint {
1363            constraint: PrincipalOrResourceConstraint::is_in(euid),
1364        }
1365    }
1366
1367    /// Hierarchical constraint to Slot
1368    pub fn is_in_slot() -> Self {
1369        Self {
1370            constraint: PrincipalOrResourceConstraint::is_in_slot(),
1371        }
1372    }
1373
1374    /// Type constraint additionally constrained to be in a slot.
1375    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1376        Self {
1377            constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1378        }
1379    }
1380
1381    /// Type constraint, with a hierarchical constraint.
1382    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1383        Self {
1384            constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1385        }
1386    }
1387
1388    /// Type constraint, with no hierarchical constraint or slot.
1389    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1390        Self {
1391            constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1392        }
1393    }
1394
1395    /// Fill in the Slot, if any, with the given EUID
1396    pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1397        match self.constraint {
1398            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1399                constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1400            },
1401            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1402                constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1403            },
1404            PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1405                constraint: PrincipalOrResourceConstraint::IsIn(
1406                    entity_type,
1407                    EntityReference::EUID(euid),
1408                ),
1409            },
1410            // enumerated so it is clearer what other variants there are
1411            PrincipalOrResourceConstraint::Is(_)
1412            | PrincipalOrResourceConstraint::Any
1413            | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1414            | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1415            | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1416        }
1417    }
1418}
1419
1420impl std::fmt::Display for PrincipalConstraint {
1421    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1422        write!(
1423            f,
1424            "{}",
1425            self.constraint.display(PrincipalOrResource::Principal)
1426        )
1427    }
1428}
1429
1430/// Template constraint on resource scope variables
1431#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1432pub struct ResourceConstraint {
1433    pub(crate) constraint: PrincipalOrResourceConstraint,
1434}
1435
1436impl ResourceConstraint {
1437    /// Construct from constraint
1438    pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1439        ResourceConstraint { constraint }
1440    }
1441
1442    /// Get constraint as ref
1443    pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1444        &self.constraint
1445    }
1446
1447    /// Get constraint by value
1448    pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1449        self.constraint
1450    }
1451
1452    /// Convert into an Expression. It will be a boolean valued expression.
1453    pub fn as_expr(&self) -> Expr {
1454        self.constraint.as_expr(PrincipalOrResource::Resource)
1455    }
1456
1457    /// Unconstrained.
1458    pub fn any() -> Self {
1459        ResourceConstraint {
1460            constraint: PrincipalOrResourceConstraint::any(),
1461        }
1462    }
1463
1464    /// Constrained to equal a specific euid.
1465    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1466        ResourceConstraint {
1467            constraint: PrincipalOrResourceConstraint::is_eq(euid),
1468        }
1469    }
1470
1471    /// Constrained to equal a slot.
1472    pub fn is_eq_slot() -> Self {
1473        Self {
1474            constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1475        }
1476    }
1477
1478    /// Constrained to be in a slot
1479    pub fn is_in_slot() -> Self {
1480        Self {
1481            constraint: PrincipalOrResourceConstraint::is_in_slot(),
1482        }
1483    }
1484
1485    /// Hierarchical constraint.
1486    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1487        ResourceConstraint {
1488            constraint: PrincipalOrResourceConstraint::is_in(euid),
1489        }
1490    }
1491
1492    /// Type constraint additionally constrained to be in a slot.
1493    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1494        Self {
1495            constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1496        }
1497    }
1498
1499    /// Type constraint, with a hierarchical constraint.
1500    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1501        Self {
1502            constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1503        }
1504    }
1505
1506    /// Type constraint, with no hierarchical constraint or slot.
1507    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1508        Self {
1509            constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1510        }
1511    }
1512
1513    /// Fill in the Slot, if any, with the given EUID
1514    pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1515        match self.constraint {
1516            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1517                constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1518            },
1519            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1520                constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1521            },
1522            PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1523                constraint: PrincipalOrResourceConstraint::IsIn(
1524                    entity_type,
1525                    EntityReference::EUID(euid),
1526                ),
1527            },
1528            // enumerated so it is clearer what other variants there are
1529            PrincipalOrResourceConstraint::Is(_)
1530            | PrincipalOrResourceConstraint::Any
1531            | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1532            | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1533            | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1534        }
1535    }
1536}
1537
1538impl std::fmt::Display for ResourceConstraint {
1539    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1540        write!(
1541            f,
1542            "{}",
1543            self.as_inner().display(PrincipalOrResource::Resource)
1544        )
1545    }
1546}
1547
1548/// A reference to an EntityUID that may be a Slot
1549#[derive(Educe, Clone, Debug, Eq)]
1550#[educe(Hash, PartialEq, PartialOrd, Ord)]
1551pub enum EntityReference {
1552    /// Reference to a literal EUID
1553    EUID(Arc<EntityUID>),
1554    /// Template Slot
1555    Slot(
1556        #[educe(PartialEq(ignore))]
1557        #[educe(PartialOrd(ignore))]
1558        #[educe(Hash(ignore))]
1559        Option<Loc>,
1560    ),
1561}
1562
1563impl EntityReference {
1564    /// Create an entity reference to a specific EntityUID
1565    pub fn euid(euid: Arc<EntityUID>) -> Self {
1566        Self::EUID(euid)
1567    }
1568
1569    /// Transform into an expression AST
1570    ///
1571    /// `slot` indicates what `SlotId` would be implied by
1572    /// `EntityReference::Slot`, which is always clear from the caller's
1573    /// context.
1574    pub fn into_expr(&self, slot: SlotId) -> Expr {
1575        match self {
1576            EntityReference::EUID(euid) => Expr::val(euid.clone()),
1577            EntityReference::Slot(loc) => Expr::slot(slot).with_maybe_source_loc(loc.clone()),
1578        }
1579    }
1580}
1581
1582/// Error for unexpected slots
1583#[derive(Debug, Clone, PartialEq, Eq, Error)]
1584pub enum UnexpectedSlotError {
1585    /// Found this slot where slots are not allowed
1586    #[error("found slot `{}` where slots are not allowed", .0.id)]
1587    FoundSlot(Slot),
1588}
1589
1590impl Diagnostic for UnexpectedSlotError {
1591    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1592        match self {
1593            Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1594                let label = miette::LabeledSpan::underline(loc.span);
1595                Box::new(std::iter::once(label)) as _
1596            }),
1597        }
1598    }
1599
1600    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
1601        match self {
1602            Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|l| l as &dyn miette::SourceCode),
1603        }
1604    }
1605}
1606
1607impl From<EntityUID> for EntityReference {
1608    fn from(euid: EntityUID) -> Self {
1609        Self::EUID(Arc::new(euid))
1610    }
1611}
1612
1613/// Subset of AST variables that have the same constraint form
1614#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1615#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1616pub enum PrincipalOrResource {
1617    /// The principal of a request
1618    Principal,
1619    /// The resource of a request
1620    Resource,
1621}
1622
1623impl std::fmt::Display for PrincipalOrResource {
1624    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1625        let v = Var::from(*self);
1626        write!(f, "{v}")
1627    }
1628}
1629
1630impl TryFrom<Var> for PrincipalOrResource {
1631    type Error = Var;
1632
1633    fn try_from(value: Var) -> Result<Self, Self::Error> {
1634        match value {
1635            Var::Principal => Ok(Self::Principal),
1636            Var::Action => Err(Var::Action),
1637            Var::Resource => Ok(Self::Resource),
1638            Var::Context => Err(Var::Context),
1639        }
1640    }
1641}
1642
1643/// Represents the constraints for principals and resources.
1644/// Can either not constrain, or constrain via `==` or `in` for a single entity literal.
1645#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1646pub enum PrincipalOrResourceConstraint {
1647    /// Unconstrained
1648    Any,
1649    /// Hierarchical constraint
1650    In(EntityReference),
1651    /// Equality constraint
1652    Eq(EntityReference),
1653    /// Type constraint,
1654    Is(Arc<EntityType>),
1655    /// Type constraint with a hierarchy constraint
1656    IsIn(Arc<EntityType>, EntityReference),
1657}
1658
1659impl PrincipalOrResourceConstraint {
1660    /// Unconstrained.
1661    pub fn any() -> Self {
1662        PrincipalOrResourceConstraint::Any
1663    }
1664
1665    /// Constrained to equal a specific euid.
1666    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1667        PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1668    }
1669
1670    /// Constrained to equal a slot
1671    pub fn is_eq_slot() -> Self {
1672        PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None))
1673    }
1674
1675    /// Constrained to be in a slot
1676    pub fn is_in_slot() -> Self {
1677        PrincipalOrResourceConstraint::In(EntityReference::Slot(None))
1678    }
1679
1680    /// Hierarchical constraint.
1681    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1682        PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1683    }
1684
1685    /// Type constraint additionally constrained to be in a slot.
1686    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1687        PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None))
1688    }
1689
1690    /// Type constraint with a hierarchical constraint.
1691    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1692        PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1693    }
1694
1695    /// Type constraint, with no hierarchical constraint or slot.
1696    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1697        PrincipalOrResourceConstraint::Is(entity_type)
1698    }
1699
1700    /// Turn the constraint into an expr
1701    /// # arguments
1702    /// * `v` - The variable name to be used in the expression.
1703    pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1704        match self {
1705            PrincipalOrResourceConstraint::Any => Expr::val(true),
1706            PrincipalOrResourceConstraint::Eq(euid) => {
1707                Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1708            }
1709            PrincipalOrResourceConstraint::In(euid) => {
1710                Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1711            }
1712            PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1713                Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1714                Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1715            ),
1716            PrincipalOrResourceConstraint::Is(entity_type) => {
1717                Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1718            }
1719        }
1720    }
1721
1722    /// Pretty print the constraint
1723    /// # arguments
1724    /// * `v` - The variable name to be used in the expression.
1725    pub fn display(&self, v: PrincipalOrResource) -> String {
1726        match self {
1727            PrincipalOrResourceConstraint::In(euid) => {
1728                format!("{} in {}", v, euid.into_expr(v.into()))
1729            }
1730            PrincipalOrResourceConstraint::Eq(euid) => {
1731                format!("{} == {}", v, euid.into_expr(v.into()))
1732            }
1733            PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1734                format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1735            }
1736            PrincipalOrResourceConstraint::Is(entity_type) => {
1737                format!("{v} is {entity_type}")
1738            }
1739            PrincipalOrResourceConstraint::Any => format!("{v}"),
1740        }
1741    }
1742
1743    /// Get the entity uid in this constraint or `None` if there are no uids in the constraint
1744    pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1745        match self {
1746            PrincipalOrResourceConstraint::Any => None,
1747            PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1748            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None,
1749            PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1750            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None,
1751            PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1752            PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None,
1753            PrincipalOrResourceConstraint::Is(_) => None,
1754        }
1755    }
1756
1757    /// Get an iterator over all of the entity type names in this constraint.
1758    pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1759        self.get_euid()
1760            .into_iter()
1761            .map(|euid| euid.entity_type())
1762            .chain(match self {
1763                PrincipalOrResourceConstraint::Is(entity_type)
1764                | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1765                _ => None,
1766            })
1767    }
1768}
1769
1770/// Constraint for action scope variables.
1771/// Action variables can be constrained to be in any variable in a list.
1772#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1773pub enum ActionConstraint {
1774    /// Unconstrained
1775    Any,
1776    /// Constrained to being in a list.
1777    In(Vec<Arc<EntityUID>>),
1778    /// Constrained to equal a specific euid.
1779    Eq(Arc<EntityUID>),
1780    #[cfg(feature = "tolerant-ast")]
1781    /// Error node representing an action constraint that failed to parse
1782    ErrorConstraint,
1783}
1784
1785impl std::fmt::Display for ActionConstraint {
1786    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1787        let render_euids =
1788            |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1789        match self {
1790            ActionConstraint::Any => write!(f, "action"),
1791            ActionConstraint::In(euids) => {
1792                write!(f, "action in [{}]", render_euids(euids))
1793            }
1794            ActionConstraint::Eq(euid) => write!(f, "action == {euid}"),
1795            #[cfg(feature = "tolerant-ast")]
1796            ActionConstraint::ErrorConstraint => write!(f, "<invalid_action_constraint>"),
1797        }
1798    }
1799}
1800
1801impl ActionConstraint {
1802    /// Unconstrained action.
1803    pub fn any() -> Self {
1804        ActionConstraint::Any
1805    }
1806
1807    /// Action constrained to being in a list of euids.
1808    pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1809        ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1810    }
1811
1812    /// Action constrained to being equal to a euid.
1813    pub fn is_eq(euid: EntityUID) -> Self {
1814        ActionConstraint::Eq(Arc::new(euid))
1815    }
1816
1817    fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1818        Expr::set(euids.into_iter().map(Expr::val))
1819    }
1820
1821    /// Turn the constraint into an expression.
1822    pub fn as_expr(&self) -> Expr {
1823        match self {
1824            ActionConstraint::Any => Expr::val(true),
1825            ActionConstraint::In(euids) => Expr::is_in(
1826                Expr::var(Var::Action),
1827                ActionConstraint::euids_into_expr(euids.iter().cloned()),
1828            ),
1829            ActionConstraint::Eq(euid) => {
1830                Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1831            }
1832            #[cfg(feature = "tolerant-ast")]
1833            ActionConstraint::ErrorConstraint => Expr::new(
1834                ExprKind::Error {
1835                    error_kind: AstExprErrorKind::InvalidExpr(
1836                        "Invalid action constraint".to_string(),
1837                    ),
1838                },
1839                None,
1840                (),
1841            ),
1842        }
1843    }
1844
1845    /// Get an iterator over all of the entity uids in this constraint.
1846    pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1847        match self {
1848            ActionConstraint::Any => EntityIterator::None,
1849            ActionConstraint::In(euids) => {
1850                EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1851            }
1852            ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1853            #[cfg(feature = "tolerant-ast")]
1854            ActionConstraint::ErrorConstraint => EntityIterator::None,
1855        }
1856    }
1857
1858    /// Get an iterator over all of the entity types in this constraint.
1859    pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1860        self.iter_euids().map(|euid| euid.entity_type())
1861    }
1862
1863    /// Check that all of the EUIDs in an action constraint have the type
1864    /// `Action`, under an arbitrary namespace.
1865    pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1866        match self {
1867            ActionConstraint::Any => Ok(self),
1868            ActionConstraint::In(ref euids) => {
1869                if let Some(euids) =
1870                    NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1871                {
1872                    Err(euids)
1873                } else {
1874                    Ok(self)
1875                }
1876            }
1877            ActionConstraint::Eq(ref euid) => {
1878                if euid.is_action() {
1879                    Ok(self)
1880                } else {
1881                    Err(nonempty![euid.clone()])
1882                }
1883            }
1884            #[cfg(feature = "tolerant-ast")]
1885            ActionConstraint::ErrorConstraint => Ok(self),
1886        }
1887    }
1888}
1889
1890impl std::fmt::Display for StaticPolicy {
1891    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1892        let policy_template = &self.0;
1893        match policy_template {
1894            TemplateBody::TemplateBody(template_body_impl) => {
1895                for (k, v) in template_body_impl.annotations.iter() {
1896                    writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1897                }
1898                write!(
1899                    f,
1900                    "{}(\n  {},\n  {},\n  {}\n)",
1901                    self.effect(),
1902                    self.principal_constraint(),
1903                    self.action_constraint(),
1904                    self.resource_constraint(),
1905                )?;
1906                if let Some(non_scope_constraints) = self.non_scope_constraints() {
1907                    write!(f, " when {{\n  {non_scope_constraints}\n}};")
1908                } else {
1909                    write!(f, ";")
1910                }
1911            }
1912            #[cfg(feature = "tolerant-ast")]
1913            TemplateBody::TemplateBodyError(policy_id, _) => {
1914                write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1915            }
1916        }
1917    }
1918}
1919
1920/// A unique identifier for a policy statement
1921#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1922pub struct PolicyID(SmolStr);
1923
1924impl PolicyID {
1925    /// Create a PolicyID from a string or string-like
1926    pub fn from_string(id: impl AsRef<str>) -> Self {
1927        Self(SmolStr::from(id.as_ref()))
1928    }
1929
1930    /// Create a PolicyID from a `SmolStr`
1931    pub fn from_smolstr(id: SmolStr) -> Self {
1932        Self(id)
1933    }
1934}
1935
1936impl std::fmt::Display for PolicyID {
1937    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1938        write!(f, "{}", self.0.escape_debug())
1939    }
1940}
1941
1942impl AsRef<str> for PolicyID {
1943    fn as_ref(&self) -> &str {
1944        &self.0
1945    }
1946}
1947
1948#[cfg(feature = "arbitrary")]
1949impl arbitrary::Arbitrary<'_> for PolicyID {
1950    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1951        let s: String = u.arbitrary()?;
1952        Ok(PolicyID::from_string(s))
1953    }
1954    fn size_hint(depth: usize) -> (usize, Option<usize>) {
1955        <String as arbitrary::Arbitrary>::size_hint(depth)
1956    }
1957}
1958
1959/// the Effect of a policy
1960#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1961#[serde(rename_all = "camelCase")]
1962#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1963#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1964#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1965pub enum Effect {
1966    /// this is a Permit policy
1967    Permit,
1968    /// this is a Forbid policy
1969    Forbid,
1970}
1971
1972impl std::fmt::Display for Effect {
1973    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1974        match self {
1975            Self::Permit => write!(f, "permit"),
1976            Self::Forbid => write!(f, "forbid"),
1977        }
1978    }
1979}
1980
1981enum EntityIterator<'a> {
1982    None,
1983    One(&'a EntityUID),
1984    Bunch(Vec<&'a EntityUID>),
1985}
1986
1987impl<'a> Iterator for EntityIterator<'a> {
1988    type Item = &'a EntityUID;
1989
1990    fn next(&mut self) -> Option<Self::Item> {
1991        match self {
1992            EntityIterator::None => None,
1993            EntityIterator::One(euid) => {
1994                let eptr = *euid;
1995                let mut ptr = EntityIterator::None;
1996                std::mem::swap(self, &mut ptr);
1997                Some(eptr)
1998            }
1999            EntityIterator::Bunch(v) => v.pop(),
2000        }
2001    }
2002}
2003
2004#[cfg(test)]
2005pub(crate) mod test_generators {
2006    use super::*;
2007
2008    pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
2009        let euid = Arc::new(EntityUID::with_eid("test"));
2010        let v = vec![
2011            PrincipalOrResourceConstraint::any(),
2012            PrincipalOrResourceConstraint::is_eq(euid.clone()),
2013            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)),
2014            PrincipalOrResourceConstraint::is_in(euid),
2015            PrincipalOrResourceConstraint::In(EntityReference::Slot(None)),
2016        ];
2017
2018        v.into_iter()
2019    }
2020
2021    pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
2022        all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
2023    }
2024
2025    pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
2026        all_por_constraints().map(|constraint| ResourceConstraint { constraint })
2027    }
2028
2029    pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
2030        let euid: EntityUID = "Action::\"test\""
2031            .parse()
2032            .expect("Invalid action constraint euid");
2033        let v = vec![
2034            ActionConstraint::any(),
2035            ActionConstraint::is_eq(euid.clone()),
2036            ActionConstraint::is_in([euid.clone()]),
2037            ActionConstraint::is_in([euid.clone(), euid]),
2038        ];
2039
2040        v.into_iter()
2041    }
2042
2043    pub fn all_templates() -> impl Iterator<Item = Template> {
2044        let mut buf = vec![];
2045        let permit = PolicyID::from_string("permit");
2046        let forbid = PolicyID::from_string("forbid");
2047        for principal in all_principal_constraints() {
2048            for action in all_actions_constraints() {
2049                for resource in all_resource_constraints() {
2050                    let permit = Template::new(
2051                        permit.clone(),
2052                        None,
2053                        Annotations::new(),
2054                        Effect::Permit,
2055                        principal.clone(),
2056                        action.clone(),
2057                        resource.clone(),
2058                        None,
2059                    );
2060                    let forbid = Template::new(
2061                        forbid.clone(),
2062                        None,
2063                        Annotations::new(),
2064                        Effect::Forbid,
2065                        principal.clone(),
2066                        action.clone(),
2067                        resource.clone(),
2068                        None,
2069                    );
2070                    buf.push(permit);
2071                    buf.push(forbid);
2072                }
2073            }
2074        }
2075        buf.into_iter()
2076    }
2077}
2078
2079#[cfg(test)]
2080// PANIC SAFETY: Unit Test Code
2081#[allow(clippy::indexing_slicing)]
2082// PANIC SAFETY: Unit Test Code
2083#[allow(clippy::panic)]
2084mod test {
2085    use cool_asserts::assert_matches;
2086    use std::collections::HashSet;
2087
2088    use super::{test_generators::*, *};
2089    use crate::{
2090        parser::{
2091            parse_policy,
2092            test_utils::{expect_exactly_one_error, expect_some_error_matches},
2093        },
2094        test_utils::ExpectedErrorMessageBuilder,
2095    };
2096
2097    #[test]
2098    fn link_templates() {
2099        for template in all_templates() {
2100            template.check_invariant();
2101            let t = Arc::new(template);
2102            let env = t
2103                .slots()
2104                .map(|slot| (slot.id, EntityUID::with_eid("eid")))
2105                .collect();
2106            let _ = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
2107        }
2108    }
2109
2110    #[test]
2111    fn test_template_rebuild() {
2112        for template in all_templates() {
2113            let id = template.id().clone();
2114            let effect = template.effect();
2115            let p = template.principal_constraint().clone();
2116            let a = template.action_constraint().clone();
2117            let r = template.resource_constraint().clone();
2118            let non_scope = template.non_scope_constraints().cloned();
2119            let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
2120            assert_eq!(template, t2);
2121        }
2122    }
2123
2124    #[test]
2125    fn test_static_policy_rebuild() {
2126        for template in all_templates() {
2127            if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
2128                let id = ip.id().clone();
2129                let e = ip.effect();
2130                let anno = ip
2131                    .annotations()
2132                    .map(|(k, v)| (k.clone(), v.clone()))
2133                    .collect();
2134                let p = ip.principal_constraint().clone();
2135                let a = ip.action_constraint().clone();
2136                let r = ip.resource_constraint().clone();
2137                let non_scope = ip.non_scope_constraints().clone();
2138                let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope.cloned())
2139                    .expect("Policy Creation Failed");
2140                assert_eq!(ip, ip2);
2141                let (t2, inst) = Template::link_static_policy(ip2);
2142                assert!(inst.is_static());
2143                assert_eq!(&template, t2.as_ref());
2144            }
2145        }
2146    }
2147
2148    #[test]
2149    fn ir_binding_too_many() {
2150        let tid = PolicyID::from_string("tid");
2151        let iid = PolicyID::from_string("iid");
2152        let t = Arc::new(Template::new(
2153            tid,
2154            None,
2155            Annotations::new(),
2156            Effect::Forbid,
2157            PrincipalConstraint::is_eq_slot(),
2158            ActionConstraint::Any,
2159            ResourceConstraint::any(),
2160            None,
2161        ));
2162        let mut m = HashMap::new();
2163        m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
2164        assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2165            assert_eq!(unbound_values, vec![SlotId::principal()]);
2166            assert_eq!(extra_values, vec![SlotId::resource()]);
2167        });
2168    }
2169
2170    #[test]
2171    fn ir_binding_too_few() {
2172        let tid = PolicyID::from_string("tid");
2173        let iid = PolicyID::from_string("iid");
2174        let t = Arc::new(Template::new(
2175            tid,
2176            None,
2177            Annotations::new(),
2178            Effect::Forbid,
2179            PrincipalConstraint::is_eq_slot(),
2180            ActionConstraint::Any,
2181            ResourceConstraint::is_in_slot(),
2182            None,
2183        ));
2184        assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2185            assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
2186            assert_eq!(extra_values, vec![]);
2187        });
2188        let mut m = HashMap::new();
2189        m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
2190        assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2191            assert_eq!(unbound_values, vec![SlotId::resource()]);
2192            assert_eq!(extra_values, vec![]);
2193        });
2194    }
2195
2196    #[test]
2197    fn ir_binding() {
2198        let tid = PolicyID::from_string("template");
2199        let iid = PolicyID::from_string("linked");
2200        let t = Arc::new(Template::new(
2201            tid,
2202            None,
2203            Annotations::new(),
2204            Effect::Permit,
2205            PrincipalConstraint::is_in_slot(),
2206            ActionConstraint::any(),
2207            ResourceConstraint::is_eq_slot(),
2208            None,
2209        ));
2210
2211        let mut m = HashMap::new();
2212        m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2213        m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2214
2215        let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2216        assert_eq!(r.id(), &iid);
2217        assert_eq!(
2218            r.env().get(&SlotId::principal()),
2219            Some(&EntityUID::with_eid("theprincipal"))
2220        );
2221        assert_eq!(
2222            r.env().get(&SlotId::resource()),
2223            Some(&EntityUID::with_eid("theresource"))
2224        );
2225    }
2226
2227    #[test]
2228    fn isnt_template_implies_from_succeeds() {
2229        for template in all_templates() {
2230            if template.slots().count() == 0 {
2231                StaticPolicy::try_from(template).expect("Should succeed");
2232            }
2233        }
2234    }
2235
2236    #[test]
2237    fn is_template_implies_from_fails() {
2238        for template in all_templates() {
2239            if template.slots().count() != 0 {
2240                assert!(
2241                    StaticPolicy::try_from(template.clone()).is_err(),
2242                    "Following template did convert {template}"
2243                );
2244            }
2245        }
2246    }
2247
2248    #[test]
2249    fn non_template_iso() {
2250        for template in all_templates() {
2251            if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2252                let (t2, _) = Template::link_static_policy(p);
2253                assert_eq!(&template, t2.as_ref());
2254            }
2255        }
2256    }
2257
2258    #[test]
2259    fn template_into_expr() {
2260        for template in all_templates() {
2261            if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2262                let t: Template = template;
2263                assert_eq!(p.condition(), t.condition());
2264                assert_eq!(p.effect(), t.effect());
2265            }
2266        }
2267    }
2268
2269    #[test]
2270    fn template_por_iter() {
2271        let e = Arc::new(EntityUID::with_eid("eid"));
2272        assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2273        assert_eq!(
2274            PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2275            Some(&e)
2276        );
2277        assert_eq!(
2278            PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(),
2279            None
2280        );
2281        assert_eq!(
2282            PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2283            Some(&e)
2284        );
2285        assert_eq!(
2286            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(),
2287            None
2288        );
2289        assert_eq!(
2290            PrincipalOrResourceConstraint::IsIn(
2291                Arc::new("T".parse().unwrap()),
2292                EntityReference::EUID(e.clone())
2293            )
2294            .get_euid(),
2295            Some(&e)
2296        );
2297        assert_eq!(
2298            PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2299            None
2300        );
2301        assert_eq!(
2302            PrincipalOrResourceConstraint::IsIn(
2303                Arc::new("T".parse().unwrap()),
2304                EntityReference::Slot(None)
2305            )
2306            .get_euid(),
2307            None
2308        );
2309    }
2310
2311    #[test]
2312    fn action_iter() {
2313        assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2314        let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2315        let v = a.iter_euids().collect::<Vec<_>>();
2316        assert_eq!(vec![&EntityUID::with_eid("test")], v);
2317        let a =
2318            ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2319        let set = a.iter_euids().collect::<HashSet<_>>();
2320        let e1 = EntityUID::with_eid("test1");
2321        let e2 = EntityUID::with_eid("test2");
2322        let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2323        assert_eq!(set, correct);
2324    }
2325
2326    #[test]
2327    fn test_iter_none() {
2328        let mut i = EntityIterator::None;
2329        assert_eq!(i.next(), None);
2330    }
2331
2332    #[test]
2333    fn test_iter_once() {
2334        let id = EntityUID::from_components(
2335            name::Name::parse_unqualified_name("s").unwrap().into(),
2336            entity::Eid::new("eid"),
2337            None,
2338        );
2339        let mut i = EntityIterator::One(&id);
2340        assert_eq!(i.next(), Some(&id));
2341        assert_eq!(i.next(), None);
2342    }
2343
2344    #[test]
2345    fn test_iter_mult() {
2346        let id1 = EntityUID::from_components(
2347            name::Name::parse_unqualified_name("s").unwrap().into(),
2348            entity::Eid::new("eid1"),
2349            None,
2350        );
2351        let id2 = EntityUID::from_components(
2352            name::Name::parse_unqualified_name("s").unwrap().into(),
2353            entity::Eid::new("eid2"),
2354            None,
2355        );
2356        let v = vec![&id1, &id2];
2357        let mut i = EntityIterator::Bunch(v);
2358        assert_eq!(i.next(), Some(&id2));
2359        assert_eq!(i.next(), Some(&id1));
2360        assert_eq!(i.next(), None)
2361    }
2362
2363    #[test]
2364    fn euid_into_expr() {
2365        let e = EntityReference::Slot(None);
2366        assert_eq!(
2367            e.into_expr(SlotId::principal()),
2368            Expr::slot(SlotId::principal())
2369        );
2370        let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2371        assert_eq!(
2372            e.into_expr(SlotId::principal()),
2373            Expr::val(EntityUID::with_eid("eid"))
2374        );
2375    }
2376
2377    #[test]
2378    fn por_constraint_display() {
2379        let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None));
2380        let s = t.display(PrincipalOrResource::Principal);
2381        assert_eq!(s, "principal == ?principal");
2382        let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2383            EntityUID::with_eid("test"),
2384        )));
2385        let s = t.display(PrincipalOrResource::Principal);
2386        assert_eq!(s, "principal == test_entity_type::\"test\"");
2387    }
2388
2389    #[test]
2390    fn unexpected_templates() {
2391        let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2392        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2393            expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2394                "expected a static policy, got a template containing the slot ?principal"
2395                )
2396                .help("try removing the template slot(s) from this policy")
2397                .exactly_one_underline("?principal")
2398                .build()
2399            );
2400        });
2401
2402        let policy_str =
2403            r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2404        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2405            expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2406                "expected a static policy, got a template containing the slot ?principal"
2407                )
2408                .help("try removing the template slot(s) from this policy")
2409                .exactly_one_underline("?principal")
2410                .build()
2411            );
2412            assert_eq!(e.len(), 2);
2413        });
2414    }
2415
2416    #[test]
2417    fn policy_to_expr() {
2418        let policy_str = r#"permit(principal is A, action, resource is B)
2419            when { 1 == 2 }
2420            unless { 2 == 1}
2421            when { 3 == 4}
2422            unless { 4 == 3};"#;
2423        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Ok(p) => {
2424            assert_eq!(ToString::to_string(&p.condition()), "(principal is A) && (true && ((resource is B) && ((1 == 2) && ((!(2 == 1)) && ((3 == 4) && (!(4 == 3)))))))");
2425        });
2426    }
2427
2428    #[cfg(feature = "tolerant-ast")]
2429    #[test]
2430    fn template_body_error_methods() {
2431        use std::str::FromStr;
2432
2433        let policy_id = PolicyID::from_string("error_policy");
2434        let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2435        let error_body = TemplateBody::TemplateBodyError(policy_id.clone(), error_loc.clone());
2436
2437        let expected_error = <ExprWithErrsBuilder as ExprBuilder>::new()
2438            .error(ParseErrors::singleton(ToASTError::new(
2439                ToASTErrorKind::ASTErrorNode,
2440                Some(Loc::new(0..1, "ASTErrorNode".into())),
2441            )))
2442            .unwrap();
2443
2444        // Test id() method
2445        assert_eq!(error_body.id(), &policy_id);
2446
2447        // Test loc() method
2448        assert_eq!(error_body.loc(), error_loc.as_ref());
2449
2450        // Test new_id() method
2451        let new_policy_id = PolicyID::from_string("new_error_policy");
2452        let updated_error_body = error_body.new_id(new_policy_id.clone());
2453        assert_matches!(updated_error_body,
2454            TemplateBody::TemplateBodyError(id, loc) if id == new_policy_id && loc == error_loc
2455        );
2456
2457        // Test effect() method
2458        assert_eq!(error_body.effect(), Effect::Forbid);
2459
2460        // Test annotation() method
2461        assert_eq!(
2462            error_body.annotation(&AnyId::from_str("test").unwrap()),
2463            None
2464        );
2465
2466        // Test annotations() method
2467        assert!(error_body.annotations().count() == 0);
2468
2469        // Test principal_constraint() method
2470        assert_eq!(
2471            *error_body.principal_constraint(),
2472            PrincipalConstraint::any()
2473        );
2474
2475        // Test action_constraint() method
2476        assert_eq!(*error_body.action_constraint(), ActionConstraint::any());
2477
2478        // Test resource_constraint() method
2479        assert_eq!(*error_body.resource_constraint(), ResourceConstraint::any());
2480
2481        // Test non_scope_constraints() method
2482        assert_eq!(error_body.non_scope_constraints(), Some(&expected_error));
2483
2484        // Test condition() method
2485        assert_eq!(error_body.condition(), expected_error);
2486
2487        // Test Display implementation
2488        let display_str = format!("{error_body}");
2489        assert!(display_str.contains("TemplateBodyError"));
2490        assert!(display_str.contains("error_policy"));
2491    }
2492
2493    #[cfg(feature = "tolerant-ast")]
2494    #[test]
2495    fn template_error_methods() {
2496        let policy_id = PolicyID::from_string("error_policy");
2497        let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2498        let error_template = Template::error(policy_id.clone(), error_loc.clone());
2499
2500        // Check template properties
2501        assert_eq!(error_template.id(), &policy_id);
2502
2503        // Check slots are empty
2504        assert!(error_template.slots().count() == 0);
2505
2506        // Check body is an error template body
2507        assert_matches!(error_template.body,
2508            TemplateBody::TemplateBodyError(ref id, ref loc) if id == &policy_id && loc == &error_loc
2509        );
2510
2511        // Test principal_constraint() method
2512        assert_eq!(
2513            error_template.principal_constraint(),
2514            &PrincipalConstraint::any()
2515        );
2516
2517        // Test action_constraint() method
2518        assert_eq!(*error_template.action_constraint(), ActionConstraint::any());
2519
2520        // Test resource_constraint() method
2521        assert_eq!(
2522            *error_template.resource_constraint(),
2523            ResourceConstraint::any()
2524        );
2525
2526        // Verify effect is Forbid
2527        assert_eq!(error_template.effect(), Effect::Forbid);
2528
2529        // Verify condition is the default error expression
2530        assert_eq!(
2531            error_template.condition(),
2532            DEFAULT_ERROR_EXPR.as_ref().clone()
2533        );
2534
2535        // Verify location is None
2536        assert_eq!(error_template.loc(), error_loc.as_ref());
2537
2538        // Verify annotations are default
2539        assert!(error_template.annotations().count() == 0);
2540
2541        // Verify display implementation
2542        let display_str = format!("{error_template}");
2543        assert!(display_str.contains("TemplateBody::TemplateBodyError"));
2544        assert!(display_str.contains(&policy_id.to_string()));
2545    }
2546}