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