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