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