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