Skip to main content

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