cedar_policy_core/ast/
policy.rs

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