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