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