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