cedar_policy_core/ast/
policy.rs

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