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