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    /// Get the location of this policy
581    pub fn loc(&self) -> Option<&Loc> {
582        self.template.loc()
583    }
584
585    /// Returns true if this policy is an inline policy
586    pub fn is_static(&self) -> bool {
587        self.link.is_none()
588    }
589
590    /// Returns all the unknown entities in the policy during evaluation
591    pub fn unknown_entities(&self) -> HashSet<EntityUID> {
592        self.condition()
593            .unknowns()
594            .filter_map(
595                |Unknown {
596                     name,
597                     type_annotation,
598                 }| {
599                    if matches!(type_annotation, Some(Type::Entity { .. })) {
600                        EntityUID::from_str(name.as_str()).ok()
601                    } else {
602                        None
603                    }
604                },
605            )
606            .collect()
607    }
608}
609
610impl std::fmt::Display for Policy {
611    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
612        if self.is_static() {
613            write!(f, "{}", self.template())
614        } else {
615            write!(
616                f,
617                "Template Instance of {}, slots: [{}]",
618                self.template().id(),
619                display_slot_env(self.env())
620            )
621        }
622    }
623}
624
625/// Map from Slot Ids to Entity UIDs which fill the slots
626pub type SlotEnv = HashMap<SlotId, EntityUID>;
627
628/// Represents either a static policy or a template linked policy.
629///
630/// Contains less rich information than `Policy`. In particular, this form is
631/// easier to convert to/from the Protobuf representation of a `Policy`, because
632/// it simply refers to the `Template` by its Id and does not contain a
633/// reference to the `Template` itself.
634#[derive(Debug, Clone, PartialEq, Eq)]
635pub struct LiteralPolicy {
636    /// ID of the template this policy is an instance of
637    template_id: PolicyID,
638    /// ID of this link.
639    /// This is `None` for static policies, and the static policy ID is defined
640    /// as the `template_id`
641    link_id: Option<PolicyID>,
642    /// Values of the slots
643    values: SlotEnv,
644}
645
646impl LiteralPolicy {
647    /// Create a `LiteralPolicy` representing a static policy with the given ID.
648    ///
649    /// The policy set should also contain a (zero-slot) `Template` with the given ID.
650    pub fn static_policy(template_id: PolicyID) -> Self {
651        Self {
652            template_id,
653            link_id: None,
654            values: SlotEnv::new(),
655        }
656    }
657
658    /// Create a `LiteralPolicy` representing a template-linked policy.
659    ///
660    /// The policy set should also contain the associated `Template`.
661    pub fn template_linked_policy(
662        template_id: PolicyID,
663        link_id: PolicyID,
664        values: SlotEnv,
665    ) -> Self {
666        Self {
667            template_id,
668            link_id: Some(link_id),
669            values,
670        }
671    }
672
673    /// Get the `EntityUID` associated with the given `SlotId`, if it exists
674    pub fn value(&self, slot: &SlotId) -> Option<&EntityUID> {
675        self.values.get(slot)
676    }
677}
678
679// Can we verify the hash property?
680
681impl std::hash::Hash for LiteralPolicy {
682    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
683        self.template_id.hash(state);
684        // this shouldn't be a performance issue as these vectors should be small
685        let mut buf = self.values.iter().collect::<Vec<_>>();
686        buf.sort();
687        for (id, euid) in buf {
688            id.hash(state);
689            euid.hash(state);
690        }
691    }
692}
693
694// These would be great as property tests
695#[cfg(test)]
696mod hashing_tests {
697    use std::{
698        collections::hash_map::DefaultHasher,
699        hash::{Hash, Hasher},
700    };
701
702    use super::*;
703
704    fn compute_hash(ir: &LiteralPolicy) -> u64 {
705        let mut s = DefaultHasher::new();
706        ir.hash(&mut s);
707        s.finish()
708    }
709
710    fn build_template_linked_policy() -> LiteralPolicy {
711        let mut map = HashMap::new();
712        map.insert(SlotId::principal(), EntityUID::with_eid("eid"));
713        LiteralPolicy {
714            template_id: PolicyID::from_string("template"),
715            link_id: Some(PolicyID::from_string("id")),
716            values: map,
717        }
718    }
719
720    #[test]
721    fn hash_property_instances() {
722        let a = build_template_linked_policy();
723        let b = build_template_linked_policy();
724        assert_eq!(a, b);
725        assert_eq!(compute_hash(&a), compute_hash(&b));
726    }
727}
728
729/// Errors that can happen during policy reification
730#[derive(Debug, Diagnostic, Error)]
731pub enum ReificationError {
732    /// The [`PolicyID`] linked to did not exist
733    #[error("the id linked to does not exist")]
734    NoSuchTemplate(PolicyID),
735    /// Error linking the policy
736    #[error(transparent)]
737    #[diagnostic(transparent)]
738    Linking(#[from] LinkingError),
739}
740
741impl LiteralPolicy {
742    /// Attempt to reify this template linked policy.
743    /// Ensures the linked template actually exists, replaces the id with a reference to the underlying template.
744    /// Fails if the template does not exist.
745    /// Consumes the policy.
746    pub fn reify(
747        self,
748        templates: &LinkedHashMap<PolicyID, Arc<Template>>,
749    ) -> Result<Policy, ReificationError> {
750        let template = templates
751            .get(&self.template_id)
752            .ok_or_else(|| ReificationError::NoSuchTemplate(self.template_id().clone()))?;
753        // INVARIANT (values total map)
754        Template::check_binding(template, &self.values).map_err(ReificationError::Linking)?;
755        Ok(Policy::new(template.clone(), self.link_id, self.values))
756    }
757
758    /// Lookup the euid bound by a SlotId
759    pub fn get(&self, id: &SlotId) -> Option<&EntityUID> {
760        self.values.get(id)
761    }
762
763    /// Get the [`PolicyID`] of this static or template-linked policy.
764    pub fn id(&self) -> &PolicyID {
765        self.link_id.as_ref().unwrap_or(&self.template_id)
766    }
767
768    /// Get the [`PolicyID`] of the template associated with this policy.
769    ///
770    /// For static policies, this is just the static policy ID.
771    pub fn template_id(&self) -> &PolicyID {
772        &self.template_id
773    }
774
775    /// Is this a static policy
776    pub fn is_static(&self) -> bool {
777        self.link_id.is_none()
778    }
779}
780
781fn display_slot_env(env: &SlotEnv) -> String {
782    env.iter()
783        .map(|(slot, value)| format!("{slot} -> {value}"))
784        .join(",")
785}
786
787impl std::fmt::Display for LiteralPolicy {
788    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
789        if self.is_static() {
790            write!(f, "Static policy w/ ID {}", self.template_id())
791        } else {
792            write!(
793                f,
794                "Template linked policy of {}, slots: [{}]",
795                self.template_id(),
796                display_slot_env(&self.values),
797            )
798        }
799    }
800}
801
802impl From<Policy> for LiteralPolicy {
803    fn from(p: Policy) -> Self {
804        Self {
805            template_id: p.template.id().clone(),
806            link_id: p.link,
807            values: p.values,
808        }
809    }
810}
811
812/// Static Policies are policy that do not come from templates.
813/// They have the same structure as a template definition, but cannot contain slots
814// INVARIANT: (Static Policy Correctness): A Static Policy TemplateBody must have zero slots
815#[derive(Clone, Hash, Eq, PartialEq, Debug)]
816pub struct StaticPolicy(TemplateBody);
817
818impl StaticPolicy {
819    /// Get the `Id` of this policy.
820    pub fn id(&self) -> &PolicyID {
821        self.0.id()
822    }
823
824    /// Clone this policy with a new `Id`.
825    pub fn new_id(&self, id: PolicyID) -> Self {
826        StaticPolicy(self.0.new_id(id))
827    }
828
829    /// Get the location of this policy
830    pub fn loc(&self) -> Option<&Loc> {
831        self.0.loc()
832    }
833
834    /// Get the `Effect` of this policy.
835    pub fn effect(&self) -> Effect {
836        self.0.effect()
837    }
838
839    /// Get data from an annotation.
840    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
841        self.0.annotation(key)
842    }
843
844    /// Get all annotation data.
845    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
846        self.0.annotations()
847    }
848
849    /// Get the `principal` scope constraint of this policy.
850    pub fn principal_constraint(&self) -> &PrincipalConstraint {
851        self.0.principal_constraint()
852    }
853
854    /// Get the `principal` scope constraint as an expression.
855    /// This will be a boolean-valued expression: either `true` (if the policy
856    /// just has `principal,`), or an equality or hierarchy constraint
857    pub fn principal_constraint_expr(&self) -> Expr {
858        self.0.principal_constraint_expr()
859    }
860
861    /// Get the `action` scope constraint of this policy.
862    pub fn action_constraint(&self) -> &ActionConstraint {
863        self.0.action_constraint()
864    }
865
866    /// Get the `action` scope constraint of this policy as an expression.
867    /// This will be a boolean-valued expression: either `true` (if the policy
868    /// just has `action,`), or an equality or hierarchy constraint
869    pub fn action_constraint_expr(&self) -> Expr {
870        self.0.action_constraint_expr()
871    }
872
873    /// Get the `resource` scope constraint of this policy.
874    pub fn resource_constraint(&self) -> &ResourceConstraint {
875        self.0.resource_constraint()
876    }
877
878    /// Get the `resource` scope constraint of this policy as an expression.
879    /// This will be a boolean-valued expression: either `true` (if the policy
880    /// just has `resource,`), or an equality or hierarchy constraint
881    pub fn resource_constraint_expr(&self) -> Expr {
882        self.0.resource_constraint_expr()
883    }
884
885    /// Get the non-scope constraints of this policy.
886    ///
887    /// This will be a conjunction of the policy's `when` conditions and the
888    /// negation of each of the policy's `unless` conditions.
889    pub fn non_scope_constraints(&self) -> Option<&Expr> {
890        self.0.non_scope_constraints()
891    }
892
893    /// Get the condition expression of this policy.
894    ///
895    /// This will be a conjunction of the policy's scope constraints (on
896    /// principal, resource, and action); the policy's "when" conditions; and
897    /// the negation of each of the policy's "unless" conditions.
898    pub fn condition(&self) -> Expr {
899        self.0.condition()
900    }
901
902    /// Construct a `StaticPolicy` from its components
903    #[allow(clippy::too_many_arguments)]
904    pub fn new(
905        id: PolicyID,
906        loc: Option<Loc>,
907        annotations: Annotations,
908        effect: Effect,
909        principal_constraint: PrincipalConstraint,
910        action_constraint: ActionConstraint,
911        resource_constraint: ResourceConstraint,
912        non_scope_constraints: Option<Expr>,
913    ) -> Result<Self, UnexpectedSlotError> {
914        let body = TemplateBody::new(
915            id,
916            loc,
917            annotations,
918            effect,
919            principal_constraint,
920            action_constraint,
921            resource_constraint,
922            non_scope_constraints,
923        );
924        let first_slot = body.condition().slots().next();
925        // INVARIANT (static policy correctness), checks that no slots exists
926        match first_slot {
927            Some(slot) => Err(UnexpectedSlotError::FoundSlot(slot))?,
928            None => Ok(Self(body)),
929        }
930    }
931}
932
933impl TryFrom<Template> for StaticPolicy {
934    type Error = UnexpectedSlotError;
935
936    fn try_from(value: Template) -> Result<Self, Self::Error> {
937        // INVARIANT (Static policy correctness): Must ensure StaticPolicy contains no slots
938        let o = value.slots().next();
939        match o {
940            Some(slot_id) => Err(Self::Error::FoundSlot(slot_id.clone())),
941            None => Ok(Self(value.body)),
942        }
943    }
944}
945
946impl From<StaticPolicy> for Policy {
947    fn from(p: StaticPolicy) -> Policy {
948        let (_, policy) = Template::link_static_policy(p);
949        policy
950    }
951}
952
953impl From<StaticPolicy> for Arc<Template> {
954    fn from(p: StaticPolicy) -> Self {
955        let (t, _) = Template::link_static_policy(p);
956        t
957    }
958}
959
960/// Policy datatype. This is used for both templates (in which case it contains
961/// slots) and static policies (in which case it contains zero slots).
962#[derive(Educe, Clone, Debug)]
963#[educe(PartialEq, Eq, Hash)]
964pub struct TemplateBodyImpl {
965    /// ID of this policy
966    id: PolicyID,
967    /// Source location spanning the entire policy
968    #[educe(PartialEq(ignore))]
969    #[educe(Hash(ignore))]
970    loc: Option<Loc>,
971    /// Annotations available for external applications, as key-value store.
972    /// Note that the keys are `AnyId`, so Cedar reserved words like `if` and `has`
973    /// are explicitly allowed as annotations.
974    annotations: Arc<Annotations>,
975    /// `Effect` of this policy
976    effect: Effect,
977    /// Scope constraint for principal. This will be a boolean-valued expression:
978    /// either `true` (if the policy just has `principal,`), or an equality or
979    /// hierarchy constraint
980    principal_constraint: PrincipalConstraint,
981    /// Scope constraint for action. This will be a boolean-valued expression:
982    /// either `true` (if the policy just has `action,`), or an equality or
983    /// hierarchy constraint
984    action_constraint: ActionConstraint,
985    /// Scope constraint for resource. This will be a boolean-valued expression:
986    /// either `true` (if the policy just has `resource,`), or an equality or
987    /// hierarchy constraint
988    resource_constraint: ResourceConstraint,
989    /// Conjunction of all of the non-scope constraints in the policy.
990    ///
991    /// This will be a conjunction of the policy's `when` conditions and the
992    /// negation of each of the policy's `unless` conditions.
993    non_scope_constraints: Option<Arc<Expr>>,
994}
995
996/// Policy datatype. This is used for both templates (in which case it contains
997/// slots) and static policies (in which case it contains zero slots).
998#[derive(Clone, Hash, Eq, PartialEq, Debug)]
999pub enum TemplateBody {
1000    /// Represents a valid template body
1001    TemplateBody(TemplateBodyImpl),
1002    #[cfg(feature = "tolerant-ast")]
1003    /// Represents a policy that failed to parse
1004    TemplateBodyError(PolicyID, Option<Loc>),
1005}
1006
1007impl TemplateBody {
1008    /// Get the `Id` of this policy.
1009    pub fn id(&self) -> &PolicyID {
1010        match self {
1011            TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => id,
1012            #[cfg(feature = "tolerant-ast")]
1013            TemplateBody::TemplateBodyError(id, _) => id,
1014        }
1015    }
1016
1017    /// Get the location of this policy
1018    pub fn loc(&self) -> Option<&Loc> {
1019        match self {
1020            TemplateBody::TemplateBody(TemplateBodyImpl { loc, .. }) => loc.as_ref(),
1021            #[cfg(feature = "tolerant-ast")]
1022            TemplateBody::TemplateBodyError(_, loc) => loc.as_ref(),
1023        }
1024    }
1025
1026    /// Clone this policy with a new `Id`.
1027    pub fn new_id(&self, id: PolicyID) -> Self {
1028        match self {
1029            TemplateBody::TemplateBody(t) => {
1030                let mut new = t.clone();
1031                new.id = id;
1032                TemplateBody::TemplateBody(new)
1033            }
1034            #[cfg(feature = "tolerant-ast")]
1035            TemplateBody::TemplateBodyError(_, loc) => {
1036                TemplateBody::TemplateBodyError(id, loc.clone())
1037            }
1038        }
1039    }
1040
1041    #[cfg(feature = "tolerant-ast")]
1042    /// Create a template body representing a policy that failed to parse
1043    pub fn error(id: PolicyID, loc: Option<Loc>) -> Self {
1044        TemplateBody::TemplateBodyError(id, loc)
1045    }
1046
1047    /// Get the `Effect` of this policy.
1048    pub fn effect(&self) -> Effect {
1049        match self {
1050            TemplateBody::TemplateBody(TemplateBodyImpl { effect, .. }) => *effect,
1051            #[cfg(feature = "tolerant-ast")]
1052            TemplateBody::TemplateBodyError(_, _) => Effect::Forbid,
1053        }
1054    }
1055
1056    /// Get data from an annotation.
1057    pub fn annotation(&self, key: &AnyId) -> Option<&Annotation> {
1058        match self {
1059            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => {
1060                annotations.get(key)
1061            }
1062            #[cfg(feature = "tolerant-ast")]
1063            TemplateBody::TemplateBodyError(_, _) => None,
1064        }
1065    }
1066
1067    /// Get shared ref to annotations
1068    pub fn annotations_arc(&self) -> &Arc<Annotations> {
1069        match self {
1070            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations,
1071            #[cfg(feature = "tolerant-ast")]
1072            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ANNOTATIONS,
1073        }
1074    }
1075
1076    /// Get all annotation data.
1077    pub fn annotations(&self) -> impl Iterator<Item = (&AnyId, &Annotation)> {
1078        match self {
1079            TemplateBody::TemplateBody(TemplateBodyImpl { annotations, .. }) => annotations.iter(),
1080            #[cfg(feature = "tolerant-ast")]
1081            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ANNOTATIONS.iter(),
1082        }
1083    }
1084
1085    /// Get the `principal` scope constraint of this policy.
1086    pub fn principal_constraint(&self) -> &PrincipalConstraint {
1087        match self {
1088            TemplateBody::TemplateBody(TemplateBodyImpl {
1089                principal_constraint,
1090                ..
1091            }) => principal_constraint,
1092            #[cfg(feature = "tolerant-ast")]
1093            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_PRINCIPAL_CONSTRAINT,
1094        }
1095
1096        // &self.principal_constraint
1097    }
1098
1099    /// Get the `principal` scope constraint as an expression.
1100    /// This will be a boolean-valued expression: either `true` (if the policy
1101    /// just has `principal,`), or an equality or hierarchy constraint
1102    pub fn principal_constraint_expr(&self) -> Expr {
1103        match self {
1104            TemplateBody::TemplateBody(TemplateBodyImpl {
1105                principal_constraint,
1106                ..
1107            }) => principal_constraint.as_expr(),
1108            #[cfg(feature = "tolerant-ast")]
1109            TemplateBody::TemplateBodyError(_, _) => DEFAULT_PRINCIPAL_CONSTRAINT.as_expr(),
1110        }
1111    }
1112
1113    /// Get the `action` scope constraint of this policy.
1114    pub fn action_constraint(&self) -> &ActionConstraint {
1115        match self {
1116            TemplateBody::TemplateBody(TemplateBodyImpl {
1117                action_constraint, ..
1118            }) => action_constraint,
1119            #[cfg(feature = "tolerant-ast")]
1120            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ACTION_CONSTRAINT,
1121        }
1122    }
1123
1124    /// Get the `action` scope constraint of this policy as an expression.
1125    /// This will be a boolean-valued expression: either `true` (if the policy
1126    /// just has `action,`), or an equality or hierarchy constraint
1127    pub fn action_constraint_expr(&self) -> Expr {
1128        match self {
1129            TemplateBody::TemplateBody(TemplateBodyImpl {
1130                action_constraint, ..
1131            }) => action_constraint.as_expr(),
1132            #[cfg(feature = "tolerant-ast")]
1133            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ACTION_CONSTRAINT.as_expr(),
1134        }
1135    }
1136
1137    /// Get the `resource` scope constraint of this policy.
1138    pub fn resource_constraint(&self) -> &ResourceConstraint {
1139        match self {
1140            TemplateBody::TemplateBody(TemplateBodyImpl {
1141                resource_constraint,
1142                ..
1143            }) => resource_constraint,
1144            #[cfg(feature = "tolerant-ast")]
1145            TemplateBody::TemplateBodyError(_, _) => &DEFAULT_RESOURCE_CONSTRAINT,
1146        }
1147    }
1148
1149    /// Get the `resource` scope constraint of this policy as an expression.
1150    /// This will be a boolean-valued expression: either `true` (if the policy
1151    /// just has `resource,`), or an equality or hierarchy constraint
1152    pub fn resource_constraint_expr(&self) -> Expr {
1153        match self {
1154            TemplateBody::TemplateBody(TemplateBodyImpl {
1155                resource_constraint,
1156                ..
1157            }) => resource_constraint.as_expr(),
1158            #[cfg(feature = "tolerant-ast")]
1159            TemplateBody::TemplateBodyError(_, _) => DEFAULT_RESOURCE_CONSTRAINT.as_expr(),
1160        }
1161    }
1162
1163    /// Get the non-scope constraints of this policy.
1164    ///
1165    /// This will be a conjunction of the policy's `when` conditions and the
1166    /// negation of each of the policy's `unless` conditions.
1167    pub fn non_scope_constraints(&self) -> Option<&Expr> {
1168        match self {
1169            TemplateBody::TemplateBody(TemplateBodyImpl {
1170                non_scope_constraints,
1171                ..
1172            }) => non_scope_constraints.as_ref().map(|e| e.as_ref()),
1173            #[cfg(feature = "tolerant-ast")]
1174            TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1175        }
1176    }
1177
1178    /// Get the Arc owning the non scope constraints
1179    pub fn non_scope_constraints_arc(&self) -> Option<&Arc<Expr>> {
1180        match self {
1181            TemplateBody::TemplateBody(TemplateBodyImpl {
1182                non_scope_constraints,
1183                ..
1184            }) => non_scope_constraints.as_ref(),
1185            #[cfg(feature = "tolerant-ast")]
1186            TemplateBody::TemplateBodyError(_, _) => Some(&DEFAULT_ERROR_EXPR),
1187        }
1188    }
1189
1190    /// Get the condition expression of this policy.
1191    ///
1192    /// This will be a conjunction of the policy's scope constraints (on
1193    /// principal, resource, and action); the policy's "when" conditions; and
1194    /// the negation of each of the policy's "unless" conditions.
1195    pub fn condition(&self) -> Expr {
1196        match self {
1197            TemplateBody::TemplateBody(TemplateBodyImpl { .. }) => {
1198                let loc = self.loc().cloned();
1199                Expr::and(
1200                    self.principal_constraint_expr(),
1201                    Expr::and(
1202                        self.action_constraint_expr(),
1203                        Expr::and(
1204                            self.resource_constraint_expr(),
1205                            self.non_scope_constraints()
1206                                .cloned()
1207                                .unwrap_or_else(|| Expr::val(true)),
1208                        )
1209                        .with_maybe_source_loc(loc.clone()),
1210                    )
1211                    .with_maybe_source_loc(loc.clone()),
1212                )
1213                .with_maybe_source_loc(loc)
1214            }
1215            #[cfg(feature = "tolerant-ast")]
1216            TemplateBody::TemplateBodyError(_, _) => DEFAULT_ERROR_EXPR.as_ref().clone(),
1217        }
1218    }
1219
1220    /// Construct a `Policy` from components that are already [`std::sync::Arc`] allocated
1221    #[allow(clippy::too_many_arguments)]
1222    pub fn new_shared(
1223        id: PolicyID,
1224        loc: Option<Loc>,
1225        annotations: Arc<Annotations>,
1226        effect: Effect,
1227        principal_constraint: PrincipalConstraint,
1228        action_constraint: ActionConstraint,
1229        resource_constraint: ResourceConstraint,
1230        non_scope_constraints: Option<Arc<Expr>>,
1231    ) -> Self {
1232        Self::TemplateBody(TemplateBodyImpl {
1233            id,
1234            loc,
1235            annotations,
1236            effect,
1237            principal_constraint,
1238            action_constraint,
1239            resource_constraint,
1240            non_scope_constraints,
1241        })
1242    }
1243
1244    /// Construct a `Policy` from its components
1245    #[allow(clippy::too_many_arguments)]
1246    pub fn new(
1247        id: PolicyID,
1248        loc: Option<Loc>,
1249        annotations: Annotations,
1250        effect: Effect,
1251        principal_constraint: PrincipalConstraint,
1252        action_constraint: ActionConstraint,
1253        resource_constraint: ResourceConstraint,
1254        non_scope_constraints: Option<Expr>,
1255    ) -> Self {
1256        Self::TemplateBody(TemplateBodyImpl {
1257            id,
1258            loc,
1259            annotations: Arc::new(annotations),
1260            effect,
1261            principal_constraint,
1262            action_constraint,
1263            resource_constraint,
1264            non_scope_constraints: non_scope_constraints.map(Arc::new),
1265        })
1266    }
1267}
1268
1269impl From<StaticPolicy> for TemplateBody {
1270    fn from(p: StaticPolicy) -> Self {
1271        p.0
1272    }
1273}
1274
1275impl std::fmt::Display for TemplateBody {
1276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1277        match self {
1278            TemplateBody::TemplateBody(template_body_impl) => {
1279                template_body_impl.annotations.fmt(f)?;
1280                write!(
1281                    f,
1282                    "{}(\n  {},\n  {},\n  {}\n)",
1283                    self.effect(),
1284                    self.principal_constraint(),
1285                    self.action_constraint(),
1286                    self.resource_constraint(),
1287                )?;
1288                if let Some(non_scope_constraints) = self.non_scope_constraints() {
1289                    write!(f, " when {{\n  {non_scope_constraints}\n}};")
1290                } else {
1291                    write!(f, ";")
1292                }
1293            }
1294            #[cfg(feature = "tolerant-ast")]
1295            TemplateBody::TemplateBodyError(policy_id, _) => {
1296                write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1297            }
1298        }
1299    }
1300}
1301
1302/// Template constraint on principal scope variables
1303#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1304pub struct PrincipalConstraint {
1305    pub(crate) constraint: PrincipalOrResourceConstraint,
1306}
1307
1308impl PrincipalConstraint {
1309    /// Construct a principal constraint
1310    pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1311        PrincipalConstraint { constraint }
1312    }
1313
1314    /// Get constraint as ref
1315    pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1316        &self.constraint
1317    }
1318
1319    /// Get constraint by value
1320    pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1321        self.constraint
1322    }
1323
1324    /// Get the constraint as raw AST
1325    pub fn as_expr(&self) -> Expr {
1326        self.constraint.as_expr(PrincipalOrResource::Principal)
1327    }
1328
1329    /// Unconstrained.
1330    pub fn any() -> Self {
1331        PrincipalConstraint {
1332            constraint: PrincipalOrResourceConstraint::any(),
1333        }
1334    }
1335
1336    /// Constrained to equal a specific euid.
1337    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1338        PrincipalConstraint {
1339            constraint: PrincipalOrResourceConstraint::is_eq(euid),
1340        }
1341    }
1342
1343    /// Constrained to be equal to a slot
1344    pub fn is_eq_slot() -> Self {
1345        Self {
1346            constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1347        }
1348    }
1349
1350    /// Hierarchical constraint.
1351    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1352        PrincipalConstraint {
1353            constraint: PrincipalOrResourceConstraint::is_in(euid),
1354        }
1355    }
1356
1357    /// Hierarchical constraint to Slot
1358    pub fn is_in_slot() -> Self {
1359        Self {
1360            constraint: PrincipalOrResourceConstraint::is_in_slot(),
1361        }
1362    }
1363
1364    /// Type constraint additionally constrained to be in a slot.
1365    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1366        Self {
1367            constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1368        }
1369    }
1370
1371    /// Type constraint, with a hierarchical constraint.
1372    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1373        Self {
1374            constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1375        }
1376    }
1377
1378    /// Type constraint, with no hierarchical constraint or slot.
1379    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1380        Self {
1381            constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1382        }
1383    }
1384
1385    /// Fill in the Slot, if any, with the given EUID
1386    pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1387        match self.constraint {
1388            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1389                constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1390            },
1391            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1392                constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1393            },
1394            PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1395                constraint: PrincipalOrResourceConstraint::IsIn(
1396                    entity_type,
1397                    EntityReference::EUID(euid),
1398                ),
1399            },
1400            // enumerated so it is clearer what other variants there are
1401            PrincipalOrResourceConstraint::Is(_)
1402            | PrincipalOrResourceConstraint::Any
1403            | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1404            | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1405            | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1406        }
1407    }
1408}
1409
1410impl std::fmt::Display for PrincipalConstraint {
1411    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1412        write!(
1413            f,
1414            "{}",
1415            self.constraint.display(PrincipalOrResource::Principal)
1416        )
1417    }
1418}
1419
1420/// Template constraint on resource scope variables
1421#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1422pub struct ResourceConstraint {
1423    pub(crate) constraint: PrincipalOrResourceConstraint,
1424}
1425
1426impl ResourceConstraint {
1427    /// Construct from constraint
1428    pub fn new(constraint: PrincipalOrResourceConstraint) -> Self {
1429        ResourceConstraint { constraint }
1430    }
1431
1432    /// Get constraint as ref
1433    pub fn as_inner(&self) -> &PrincipalOrResourceConstraint {
1434        &self.constraint
1435    }
1436
1437    /// Get constraint by value
1438    pub fn into_inner(self) -> PrincipalOrResourceConstraint {
1439        self.constraint
1440    }
1441
1442    /// Convert into an Expression. It will be a boolean valued expression.
1443    pub fn as_expr(&self) -> Expr {
1444        self.constraint.as_expr(PrincipalOrResource::Resource)
1445    }
1446
1447    /// Unconstrained.
1448    pub fn any() -> Self {
1449        ResourceConstraint {
1450            constraint: PrincipalOrResourceConstraint::any(),
1451        }
1452    }
1453
1454    /// Constrained to equal a specific euid.
1455    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1456        ResourceConstraint {
1457            constraint: PrincipalOrResourceConstraint::is_eq(euid),
1458        }
1459    }
1460
1461    /// Constrained to equal a slot.
1462    pub fn is_eq_slot() -> Self {
1463        Self {
1464            constraint: PrincipalOrResourceConstraint::is_eq_slot(),
1465        }
1466    }
1467
1468    /// Constrained to be in a slot
1469    pub fn is_in_slot() -> Self {
1470        Self {
1471            constraint: PrincipalOrResourceConstraint::is_in_slot(),
1472        }
1473    }
1474
1475    /// Hierarchical constraint.
1476    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1477        ResourceConstraint {
1478            constraint: PrincipalOrResourceConstraint::is_in(euid),
1479        }
1480    }
1481
1482    /// Type constraint additionally constrained to be in a slot.
1483    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1484        Self {
1485            constraint: PrincipalOrResourceConstraint::is_entity_type_in_slot(entity_type),
1486        }
1487    }
1488
1489    /// Type constraint, with a hierarchical constraint.
1490    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1491        Self {
1492            constraint: PrincipalOrResourceConstraint::is_entity_type_in(entity_type, in_entity),
1493        }
1494    }
1495
1496    /// Type constraint, with no hierarchical constraint or slot.
1497    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1498        Self {
1499            constraint: PrincipalOrResourceConstraint::is_entity_type(entity_type),
1500        }
1501    }
1502
1503    /// Fill in the Slot, if any, with the given EUID
1504    pub fn with_filled_slot(self, euid: Arc<EntityUID>) -> Self {
1505        match self.constraint {
1506            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => Self {
1507                constraint: PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)),
1508            },
1509            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => Self {
1510                constraint: PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)),
1511            },
1512            PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(_)) => Self {
1513                constraint: PrincipalOrResourceConstraint::IsIn(
1514                    entity_type,
1515                    EntityReference::EUID(euid),
1516                ),
1517            },
1518            // enumerated so it is clearer what other variants there are
1519            PrincipalOrResourceConstraint::Is(_)
1520            | PrincipalOrResourceConstraint::Any
1521            | PrincipalOrResourceConstraint::Eq(EntityReference::EUID(_))
1522            | PrincipalOrResourceConstraint::In(EntityReference::EUID(_))
1523            | PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(_)) => self,
1524        }
1525    }
1526}
1527
1528impl std::fmt::Display for ResourceConstraint {
1529    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1530        write!(
1531            f,
1532            "{}",
1533            self.as_inner().display(PrincipalOrResource::Resource)
1534        )
1535    }
1536}
1537
1538/// A reference to an EntityUID that may be a Slot
1539#[derive(Educe, Clone, Debug, Eq)]
1540#[educe(Hash, PartialEq, PartialOrd, Ord)]
1541pub enum EntityReference {
1542    /// Reference to a literal EUID
1543    EUID(Arc<EntityUID>),
1544    /// Template Slot
1545    Slot(
1546        #[educe(PartialEq(ignore))]
1547        #[educe(PartialOrd(ignore))]
1548        #[educe(Hash(ignore))]
1549        Option<Loc>,
1550    ),
1551}
1552
1553impl EntityReference {
1554    /// Create an entity reference to a specific EntityUID
1555    pub fn euid(euid: Arc<EntityUID>) -> Self {
1556        Self::EUID(euid)
1557    }
1558
1559    /// Transform into an expression AST
1560    ///
1561    /// `slot` indicates what `SlotId` would be implied by
1562    /// `EntityReference::Slot`, which is always clear from the caller's
1563    /// context.
1564    pub fn into_expr(&self, slot: SlotId) -> Expr {
1565        match self {
1566            EntityReference::EUID(euid) => Expr::val(euid.clone()),
1567            EntityReference::Slot(loc) => Expr::slot(slot).with_maybe_source_loc(loc.clone()),
1568        }
1569    }
1570}
1571
1572/// Error for unexpected slots
1573#[derive(Debug, Clone, PartialEq, Eq, Error)]
1574pub enum UnexpectedSlotError {
1575    /// Found this slot where slots are not allowed
1576    #[error("found slot `{}` where slots are not allowed", .0.id)]
1577    FoundSlot(Slot),
1578}
1579
1580impl Diagnostic for UnexpectedSlotError {
1581    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
1582        match self {
1583            Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|loc| {
1584                let label = miette::LabeledSpan::underline(loc.span);
1585                Box::new(std::iter::once(label)) as _
1586            }),
1587        }
1588    }
1589
1590    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
1591        match self {
1592            Self::FoundSlot(Slot { loc, .. }) => loc.as_ref().map(|l| l as &dyn miette::SourceCode),
1593        }
1594    }
1595}
1596
1597impl From<EntityUID> for EntityReference {
1598    fn from(euid: EntityUID) -> Self {
1599        Self::EUID(Arc::new(euid))
1600    }
1601}
1602
1603/// Subset of AST variables that have the same constraint form
1604#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1605#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1606pub enum PrincipalOrResource {
1607    /// The principal of a request
1608    Principal,
1609    /// The resource of a request
1610    Resource,
1611}
1612
1613impl std::fmt::Display for PrincipalOrResource {
1614    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1615        let v = Var::from(*self);
1616        write!(f, "{v}")
1617    }
1618}
1619
1620impl TryFrom<Var> for PrincipalOrResource {
1621    type Error = Var;
1622
1623    fn try_from(value: Var) -> Result<Self, Self::Error> {
1624        match value {
1625            Var::Principal => Ok(Self::Principal),
1626            Var::Action => Err(Var::Action),
1627            Var::Resource => Ok(Self::Resource),
1628            Var::Context => Err(Var::Context),
1629        }
1630    }
1631}
1632
1633/// Represents the constraints for principals and resources.
1634/// Can either not constrain, or constrain via `==` or `in` for a single entity literal.
1635#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1636pub enum PrincipalOrResourceConstraint {
1637    /// Unconstrained
1638    Any,
1639    /// Hierarchical constraint
1640    In(EntityReference),
1641    /// Equality constraint
1642    Eq(EntityReference),
1643    /// Type constraint,
1644    Is(Arc<EntityType>),
1645    /// Type constraint with a hierarchy constraint
1646    IsIn(Arc<EntityType>, EntityReference),
1647}
1648
1649impl PrincipalOrResourceConstraint {
1650    /// Unconstrained.
1651    pub fn any() -> Self {
1652        PrincipalOrResourceConstraint::Any
1653    }
1654
1655    /// Constrained to equal a specific euid.
1656    pub fn is_eq(euid: Arc<EntityUID>) -> Self {
1657        PrincipalOrResourceConstraint::Eq(EntityReference::euid(euid))
1658    }
1659
1660    /// Constrained to equal a slot
1661    pub fn is_eq_slot() -> Self {
1662        PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None))
1663    }
1664
1665    /// Constrained to be in a slot
1666    pub fn is_in_slot() -> Self {
1667        PrincipalOrResourceConstraint::In(EntityReference::Slot(None))
1668    }
1669
1670    /// Hierarchical constraint.
1671    pub fn is_in(euid: Arc<EntityUID>) -> Self {
1672        PrincipalOrResourceConstraint::In(EntityReference::euid(euid))
1673    }
1674
1675    /// Type constraint additionally constrained to be in a slot.
1676    pub fn is_entity_type_in_slot(entity_type: Arc<EntityType>) -> Self {
1677        PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::Slot(None))
1678    }
1679
1680    /// Type constraint with a hierarchical constraint.
1681    pub fn is_entity_type_in(entity_type: Arc<EntityType>, in_entity: Arc<EntityUID>) -> Self {
1682        PrincipalOrResourceConstraint::IsIn(entity_type, EntityReference::euid(in_entity))
1683    }
1684
1685    /// Type constraint, with no hierarchical constraint or slot.
1686    pub fn is_entity_type(entity_type: Arc<EntityType>) -> Self {
1687        PrincipalOrResourceConstraint::Is(entity_type)
1688    }
1689
1690    /// Turn the constraint into an expr
1691    /// # arguments
1692    /// * `v` - The variable name to be used in the expression.
1693    pub fn as_expr(&self, v: PrincipalOrResource) -> Expr {
1694        match self {
1695            PrincipalOrResourceConstraint::Any => Expr::val(true),
1696            PrincipalOrResourceConstraint::Eq(euid) => {
1697                Expr::is_eq(Expr::var(v.into()), euid.into_expr(v.into()))
1698            }
1699            PrincipalOrResourceConstraint::In(euid) => {
1700                Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into()))
1701            }
1702            PrincipalOrResourceConstraint::IsIn(entity_type, euid) => Expr::and(
1703                Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone()),
1704                Expr::is_in(Expr::var(v.into()), euid.into_expr(v.into())),
1705            ),
1706            PrincipalOrResourceConstraint::Is(entity_type) => {
1707                Expr::is_entity_type(Expr::var(v.into()), entity_type.as_ref().clone())
1708            }
1709        }
1710    }
1711
1712    /// Pretty print the constraint
1713    /// # arguments
1714    /// * `v` - The variable name to be used in the expression.
1715    pub fn display(&self, v: PrincipalOrResource) -> String {
1716        match self {
1717            PrincipalOrResourceConstraint::In(euid) => {
1718                format!("{} in {}", v, euid.into_expr(v.into()))
1719            }
1720            PrincipalOrResourceConstraint::Eq(euid) => {
1721                format!("{} == {}", v, euid.into_expr(v.into()))
1722            }
1723            PrincipalOrResourceConstraint::IsIn(entity_type, euid) => {
1724                format!("{} is {} in {}", v, entity_type, euid.into_expr(v.into()))
1725            }
1726            PrincipalOrResourceConstraint::Is(entity_type) => {
1727                format!("{v} is {entity_type}")
1728            }
1729            PrincipalOrResourceConstraint::Any => format!("{v}"),
1730        }
1731    }
1732
1733    /// Get the entity uid in this constraint or `None` if there are no uids in the constraint
1734    pub fn get_euid(&self) -> Option<&Arc<EntityUID>> {
1735        match self {
1736            PrincipalOrResourceConstraint::Any => None,
1737            PrincipalOrResourceConstraint::In(EntityReference::EUID(euid)) => Some(euid),
1738            PrincipalOrResourceConstraint::In(EntityReference::Slot(_)) => None,
1739            PrincipalOrResourceConstraint::Eq(EntityReference::EUID(euid)) => Some(euid),
1740            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(_)) => None,
1741            PrincipalOrResourceConstraint::IsIn(_, EntityReference::EUID(euid)) => Some(euid),
1742            PrincipalOrResourceConstraint::IsIn(_, EntityReference::Slot(_)) => None,
1743            PrincipalOrResourceConstraint::Is(_) => None,
1744        }
1745    }
1746
1747    /// Get an iterator over all of the entity type names in this constraint.
1748    pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1749        self.get_euid()
1750            .into_iter()
1751            .map(|euid| euid.entity_type())
1752            .chain(match self {
1753                PrincipalOrResourceConstraint::Is(entity_type)
1754                | PrincipalOrResourceConstraint::IsIn(entity_type, _) => Some(entity_type.as_ref()),
1755                _ => None,
1756            })
1757    }
1758}
1759
1760/// Constraint for action scope variables.
1761/// Action variables can be constrained to be in any variable in a list.
1762#[derive(Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)]
1763pub enum ActionConstraint {
1764    /// Unconstrained
1765    Any,
1766    /// Constrained to being in a list.
1767    In(Vec<Arc<EntityUID>>),
1768    /// Constrained to equal a specific euid.
1769    Eq(Arc<EntityUID>),
1770    #[cfg(feature = "tolerant-ast")]
1771    /// Error node representing an action constraint that failed to parse
1772    ErrorConstraint,
1773}
1774
1775impl std::fmt::Display for ActionConstraint {
1776    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1777        let render_euids =
1778            |euids: &Vec<Arc<EntityUID>>| euids.iter().map(|euid| format!("{euid}")).join(",");
1779        match self {
1780            ActionConstraint::Any => write!(f, "action"),
1781            ActionConstraint::In(euids) => {
1782                write!(f, "action in [{}]", render_euids(euids))
1783            }
1784            ActionConstraint::Eq(euid) => write!(f, "action == {euid}"),
1785            #[cfg(feature = "tolerant-ast")]
1786            ActionConstraint::ErrorConstraint => write!(f, "<invalid_action_constraint>"),
1787        }
1788    }
1789}
1790
1791impl ActionConstraint {
1792    /// Unconstrained action.
1793    pub fn any() -> Self {
1794        ActionConstraint::Any
1795    }
1796
1797    /// Action constrained to being in a list of euids.
1798    pub fn is_in(euids: impl IntoIterator<Item = EntityUID>) -> Self {
1799        ActionConstraint::In(euids.into_iter().map(Arc::new).collect())
1800    }
1801
1802    /// Action constrained to being equal to a euid.
1803    pub fn is_eq(euid: EntityUID) -> Self {
1804        ActionConstraint::Eq(Arc::new(euid))
1805    }
1806
1807    fn euids_into_expr(euids: impl IntoIterator<Item = Arc<EntityUID>>) -> Expr {
1808        Expr::set(euids.into_iter().map(Expr::val))
1809    }
1810
1811    /// Turn the constraint into an expression.
1812    pub fn as_expr(&self) -> Expr {
1813        match self {
1814            ActionConstraint::Any => Expr::val(true),
1815            ActionConstraint::In(euids) => Expr::is_in(
1816                Expr::var(Var::Action),
1817                ActionConstraint::euids_into_expr(euids.iter().cloned()),
1818            ),
1819            ActionConstraint::Eq(euid) => {
1820                Expr::is_eq(Expr::var(Var::Action), Expr::val(euid.clone()))
1821            }
1822            #[cfg(feature = "tolerant-ast")]
1823            ActionConstraint::ErrorConstraint => Expr::new(
1824                ExprKind::Error {
1825                    error_kind: AstExprErrorKind::InvalidExpr(
1826                        "Invalid action constraint".to_string(),
1827                    ),
1828                },
1829                None,
1830                (),
1831            ),
1832        }
1833    }
1834
1835    /// Get an iterator over all of the entity uids in this constraint.
1836    pub fn iter_euids(&self) -> impl Iterator<Item = &'_ EntityUID> {
1837        match self {
1838            ActionConstraint::Any => EntityIterator::None,
1839            ActionConstraint::In(euids) => {
1840                EntityIterator::Bunch(euids.iter().map(Arc::as_ref).collect())
1841            }
1842            ActionConstraint::Eq(euid) => EntityIterator::One(euid),
1843            #[cfg(feature = "tolerant-ast")]
1844            ActionConstraint::ErrorConstraint => EntityIterator::None,
1845        }
1846    }
1847
1848    /// Get an iterator over all of the entity types in this constraint.
1849    pub fn iter_entity_type_names(&self) -> impl Iterator<Item = &'_ EntityType> {
1850        self.iter_euids().map(|euid| euid.entity_type())
1851    }
1852
1853    /// Check that all of the EUIDs in an action constraint have the type
1854    /// `Action`, under an arbitrary namespace.
1855    pub fn contains_only_action_types(self) -> Result<Self, NonEmpty<Arc<EntityUID>>> {
1856        match self {
1857            ActionConstraint::Any => Ok(self),
1858            ActionConstraint::In(ref euids) => {
1859                if let Some(euids) =
1860                    NonEmpty::collect(euids.iter().filter(|euid| !euid.is_action()).cloned())
1861                {
1862                    Err(euids)
1863                } else {
1864                    Ok(self)
1865                }
1866            }
1867            ActionConstraint::Eq(ref euid) => {
1868                if euid.is_action() {
1869                    Ok(self)
1870                } else {
1871                    Err(nonempty![euid.clone()])
1872                }
1873            }
1874            #[cfg(feature = "tolerant-ast")]
1875            ActionConstraint::ErrorConstraint => Ok(self),
1876        }
1877    }
1878}
1879
1880impl std::fmt::Display for StaticPolicy {
1881    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1882        let policy_template = &self.0;
1883        match policy_template {
1884            TemplateBody::TemplateBody(template_body_impl) => {
1885                for (k, v) in template_body_impl.annotations.iter() {
1886                    writeln!(f, "@{}(\"{}\")", k, v.val.escape_debug())?
1887                }
1888                write!(
1889                    f,
1890                    "{}(\n  {},\n  {},\n  {}\n)",
1891                    self.effect(),
1892                    self.principal_constraint(),
1893                    self.action_constraint(),
1894                    self.resource_constraint(),
1895                )?;
1896                if let Some(non_scope_constraints) = self.non_scope_constraints() {
1897                    write!(f, " when {{\n  {non_scope_constraints}\n}};")
1898                } else {
1899                    write!(f, ";")
1900                }
1901            }
1902            #[cfg(feature = "tolerant-ast")]
1903            TemplateBody::TemplateBodyError(policy_id, _) => {
1904                write!(f, "TemplateBody::TemplateBodyError({policy_id})")
1905            }
1906        }
1907    }
1908}
1909
1910/// A unique identifier for a policy statement
1911#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
1912pub struct PolicyID(SmolStr);
1913
1914impl PolicyID {
1915    /// Create a PolicyID from a string or string-like
1916    pub fn from_string(id: impl AsRef<str>) -> Self {
1917        Self(SmolStr::from(id.as_ref()))
1918    }
1919
1920    /// Create a PolicyID from a `SmolStr`
1921    pub fn from_smolstr(id: SmolStr) -> Self {
1922        Self(id)
1923    }
1924}
1925
1926impl std::fmt::Display for PolicyID {
1927    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1928        write!(f, "{}", self.0.escape_debug())
1929    }
1930}
1931
1932impl AsRef<str> for PolicyID {
1933    fn as_ref(&self) -> &str {
1934        &self.0
1935    }
1936}
1937
1938#[cfg(feature = "arbitrary")]
1939impl arbitrary::Arbitrary<'_> for PolicyID {
1940    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<PolicyID> {
1941        let s: String = u.arbitrary()?;
1942        Ok(PolicyID::from_string(s))
1943    }
1944    fn size_hint(depth: usize) -> (usize, Option<usize>) {
1945        <String as arbitrary::Arbitrary>::size_hint(depth)
1946    }
1947}
1948
1949/// the Effect of a policy
1950#[derive(Serialize, Deserialize, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
1951#[serde(rename_all = "camelCase")]
1952#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1953#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1954#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1955pub enum Effect {
1956    /// this is a Permit policy
1957    Permit,
1958    /// this is a Forbid policy
1959    Forbid,
1960}
1961
1962impl std::fmt::Display for Effect {
1963    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1964        match self {
1965            Self::Permit => write!(f, "permit"),
1966            Self::Forbid => write!(f, "forbid"),
1967        }
1968    }
1969}
1970
1971enum EntityIterator<'a> {
1972    None,
1973    One(&'a EntityUID),
1974    Bunch(Vec<&'a EntityUID>),
1975}
1976
1977impl<'a> Iterator for EntityIterator<'a> {
1978    type Item = &'a EntityUID;
1979
1980    fn next(&mut self) -> Option<Self::Item> {
1981        match self {
1982            EntityIterator::None => None,
1983            EntityIterator::One(euid) => {
1984                let eptr = *euid;
1985                let mut ptr = EntityIterator::None;
1986                std::mem::swap(self, &mut ptr);
1987                Some(eptr)
1988            }
1989            EntityIterator::Bunch(v) => v.pop(),
1990        }
1991    }
1992}
1993
1994#[cfg(test)]
1995pub(crate) mod test_generators {
1996    use super::*;
1997
1998    pub fn all_por_constraints() -> impl Iterator<Item = PrincipalOrResourceConstraint> {
1999        let euid = Arc::new(EntityUID::with_eid("test"));
2000        let v = vec![
2001            PrincipalOrResourceConstraint::any(),
2002            PrincipalOrResourceConstraint::is_eq(euid.clone()),
2003            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)),
2004            PrincipalOrResourceConstraint::is_in(euid),
2005            PrincipalOrResourceConstraint::In(EntityReference::Slot(None)),
2006        ];
2007
2008        v.into_iter()
2009    }
2010
2011    pub fn all_principal_constraints() -> impl Iterator<Item = PrincipalConstraint> {
2012        all_por_constraints().map(|constraint| PrincipalConstraint { constraint })
2013    }
2014
2015    pub fn all_resource_constraints() -> impl Iterator<Item = ResourceConstraint> {
2016        all_por_constraints().map(|constraint| ResourceConstraint { constraint })
2017    }
2018
2019    pub fn all_actions_constraints() -> impl Iterator<Item = ActionConstraint> {
2020        let euid: EntityUID = "Action::\"test\""
2021            .parse()
2022            .expect("Invalid action constraint euid");
2023        let v = vec![
2024            ActionConstraint::any(),
2025            ActionConstraint::is_eq(euid.clone()),
2026            ActionConstraint::is_in([euid.clone()]),
2027            ActionConstraint::is_in([euid.clone(), euid]),
2028        ];
2029
2030        v.into_iter()
2031    }
2032
2033    pub fn all_templates() -> impl Iterator<Item = Template> {
2034        let mut buf = vec![];
2035        let permit = PolicyID::from_string("permit");
2036        let forbid = PolicyID::from_string("forbid");
2037        for principal in all_principal_constraints() {
2038            for action in all_actions_constraints() {
2039                for resource in all_resource_constraints() {
2040                    let permit = Template::new(
2041                        permit.clone(),
2042                        None,
2043                        Annotations::new(),
2044                        Effect::Permit,
2045                        principal.clone(),
2046                        action.clone(),
2047                        resource.clone(),
2048                        None,
2049                    );
2050                    let forbid = Template::new(
2051                        forbid.clone(),
2052                        None,
2053                        Annotations::new(),
2054                        Effect::Forbid,
2055                        principal.clone(),
2056                        action.clone(),
2057                        resource.clone(),
2058                        None,
2059                    );
2060                    buf.push(permit);
2061                    buf.push(forbid);
2062                }
2063            }
2064        }
2065        buf.into_iter()
2066    }
2067}
2068
2069#[cfg(test)]
2070// PANIC SAFETY: Unit Test Code
2071#[allow(clippy::indexing_slicing)]
2072// PANIC SAFETY: Unit Test Code
2073#[allow(clippy::panic)]
2074mod test {
2075    use cool_asserts::assert_matches;
2076    use std::collections::HashSet;
2077
2078    use super::{test_generators::*, *};
2079    use crate::{
2080        parser::{
2081            parse_policy,
2082            test_utils::{expect_exactly_one_error, expect_some_error_matches},
2083        },
2084        test_utils::ExpectedErrorMessageBuilder,
2085    };
2086
2087    #[test]
2088    fn link_templates() {
2089        for template in all_templates() {
2090            template.check_invariant();
2091            let t = Arc::new(template);
2092            let env = t
2093                .slots()
2094                .map(|slot| (slot.id, EntityUID::with_eid("eid")))
2095                .collect();
2096            let _ = Template::link(t, PolicyID::from_string("id"), env).expect("Linking failed");
2097        }
2098    }
2099
2100    #[test]
2101    fn test_template_rebuild() {
2102        for template in all_templates() {
2103            let id = template.id().clone();
2104            let effect = template.effect();
2105            let p = template.principal_constraint().clone();
2106            let a = template.action_constraint().clone();
2107            let r = template.resource_constraint().clone();
2108            let non_scope = template.non_scope_constraints().cloned();
2109            let t2 = Template::new(id, None, Annotations::new(), effect, p, a, r, non_scope);
2110            assert_eq!(template, t2);
2111        }
2112    }
2113
2114    #[test]
2115    fn test_static_policy_rebuild() {
2116        for template in all_templates() {
2117            if let Ok(ip) = StaticPolicy::try_from(template.clone()) {
2118                let id = ip.id().clone();
2119                let e = ip.effect();
2120                let anno = ip
2121                    .annotations()
2122                    .map(|(k, v)| (k.clone(), v.clone()))
2123                    .collect();
2124                let p = ip.principal_constraint().clone();
2125                let a = ip.action_constraint().clone();
2126                let r = ip.resource_constraint().clone();
2127                let non_scope = ip.non_scope_constraints().clone();
2128                let ip2 = StaticPolicy::new(id, None, anno, e, p, a, r, non_scope.cloned())
2129                    .expect("Policy Creation Failed");
2130                assert_eq!(ip, ip2);
2131                let (t2, inst) = Template::link_static_policy(ip2);
2132                assert!(inst.is_static());
2133                assert_eq!(&template, t2.as_ref());
2134            }
2135        }
2136    }
2137
2138    #[test]
2139    fn ir_binding_too_many() {
2140        let tid = PolicyID::from_string("tid");
2141        let iid = PolicyID::from_string("iid");
2142        let t = Arc::new(Template::new(
2143            tid,
2144            None,
2145            Annotations::new(),
2146            Effect::Forbid,
2147            PrincipalConstraint::is_eq_slot(),
2148            ActionConstraint::Any,
2149            ResourceConstraint::any(),
2150            None,
2151        ));
2152        let mut m = HashMap::new();
2153        m.insert(SlotId::resource(), EntityUID::with_eid("eid"));
2154        assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2155            assert_eq!(unbound_values, vec![SlotId::principal()]);
2156            assert_eq!(extra_values, vec![SlotId::resource()]);
2157        });
2158    }
2159
2160    #[test]
2161    fn ir_binding_too_few() {
2162        let tid = PolicyID::from_string("tid");
2163        let iid = PolicyID::from_string("iid");
2164        let t = Arc::new(Template::new(
2165            tid,
2166            None,
2167            Annotations::new(),
2168            Effect::Forbid,
2169            PrincipalConstraint::is_eq_slot(),
2170            ActionConstraint::Any,
2171            ResourceConstraint::is_in_slot(),
2172            None,
2173        ));
2174        assert_matches!(Template::link(t.clone(), iid.clone(), HashMap::new()), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2175            assert_eq!(unbound_values, vec![SlotId::resource(), SlotId::principal()]);
2176            assert_eq!(extra_values, vec![]);
2177        });
2178        let mut m = HashMap::new();
2179        m.insert(SlotId::principal(), EntityUID::with_eid("eid"));
2180        assert_matches!(Template::link(t, iid, m), Err(LinkingError::ArityError { unbound_values, extra_values }) => {
2181            assert_eq!(unbound_values, vec![SlotId::resource()]);
2182            assert_eq!(extra_values, vec![]);
2183        });
2184    }
2185
2186    #[test]
2187    fn ir_binding() {
2188        let tid = PolicyID::from_string("template");
2189        let iid = PolicyID::from_string("linked");
2190        let t = Arc::new(Template::new(
2191            tid,
2192            None,
2193            Annotations::new(),
2194            Effect::Permit,
2195            PrincipalConstraint::is_in_slot(),
2196            ActionConstraint::any(),
2197            ResourceConstraint::is_eq_slot(),
2198            None,
2199        ));
2200
2201        let mut m = HashMap::new();
2202        m.insert(SlotId::principal(), EntityUID::with_eid("theprincipal"));
2203        m.insert(SlotId::resource(), EntityUID::with_eid("theresource"));
2204
2205        let r = Template::link(t, iid.clone(), m).expect("Should Succeed");
2206        assert_eq!(r.id(), &iid);
2207        assert_eq!(
2208            r.env().get(&SlotId::principal()),
2209            Some(&EntityUID::with_eid("theprincipal"))
2210        );
2211        assert_eq!(
2212            r.env().get(&SlotId::resource()),
2213            Some(&EntityUID::with_eid("theresource"))
2214        );
2215    }
2216
2217    #[test]
2218    fn isnt_template_implies_from_succeeds() {
2219        for template in all_templates() {
2220            if template.slots().count() == 0 {
2221                StaticPolicy::try_from(template).expect("Should succeed");
2222            }
2223        }
2224    }
2225
2226    #[test]
2227    fn is_template_implies_from_fails() {
2228        for template in all_templates() {
2229            if template.slots().count() != 0 {
2230                assert!(
2231                    StaticPolicy::try_from(template.clone()).is_err(),
2232                    "Following template did convert {template}"
2233                );
2234            }
2235        }
2236    }
2237
2238    #[test]
2239    fn non_template_iso() {
2240        for template in all_templates() {
2241            if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2242                let (t2, _) = Template::link_static_policy(p);
2243                assert_eq!(&template, t2.as_ref());
2244            }
2245        }
2246    }
2247
2248    #[test]
2249    fn template_into_expr() {
2250        for template in all_templates() {
2251            if let Ok(p) = StaticPolicy::try_from(template.clone()) {
2252                let t: Template = template;
2253                assert_eq!(p.condition(), t.condition());
2254                assert_eq!(p.effect(), t.effect());
2255            }
2256        }
2257    }
2258
2259    #[test]
2260    fn template_por_iter() {
2261        let e = Arc::new(EntityUID::with_eid("eid"));
2262        assert_eq!(PrincipalOrResourceConstraint::Any.get_euid(), None);
2263        assert_eq!(
2264            PrincipalOrResourceConstraint::In(EntityReference::EUID(e.clone())).get_euid(),
2265            Some(&e)
2266        );
2267        assert_eq!(
2268            PrincipalOrResourceConstraint::In(EntityReference::Slot(None)).get_euid(),
2269            None
2270        );
2271        assert_eq!(
2272            PrincipalOrResourceConstraint::Eq(EntityReference::EUID(e.clone())).get_euid(),
2273            Some(&e)
2274        );
2275        assert_eq!(
2276            PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None)).get_euid(),
2277            None
2278        );
2279        assert_eq!(
2280            PrincipalOrResourceConstraint::IsIn(
2281                Arc::new("T".parse().unwrap()),
2282                EntityReference::EUID(e.clone())
2283            )
2284            .get_euid(),
2285            Some(&e)
2286        );
2287        assert_eq!(
2288            PrincipalOrResourceConstraint::Is(Arc::new("T".parse().unwrap())).get_euid(),
2289            None
2290        );
2291        assert_eq!(
2292            PrincipalOrResourceConstraint::IsIn(
2293                Arc::new("T".parse().unwrap()),
2294                EntityReference::Slot(None)
2295            )
2296            .get_euid(),
2297            None
2298        );
2299    }
2300
2301    #[test]
2302    fn action_iter() {
2303        assert_eq!(ActionConstraint::Any.iter_euids().count(), 0);
2304        let a = ActionConstraint::Eq(Arc::new(EntityUID::with_eid("test")));
2305        let v = a.iter_euids().collect::<Vec<_>>();
2306        assert_eq!(vec![&EntityUID::with_eid("test")], v);
2307        let a =
2308            ActionConstraint::is_in([EntityUID::with_eid("test1"), EntityUID::with_eid("test2")]);
2309        let set = a.iter_euids().collect::<HashSet<_>>();
2310        let e1 = EntityUID::with_eid("test1");
2311        let e2 = EntityUID::with_eid("test2");
2312        let correct = vec![&e1, &e2].into_iter().collect::<HashSet<_>>();
2313        assert_eq!(set, correct);
2314    }
2315
2316    #[test]
2317    fn test_iter_none() {
2318        let mut i = EntityIterator::None;
2319        assert_eq!(i.next(), None);
2320    }
2321
2322    #[test]
2323    fn test_iter_once() {
2324        let id = EntityUID::from_components(
2325            name::Name::parse_unqualified_name("s").unwrap().into(),
2326            entity::Eid::new("eid"),
2327            None,
2328        );
2329        let mut i = EntityIterator::One(&id);
2330        assert_eq!(i.next(), Some(&id));
2331        assert_eq!(i.next(), None);
2332    }
2333
2334    #[test]
2335    fn test_iter_mult() {
2336        let id1 = EntityUID::from_components(
2337            name::Name::parse_unqualified_name("s").unwrap().into(),
2338            entity::Eid::new("eid1"),
2339            None,
2340        );
2341        let id2 = EntityUID::from_components(
2342            name::Name::parse_unqualified_name("s").unwrap().into(),
2343            entity::Eid::new("eid2"),
2344            None,
2345        );
2346        let v = vec![&id1, &id2];
2347        let mut i = EntityIterator::Bunch(v);
2348        assert_eq!(i.next(), Some(&id2));
2349        assert_eq!(i.next(), Some(&id1));
2350        assert_eq!(i.next(), None)
2351    }
2352
2353    #[test]
2354    fn euid_into_expr() {
2355        let e = EntityReference::Slot(None);
2356        assert_eq!(
2357            e.into_expr(SlotId::principal()),
2358            Expr::slot(SlotId::principal())
2359        );
2360        let e = EntityReference::euid(Arc::new(EntityUID::with_eid("eid")));
2361        assert_eq!(
2362            e.into_expr(SlotId::principal()),
2363            Expr::val(EntityUID::with_eid("eid"))
2364        );
2365    }
2366
2367    #[test]
2368    fn por_constraint_display() {
2369        let t = PrincipalOrResourceConstraint::Eq(EntityReference::Slot(None));
2370        let s = t.display(PrincipalOrResource::Principal);
2371        assert_eq!(s, "principal == ?principal");
2372        let t = PrincipalOrResourceConstraint::Eq(EntityReference::euid(Arc::new(
2373            EntityUID::with_eid("test"),
2374        )));
2375        let s = t.display(PrincipalOrResource::Principal);
2376        assert_eq!(s, "principal == test_entity_type::\"test\"");
2377    }
2378
2379    #[test]
2380    fn unexpected_templates() {
2381        let policy_str = r#"permit(principal == ?principal, action, resource);"#;
2382        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2383            expect_exactly_one_error(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2384                "expected a static policy, got a template containing the slot ?principal"
2385                )
2386                .help("try removing the template slot(s) from this policy")
2387                .exactly_one_underline("?principal")
2388                .build()
2389            );
2390        });
2391
2392        let policy_str =
2393            r#"permit(principal == ?principal, action, resource) when { ?principal == 3 } ;"#;
2394        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Err(e) => {
2395            expect_some_error_matches(policy_str, &e, &ExpectedErrorMessageBuilder::error(
2396                "expected a static policy, got a template containing the slot ?principal"
2397                )
2398                .help("try removing the template slot(s) from this policy")
2399                .exactly_one_underline("?principal")
2400                .build()
2401            );
2402            assert_eq!(e.len(), 2);
2403        });
2404    }
2405
2406    #[test]
2407    fn policy_to_expr() {
2408        let policy_str = r#"permit(principal is A, action, resource is B)
2409            when { 1 == 2 }
2410            unless { 2 == 1}
2411            when { 3 == 4}
2412            unless { 4 == 3};"#;
2413        assert_matches!(parse_policy(Some(PolicyID::from_string("id")), policy_str), Ok(p) => {
2414            assert_eq!(ToString::to_string(&p.condition()), "(principal is A) && (true && ((resource is B) && ((1 == 2) && ((!(2 == 1)) && ((3 == 4) && (!(4 == 3)))))))");
2415        });
2416    }
2417
2418    #[cfg(feature = "tolerant-ast")]
2419    #[test]
2420    fn template_body_error_methods() {
2421        use std::str::FromStr;
2422
2423        let policy_id = PolicyID::from_string("error_policy");
2424        let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2425        let error_body = TemplateBody::TemplateBodyError(policy_id.clone(), error_loc.clone());
2426
2427        let expected_error = <ExprWithErrsBuilder as ExprBuilder>::new()
2428            .error(ParseErrors::singleton(ToASTError::new(
2429                ToASTErrorKind::ASTErrorNode,
2430                Some(Loc::new(0..1, "ASTErrorNode".into())),
2431            )))
2432            .unwrap();
2433
2434        // Test id() method
2435        assert_eq!(error_body.id(), &policy_id);
2436
2437        // Test loc() method
2438        assert_eq!(error_body.loc(), error_loc.as_ref());
2439
2440        // Test new_id() method
2441        let new_policy_id = PolicyID::from_string("new_error_policy");
2442        let updated_error_body = error_body.new_id(new_policy_id.clone());
2443        assert_matches!(updated_error_body,
2444            TemplateBody::TemplateBodyError(id, loc) if id == new_policy_id && loc == error_loc
2445        );
2446
2447        // Test effect() method
2448        assert_eq!(error_body.effect(), Effect::Forbid);
2449
2450        // Test annotation() method
2451        assert_eq!(
2452            error_body.annotation(&AnyId::from_str("test").unwrap()),
2453            None
2454        );
2455
2456        // Test annotations() method
2457        assert!(error_body.annotations().count() == 0);
2458
2459        // Test principal_constraint() method
2460        assert_eq!(
2461            *error_body.principal_constraint(),
2462            PrincipalConstraint::any()
2463        );
2464
2465        // Test action_constraint() method
2466        assert_eq!(*error_body.action_constraint(), ActionConstraint::any());
2467
2468        // Test resource_constraint() method
2469        assert_eq!(*error_body.resource_constraint(), ResourceConstraint::any());
2470
2471        // Test non_scope_constraints() method
2472        assert_eq!(error_body.non_scope_constraints(), Some(&expected_error));
2473
2474        // Test condition() method
2475        assert_eq!(error_body.condition(), expected_error);
2476
2477        // Test Display implementation
2478        let display_str = format!("{error_body}");
2479        assert!(display_str.contains("TemplateBodyError"));
2480        assert!(display_str.contains("error_policy"));
2481    }
2482
2483    #[cfg(feature = "tolerant-ast")]
2484    #[test]
2485    fn template_error_methods() {
2486        let policy_id = PolicyID::from_string("error_policy");
2487        let error_loc = Some(Loc::new(0..1, "ASTErrorNode".into()));
2488        let error_template = Template::error(policy_id.clone(), error_loc.clone());
2489
2490        // Check template properties
2491        assert_eq!(error_template.id(), &policy_id);
2492
2493        // Check slots are empty
2494        assert!(error_template.slots().count() == 0);
2495
2496        // Check body is an error template body
2497        assert_matches!(error_template.body,
2498            TemplateBody::TemplateBodyError(ref id, ref loc) if id == &policy_id && loc == &error_loc
2499        );
2500
2501        // Test principal_constraint() method
2502        assert_eq!(
2503            error_template.principal_constraint(),
2504            &PrincipalConstraint::any()
2505        );
2506
2507        // Test action_constraint() method
2508        assert_eq!(*error_template.action_constraint(), ActionConstraint::any());
2509
2510        // Test resource_constraint() method
2511        assert_eq!(
2512            *error_template.resource_constraint(),
2513            ResourceConstraint::any()
2514        );
2515
2516        // Verify effect is Forbid
2517        assert_eq!(error_template.effect(), Effect::Forbid);
2518
2519        // Verify condition is the default error expression
2520        assert_eq!(
2521            error_template.condition(),
2522            DEFAULT_ERROR_EXPR.as_ref().clone()
2523        );
2524
2525        // Verify location is None
2526        assert_eq!(error_template.loc(), error_loc.as_ref());
2527
2528        // Verify annotations are default
2529        assert!(error_template.annotations().count() == 0);
2530
2531        // Verify display implementation
2532        let display_str = format!("{error_template}");
2533        assert!(display_str.contains("TemplateBody::TemplateBodyError"));
2534        assert!(display_str.contains(&policy_id.to_string()));
2535    }
2536}