cedar_policy_core/ast/
policy.rs

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