Skip to main content

cedar_policy_core/ast/
policy_set.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use super::{
18    EntityUID, LinkingError, LiteralPolicy, Policy, PolicyID, ReificationError, SlotId,
19    StaticPolicy, Template,
20};
21use itertools::Itertools;
22use linked_hash_map::{Entry, LinkedHashMap};
23use linked_hash_set::LinkedHashSet;
24
25use miette::Diagnostic;
26use smol_str::format_smolstr;
27use std::{borrow::Borrow, collections::HashMap, sync::Arc};
28use thiserror::Error;
29
30/// Represents a set of `Policy`s
31#[derive(Debug, Default, Clone, PartialEq, Eq)]
32pub struct PolicySet {
33    /// `templates` contains all bodies of policies in the `PolicySet`.
34    /// A body is either:
35    /// - A Body of a `Template`, which has slots that need to be filled in
36    /// - A Body of a `StaticPolicy`, which has been converted into a `Template` that has zero slots.
37    ///   The static policy's [`PolicyID`] is the same in both `templates` and `links`.
38    templates: LinkedHashMap<PolicyID, Arc<Template>>,
39    /// `links` contains all of the executable policies in the `PolicySet`
40    /// A `StaticPolicy` must have exactly one `Policy` in `links`
41    ///   (this is managed by `PolicySet::add`)
42    ///   The static policy's PolicyID is the same in both `templates` and `links`
43    /// A `Template` may have zero or many links
44    links: LinkedHashMap<PolicyID, Policy>,
45
46    /// Map from a template `PolicyID` to the set of `PolicyID`s in `links` that are linked to that template.
47    /// There is a key `t` iff `templates` contains the key `t`. The value of `t` will be a (possibly empty)
48    /// set of every `p` in `links` s.t. `p.template().id() == t`.
49    template_to_links_map: LinkedHashMap<PolicyID, LinkedHashSet<PolicyID>>,
50}
51
52/// A Policy Set that contains less rich information than `PolicySet`.
53///
54/// In particular, this form is easier to convert to/from the Protobuf
55/// representation of a `PolicySet`, because policies are represented as
56/// `LiteralPolicy` instead of `Policy`.
57#[derive(Debug)]
58pub struct LiteralPolicySet {
59    /// Like the `templates` field of `PolicySet`
60    templates: LinkedHashMap<PolicyID, Template>,
61    /// Like the `links` field of `PolicySet`, but maps to `LiteralPolicy` only.
62    /// The same invariants apply: e.g., a `StaticPolicy` must have exactly one `Policy` in `links`.
63    links: LinkedHashMap<PolicyID, LiteralPolicy>,
64}
65
66impl LiteralPolicySet {
67    /// Create a new `LiteralPolicySet`. Caller is responsible for ensuring the
68    /// invariants on `LiteralPolicySet`.
69    pub fn new(
70        templates: impl IntoIterator<Item = (PolicyID, Template)>,
71        links: impl IntoIterator<Item = (PolicyID, LiteralPolicy)>,
72    ) -> Self {
73        Self {
74            templates: templates.into_iter().collect(),
75            links: links.into_iter().collect(),
76        }
77    }
78
79    /// Iterate over the `Template`s in the `LiteralPolicySet`. This will
80    /// include both templates and static policies (represented as templates
81    /// with zero slots)
82    pub fn templates(&self) -> impl Iterator<Item = &Template> {
83        self.templates.values()
84    }
85
86    /// Iterate over the `LiteralPolicy`s in the `LiteralPolicySet`. This will
87    /// include both static and template-linked policies.
88    pub fn policies(&self) -> impl Iterator<Item = &LiteralPolicy> {
89        self.links.values()
90    }
91}
92
93/// Converts a LiteralPolicySet into a PolicySet, ensuring the invariants are met
94/// Every `Policy` must point to a `Template` that exists in the set.
95impl TryFrom<LiteralPolicySet> for PolicySet {
96    type Error = ReificationError;
97    fn try_from(pset: LiteralPolicySet) -> Result<Self, Self::Error> {
98        // Allocate the templates into Arc's
99        let templates = pset
100            .templates
101            .into_iter()
102            .map(|(id, template)| (id, Arc::new(template)))
103            .collect::<LinkedHashMap<PolicyID, Arc<Template>>>();
104        let links = pset
105            .links
106            .into_iter()
107            .map(|(id, literal)| literal.reify(&templates).map(|linked| (id, linked)))
108            .collect::<Result<LinkedHashMap<PolicyID, Policy>, ReificationError>>()?;
109
110        let mut template_to_links_map = LinkedHashMap::new();
111        for template in &templates {
112            template_to_links_map.insert(template.0.clone(), LinkedHashSet::new());
113        }
114        for (link_id, link) in &links {
115            let template = link.template().id();
116            match template_to_links_map.entry(template.clone()) {
117                Entry::Occupied(t) => t.into_mut().insert(link_id.clone()),
118                Entry::Vacant(_) => return Err(ReificationError::NoSuchTemplate(template.clone())),
119            };
120        }
121
122        Ok(Self {
123            templates,
124            links,
125            template_to_links_map,
126        })
127    }
128}
129
130impl From<PolicySet> for LiteralPolicySet {
131    fn from(pset: PolicySet) -> Self {
132        let templates = pset
133            .templates
134            .into_iter()
135            .map(|(id, template)| (id, template.as_ref().clone()))
136            .collect();
137        let links = pset
138            .links
139            .into_iter()
140            .map(|(id, p)| (id, p.into()))
141            .collect();
142        Self { templates, links }
143    }
144}
145
146/// Potential errors when working with `PolicySet`s.
147#[derive(Debug, Diagnostic, Error)]
148pub enum PolicySetError {
149    /// There was a duplicate [`PolicyID`] encountered in either the set of
150    /// templates or the set of policies.
151    #[error("duplicate template or policy id `{id}`")]
152    Occupied {
153        /// [`PolicyID`] that was duplicate
154        id: PolicyID,
155    },
156}
157
158/// Potential errors when working with `PolicySet`s.
159#[derive(Debug, Diagnostic, Error)]
160pub enum PolicySetGetLinksError {
161    /// There was no [`PolicyID`] in the set of templates.
162    #[error("No template `{0}`")]
163    MissingTemplate(PolicyID),
164}
165
166/// Potential errors when unlinking from a `PolicySet`.
167#[derive(Debug, Diagnostic, Error)]
168pub enum PolicySetUnlinkError {
169    /// There was no [`PolicyID`] linked policy to unlink
170    #[error("unable to unlink policy id `{0}` because it does not exist")]
171    UnlinkingError(PolicyID),
172    /// There was a template [`PolicyID`] in the list of templates, so `PolicyID` is a static policy
173    #[error("unable to remove link with policy id `{0}` because it is a static policy")]
174    NotLinkError(PolicyID),
175}
176
177/// Potential errors when removing templates from a `PolicySet`.
178#[derive(Debug, Diagnostic, Error)]
179pub enum PolicySetTemplateRemovalError {
180    /// There was no [`PolicyID`] template in the list of templates.
181    #[error("unable to remove template id `{0}` from template list because it does not exist")]
182    RemovePolicyNoTemplateError(PolicyID),
183    /// There are still active links to template [`PolicyID`].
184    #[error(
185        "unable to remove template id `{0}` from template list because it still has active links"
186    )]
187    RemoveTemplateWithLinksError(PolicyID),
188    /// There was a link [`PolicyID`] in the list of links, so `PolicyID` is a static policy
189    #[error("unable to remove template with policy id `{0}` because it is a static policy")]
190    NotTemplateError(PolicyID),
191}
192
193/// Potential errors when removing policies from a `PolicySet`.
194#[derive(Debug, Diagnostic, Error)]
195pub enum PolicySetPolicyRemovalError {
196    /// There was no link [`PolicyID`] in the list of links.
197    #[error("unable to remove static policy id `{0}` from link list because it does not exist")]
198    RemovePolicyNoLinkError(PolicyID),
199    /// There was no template [`PolicyID`] in the list of templates.
200    #[error(
201        "unable to remove static policy id `{0}` from template list because it does not exist"
202    )]
203    RemovePolicyNoTemplateError(PolicyID),
204}
205
206// The public interface of `PolicySet` is intentionally narrow, to allow us
207// maximum flexibility to change the underlying implementation in the future
208impl PolicySet {
209    /// Create a fresh empty `PolicySet`
210    pub fn new() -> Self {
211        Self {
212            templates: LinkedHashMap::new(),
213            links: LinkedHashMap::new(),
214            template_to_links_map: LinkedHashMap::new(),
215        }
216    }
217
218    /// Create a `PolicySet` containing a single policy.
219    pub fn singleton(p: Policy) -> Self {
220        let t = p.template_arc();
221
222        let templates = std::iter::once((t.id().clone(), t.clone())).collect();
223        let template_to_links_map =
224            std::iter::once((t.id().clone(), std::iter::once(p.id().clone()).collect())).collect();
225        let links = std::iter::once((p.id().clone(), p)).collect();
226
227        Self {
228            templates,
229            links,
230            template_to_links_map,
231        }
232    }
233
234    /// Add a `Policy` to the `PolicySet`.
235    pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
236        let t = policy.template_arc();
237
238        // we need to check for all possible errors before making any
239        // modifications to `self`.
240        // So we just collect the `ventry` here, and we only do the insertion
241        // once we know there will be no error
242        let template_ventry = match self.templates.entry(t.id().clone()) {
243            Entry::Vacant(ventry) => Some(ventry),
244            Entry::Occupied(oentry) => {
245                if oentry.get() != &t {
246                    return Err(PolicySetError::Occupied {
247                        id: oentry.key().clone(),
248                    });
249                }
250                None
251            }
252        };
253
254        let link_ventry = match self.links.entry(policy.id().clone()) {
255            Entry::Vacant(ventry) => Some(ventry),
256            Entry::Occupied(oentry) => {
257                return Err(PolicySetError::Occupied {
258                    id: oentry.key().clone(),
259                });
260            }
261        };
262
263        // if we get here, there will be no errors.  So actually do the
264        // insertions.
265        if let Some(ventry) = template_ventry {
266            self.template_to_links_map.insert(
267                t.id().clone(),
268                std::iter::once(policy.id().clone()).collect(),
269            );
270            ventry.insert(t);
271        } else {
272            //`template_ventry` is None, so `templates` has `t` and we never use the `HashSet::new()`
273            self.template_to_links_map
274                .entry(t.id().clone())
275                .or_default()
276                .insert(policy.id().clone());
277        }
278        if let Some(ventry) = link_ventry {
279            ventry.insert(policy);
280        }
281
282        Ok(())
283    }
284
285    /// Helper function for `merge_policyset` to check if the `PolicyID` pid
286    /// appears in this `PolicySet`'s links or templates.
287    fn policy_id_is_bound(&self, pid: &PolicyID) -> bool {
288        self.templates.contains_key(pid) || self.links.contains_key(pid)
289    }
290
291    /// Get a `PolicyId` that is unoccupied in `self` and `other`.
292    fn get_fresh_id(&self, other: &Self, start_ind: &mut u32) -> PolicyID {
293        let mut new_pid = PolicyID::from_smolstr(format_smolstr!("policy{}", start_ind));
294        *start_ind += 1;
295        while self.policy_id_is_bound(&new_pid) || other.policy_id_is_bound(&new_pid) {
296            new_pid = PolicyID::from_smolstr(format_smolstr!("policy{}", start_ind));
297            *start_ind += 1;
298        }
299        new_pid
300    }
301
302    /// Helper function for `merge_policyset` to construct a renaming
303    /// that would resolve any conflicting `PolicyID`s. We use the type parameter `T`
304    /// to allow this code to be applied to both Templates and Policies.
305    fn update_renaming<T>(
306        &self,
307        this_contents: &LinkedHashMap<PolicyID, T>,
308        other: &Self,
309        other_contents: &LinkedHashMap<PolicyID, T>,
310        renaming: &mut LinkedHashMap<PolicyID, PolicyID>,
311        start_ind: &mut u32,
312    ) where
313        T: PartialEq + Clone,
314    {
315        for (pid, ot) in other_contents {
316            if let Some(tt) = this_contents.get(pid) {
317                if tt != ot && !renaming.contains_key(pid) {
318                    let new_pid = self.get_fresh_id(other, start_ind);
319                    renaming.insert(pid.clone(), new_pid);
320                }
321            }
322        }
323    }
324
325    /// Merges this `PolicySet` with another `PolicySet`.
326    /// This `PolicySet` is modified while the other `PolicySet`
327    /// remains unchanged.
328    ///
329    /// The flag `rename_duplicates` controls the expected behavior
330    /// when a `PolicyID` in this and the other `PolicySet` conflict.
331    ///
332    /// When `rename_duplicates` is false, conflicting `PolicyID`s result
333    /// in a occupied `PolicySetError`.
334    ///
335    /// Otherwise, when `rename_duplicates` is true, conflicting `PolicyID`s from
336    /// the other `PolicySet` are automatically renamed to avoid conflict.
337    /// This renaming is returned as a Hashmap from the old `PolicyID` to the
338    /// renamed `PolicyID`.
339    pub fn merge_policyset(
340        &mut self,
341        other: &PolicySet,
342        rename_duplicates: bool,
343    ) -> Result<LinkedHashMap<PolicyID, PolicyID>, PolicySetError> {
344        // Check for conflicting policy ids. If there is a conflict either
345        // throw an error or construct a renaming (if `rename_duplicates` is true)
346        let mut min_id = 0;
347        let mut renaming = LinkedHashMap::new();
348        self.update_renaming(
349            &self.templates,
350            other,
351            &other.templates,
352            &mut renaming,
353            &mut min_id,
354        );
355        self.update_renaming(&self.links, other, &other.links, &mut renaming, &mut min_id);
356        // We also need to check for any non-static template that collide with a link.
357        // A static template is expected to collide with the single link for that static policy.
358        for (pid, template) in &other.templates {
359            if !template.is_static() && self.get(pid).is_some() && !renaming.contains_key(pid) {
360                let new_pid = self.get_fresh_id(other, &mut min_id);
361                renaming.insert(pid.clone(), new_pid);
362            }
363        }
364        // Similarly for non-static links colliding with templates
365        for (pid, link) in &other.links {
366            if !link.is_static() && self.get_template(pid).is_some() && !renaming.contains_key(pid)
367            {
368                let new_pid = self.get_fresh_id(other, &mut min_id);
369                renaming.insert(pid.clone(), new_pid);
370            }
371        }
372
373        // If `rename_duplicates` is false, then throw an error if any renaming should happen
374        if !rename_duplicates {
375            if let Some(pid) = renaming.keys().next() {
376                return Err(PolicySetError::Occupied { id: pid.clone() });
377            }
378        }
379        // either there are no conflicting policy ids
380        // or we should rename conflicting policy ids (using renaming) to avoid conflicting policy ids
381        for (pid, other_template) in &other.templates {
382            if let Some(new_pid) = renaming.get(pid) {
383                self.templates.insert(
384                    new_pid.clone(),
385                    Arc::new(other_template.new_id(new_pid.clone())),
386                );
387            } else {
388                self.templates.insert(pid.clone(), other_template.clone());
389            }
390        }
391        for (pid, other_policy) in &other.links {
392            // First update this policy's id. We need to do this for static and linked policies.
393            let (new_pid, other_policy) = if let Some(new_pid) = renaming.get(pid) {
394                (new_pid.clone(), other_policy.new_id(new_pid.clone()))
395            } else {
396                (pid.clone(), other_policy.clone())
397            };
398            // Now update the id of the referenced template if this is a link.
399            // If it's static, then we updated this already by updating the
400            // policy's own id.
401            let other_policy = match renaming.get(other_policy.template().id()) {
402                #[expect(
403                    clippy::unwrap_used,
404                    reason = "`if` confirms that `other_policy` is a template link"
405                )]
406                Some(new_tid) if !other_policy.is_static() => {
407                    other_policy.new_template_id(new_tid.clone()).unwrap()
408                }
409                _ => other_policy,
410            };
411            self.links.insert(new_pid, other_policy);
412        }
413        for (tid, other_template_link_set) in &other.template_to_links_map {
414            let tid = renaming.get(tid).unwrap_or(tid);
415            let mut this_template_link_set =
416                self.template_to_links_map.remove(tid).unwrap_or_default();
417            for pid in other_template_link_set {
418                let pid = renaming.get(pid).unwrap_or(pid);
419                this_template_link_set.insert(pid.clone());
420            }
421            self.template_to_links_map
422                .insert(tid.clone(), this_template_link_set);
423        }
424        Ok(renaming)
425    }
426
427    /// Remove a static `Policy`` from the `PolicySet`.
428    pub fn remove_static(
429        &mut self,
430        policy_id: &PolicyID,
431    ) -> Result<Policy, PolicySetPolicyRemovalError> {
432        // Invariant: if `policy_id` is a key in both `self.links` and `self.templates`,
433        // then self.templates[policy_id] has exactly one link: self.links[policy_id]
434        let policy = match self.links.remove(policy_id) {
435            Some(p) => p,
436            None => {
437                return Err(PolicySetPolicyRemovalError::RemovePolicyNoLinkError(
438                    policy_id.clone(),
439                ))
440            }
441        };
442        //links mapped by `PolicyId`, so `policy` is unique
443        match self.templates.remove(policy_id) {
444            Some(_) => {
445                self.template_to_links_map.remove(policy_id);
446                Ok(policy)
447            }
448            None => {
449                //If we removed the link but failed to remove the template
450                //restore the link and return an error
451                self.links.insert(policy_id.clone(), policy);
452                Err(PolicySetPolicyRemovalError::RemovePolicyNoTemplateError(
453                    policy_id.clone(),
454                ))
455            }
456        }
457    }
458
459    /// Add a `StaticPolicy` to the `PolicySet`.
460    pub fn add_static(&mut self, policy: StaticPolicy) -> Result<(), PolicySetError> {
461        let (t, p) = Template::link_static_policy(policy);
462
463        match (
464            self.templates.entry(t.id().clone()),
465            self.links.entry(t.id().clone()),
466        ) {
467            (Entry::Vacant(templates_entry), Entry::Vacant(links_entry)) => {
468                self.template_to_links_map.insert(
469                    t.id().clone(),
470                    vec![p.id().clone()]
471                        .into_iter()
472                        .collect::<LinkedHashSet<PolicyID>>(),
473                );
474                templates_entry.insert(t);
475                links_entry.insert(p);
476                Ok(())
477            }
478            (Entry::Occupied(oentry), _) => Err(PolicySetError::Occupied {
479                id: oentry.key().clone(),
480            }),
481            (_, Entry::Occupied(oentry)) => Err(PolicySetError::Occupied {
482                id: oentry.key().clone(),
483            }),
484        }
485    }
486
487    /// Add a template to the policy set.
488    /// If a link, static policy or template with the same name already exists, this will error.
489    pub fn add_template(&mut self, t: Template) -> Result<(), PolicySetError> {
490        if self.links.contains_key(t.id()) {
491            return Err(PolicySetError::Occupied { id: t.id().clone() });
492        }
493
494        match self.templates.entry(t.id().clone()) {
495            Entry::Occupied(oentry) => Err(PolicySetError::Occupied {
496                id: oentry.key().clone(),
497            }),
498            Entry::Vacant(ventry) => {
499                self.template_to_links_map
500                    .insert(t.id().clone(), LinkedHashSet::new());
501                ventry.insert(Arc::new(t));
502                Ok(())
503            }
504        }
505    }
506
507    /// Remove a template from the policy set.
508    /// This will error if any policy is linked to the template.
509    /// This will error if `policy_id` is not a template.
510    pub fn remove_template(
511        &mut self,
512        policy_id: &PolicyID,
513    ) -> Result<Template, PolicySetTemplateRemovalError> {
514        //A template occurs in templates but not in links.
515        if self.links.contains_key(policy_id) {
516            return Err(PolicySetTemplateRemovalError::NotTemplateError(
517                policy_id.clone(),
518            ));
519        }
520
521        match self.template_to_links_map.get(policy_id) {
522            Some(map) => {
523                if !map.is_empty() {
524                    return Err(PolicySetTemplateRemovalError::RemoveTemplateWithLinksError(
525                        policy_id.clone(),
526                    ));
527                }
528            }
529            None => {
530                return Err(PolicySetTemplateRemovalError::RemovePolicyNoTemplateError(
531                    policy_id.clone(),
532                ))
533            }
534        };
535
536        #[expect(clippy::panic, reason = "every linked policy should have a template")]
537        match self.templates.remove(policy_id) {
538            Some(t) => {
539                self.template_to_links_map.remove(policy_id);
540                Ok(Arc::unwrap_or_clone(t))
541            }
542            None => panic!("Found in template_to_links_map but not in templates"),
543        }
544    }
545
546    /// Get the list of policies linked to `template_id`.
547    /// Returns all p in `links` s.t. `p.template().id() == template_id`
548    pub fn get_linked_policies(
549        &self,
550        template_id: &PolicyID,
551    ) -> Result<impl Iterator<Item = &PolicyID>, PolicySetGetLinksError> {
552        match self.template_to_links_map.get(template_id) {
553            Some(s) => Ok(s.iter()),
554            None => Err(PolicySetGetLinksError::MissingTemplate(template_id.clone())),
555        }
556    }
557
558    /// Attempt to create a new template linked policy and add it to the policy
559    /// set. Returns a references to the new template linked policy if
560    /// successful.
561    ///
562    /// Errors for two reasons
563    ///   1) The the passed SlotEnv either does not match the slots in the templates
564    ///   2) The passed link Id conflicts with an Id already in the set
565    pub fn link(
566        &mut self,
567        template_id: PolicyID,
568        new_id: PolicyID,
569        values: HashMap<SlotId, EntityUID>,
570    ) -> Result<&Policy, LinkingError> {
571        let t =
572            self.get_template_arc(&template_id)
573                .ok_or_else(|| LinkingError::NoSuchTemplate {
574                    id: template_id.clone(),
575                })?;
576        let r = Template::link(t, new_id.clone(), values)?;
577
578        // Both maps must not contain the `new_id`
579        match (
580            self.links.entry(new_id.clone()),
581            self.templates.entry(new_id.clone()),
582        ) {
583            (Entry::Vacant(links_entry), Entry::Vacant(_)) => {
584                //We will never use the .or_default() because we just found `t` above
585                self.template_to_links_map
586                    .entry(template_id)
587                    .or_default()
588                    .insert(new_id);
589                Ok(links_entry.insert(r))
590            }
591            (Entry::Occupied(oentry), _) => Err(LinkingError::PolicyIdConflict {
592                id: oentry.key().clone(),
593            }),
594            (_, Entry::Occupied(oentry)) => Err(LinkingError::PolicyIdConflict {
595                id: oentry.key().clone(),
596            }),
597        }
598    }
599
600    /// Unlink `policy_id`
601    /// If it is not a link this will error
602    pub fn unlink(&mut self, policy_id: &PolicyID) -> Result<Policy, PolicySetUnlinkError> {
603        //A link occurs in links but not in templates.
604        if self.templates.contains_key(policy_id) {
605            return Err(PolicySetUnlinkError::NotLinkError(policy_id.clone()));
606        }
607        match self.links.remove(policy_id) {
608            Some(p) => {
609                #[expect(clippy::panic, reason = "every linked policy should have a template")]
610                match self.template_to_links_map.entry(p.template().id().clone()) {
611                    Entry::Occupied(t) => t.into_mut().remove(policy_id),
612                    Entry::Vacant(_) => {
613                        panic!("No template found for linked policy")
614                    }
615                };
616                Ok(p)
617            }
618            None => Err(PolicySetUnlinkError::UnlinkingError(policy_id.clone())),
619        }
620    }
621
622    /// Iterate over all policies
623    pub fn policies(&self) -> impl Iterator<Item = &Policy> {
624        self.links.values()
625    }
626
627    /// Consume the `PolicySet`, producing an iterator of all the policies in it
628    pub fn into_policies(self) -> impl Iterator<Item = Policy> {
629        self.links.into_iter().map(|(_, p)| p)
630    }
631
632    /// Iterate over everything stored as template, including static policies.
633    /// Ie: all_templates() should equal templates() ++ static_policies().map(|p| p.template())
634    pub fn all_templates(&self) -> impl Iterator<Item = &Template> {
635        self.templates.values().map(|t| t.borrow())
636    }
637
638    /// Iterate over templates with slots
639    pub fn templates(&self) -> impl Iterator<Item = &Template> {
640        self.all_templates().filter(|t| t.slots().count() != 0)
641    }
642
643    /// Iterate over all of the static policies.
644    pub fn static_policies(&self) -> impl Iterator<Item = &Policy> {
645        self.policies().filter(|p| p.is_static())
646    }
647
648    /// Returns true iff the `PolicySet` is empty
649    pub fn is_empty(&self) -> bool {
650        self.templates.is_empty() && self.links.is_empty()
651    }
652
653    /// Lookup a template by policy id, returns [`Option<Arc<Template>>`]
654    pub fn get_template_arc(&self, id: &PolicyID) -> Option<Arc<Template>> {
655        self.templates.get(id).cloned()
656    }
657
658    /// Lookup a template by policy id, returns [`Option<&Template>`]
659    pub fn get_template(&self, id: &PolicyID) -> Option<&Template> {
660        self.templates.get(id).map(AsRef::as_ref)
661    }
662
663    /// Lookup an policy by policy id
664    pub fn get(&self, id: &PolicyID) -> Option<&Policy> {
665        self.links.get(id)
666    }
667
668    /// Attempt to collect an iterator over policies into a PolicySet
669    pub fn try_from_iter<T: IntoIterator<Item = Policy>>(iter: T) -> Result<Self, PolicySetError> {
670        let mut set = Self::new();
671        for p in iter {
672            set.add(p)?;
673        }
674        Ok(set)
675    }
676}
677
678impl std::fmt::Display for PolicySet {
679    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
680        // we don't show the ID, because the Display impl for Policy itself shows the ID
681        if self.is_empty() {
682            write!(f, "<empty policyset>")
683        } else {
684            write!(
685                f,
686                "Templates:\n{}, Template Linked Policies:\n{}",
687                self.all_templates().join("\n"),
688                self.policies().join("\n")
689            )
690        }
691    }
692}
693
694#[expect(clippy::panic, clippy::indexing_slicing, reason = "tests")]
695#[cfg(test)]
696mod test {
697    use super::*;
698    use crate::{
699        ast::{
700            annotation::Annotations, ActionConstraint, Effect, PrincipalConstraint,
701            ResourceConstraint,
702        },
703        parser,
704    };
705
706    use similar_asserts::assert_eq;
707    use std::collections::HashMap;
708
709    #[test]
710    fn link_conflicts() {
711        let mut pset = PolicySet::new();
712        let p1 = parser::parse_policy(
713            Some(PolicyID::from_string("id")),
714            "permit(principal,action,resource);",
715        )
716        .expect("Failed to parse");
717        pset.add_static(p1).expect("Failed to add!");
718        let template = parser::parse_policy_or_template(
719            Some(PolicyID::from_string("t")),
720            "permit(principal == ?principal, action, resource);",
721        )
722        .expect("Failed to parse");
723        pset.add_template(template).expect("Add failed");
724
725        let env: HashMap<SlotId, EntityUID> = HashMap::from([(
726            SlotId::principal(),
727            r#"Test::"test""#.parse().expect("Failed to parse"),
728        )]);
729
730        let r = pset.link(PolicyID::from_string("t"), PolicyID::from_string("id"), env);
731
732        match r {
733            Ok(_) => panic!("Should have failed due to conflict"),
734            Err(LinkingError::PolicyIdConflict { id }) => {
735                assert_eq!(id, PolicyID::from_string("id"))
736            }
737            Err(e) => panic!("Incorrect error: {e}"),
738        };
739    }
740
741    /// This test focuses on `PolicySet::add()`, while other tests mostly use
742    /// `PolicySet::add_static()` and `PolicySet::link()`.
743    #[test]
744    fn policyset_add() {
745        let mut pset = PolicySet::new();
746        let static_policy = parser::parse_policy(
747            Some(PolicyID::from_string("id")),
748            "permit(principal,action,resource);",
749        )
750        .expect("Failed to parse");
751        let static_policy: Policy = static_policy.into();
752        pset.add(static_policy)
753            .expect("Adding static policy in Policy form should succeed");
754
755        let template = Arc::new(
756            parser::parse_policy_or_template(
757                Some(PolicyID::from_string("t")),
758                "permit(principal == ?principal, action, resource);",
759            )
760            .expect("Failed to parse"),
761        );
762        let env1: HashMap<SlotId, EntityUID> = HashMap::from([(
763            SlotId::principal(),
764            r#"Test::"test1""#.parse().expect("Failed to parse"),
765        )]);
766
767        let p1 = Template::link(Arc::clone(&template), PolicyID::from_string("link"), env1)
768            .expect("Failed to link");
769        pset.add(p1).expect(
770            "Adding link should succeed, even though the template wasn't previously in the pset",
771        );
772        assert!(
773            pset.get_template_arc(&PolicyID::from_string("t")).is_some(),
774            "Adding link should implicitly add the template"
775        );
776
777        let env2: HashMap<SlotId, EntityUID> = HashMap::from([(
778            SlotId::principal(),
779            r#"Test::"test2""#.parse().expect("Failed to parse"),
780        )]);
781
782        let p2 = Template::link(
783            Arc::clone(&template),
784            PolicyID::from_string("link"),
785            env2.clone(),
786        )
787        .expect("Failed to link");
788        match pset.add(p2) {
789            Ok(_) => panic!("Should have failed due to conflict with existing link id"),
790            Err(PolicySetError::Occupied { id }) => assert_eq!(id, PolicyID::from_string("link")),
791        }
792
793        let p3 = Template::link(Arc::clone(&template), PolicyID::from_string("link2"), env2)
794            .expect("Failed to link");
795        pset.add(p3).expect(
796            "Adding link should succeed, even though the template already existed in the pset",
797        );
798
799        let template2 = Arc::new(
800            parser::parse_policy_or_template(
801                Some(PolicyID::from_string("t")),
802                "forbid(principal, action, resource == ?resource);",
803            )
804            .expect("Failed to parse"),
805        );
806        let env3: HashMap<SlotId, EntityUID> = HashMap::from([(
807            SlotId::resource(),
808            r#"Test::"test3""#.parse().expect("Failed to parse"),
809        )]);
810
811        let p4 = Template::link(
812            Arc::clone(&template2),
813            PolicyID::from_string("unique3"),
814            env3,
815        )
816        .expect("Failed to link");
817        match pset.add(p4) {
818            Ok(_) => panic!("Should have failed due to conflict on template id"),
819            Err(PolicySetError::Occupied { id }) => {
820                assert_eq!(id, PolicyID::from_string("t"))
821            }
822        }
823    }
824
825    #[test]
826    fn policy_set_singleton_static() {
827        let policy: Policy = parser::parse_policy(
828            Some(PolicyID::from_string("id")),
829            "permit(principal,action,resource);",
830        )
831        .unwrap()
832        .into();
833
834        let pset_singleton = PolicySet::singleton(policy.clone());
835        let mut pset_add = PolicySet::new();
836        pset_add.add(policy).unwrap();
837        assert_eq!(pset_singleton, pset_add);
838    }
839
840    #[test]
841    fn policy_set_singleton_link() {
842        let template = Arc::new(
843            parser::parse_policy_or_template(
844                Some(PolicyID::from_string("t")),
845                "permit(principal == ?principal, action, resource);",
846            )
847            .expect("Failed to parse"),
848        );
849        let env1 = HashMap::from([(
850            SlotId::principal(),
851            r#"Test::"test1""#.parse().expect("Failed to parse"),
852        )]);
853        let policy =
854            Template::link(template, PolicyID::from_string("link"), env1).expect("Failed to link");
855
856        let pset_singleton = PolicySet::singleton(policy.clone());
857        let mut pset_add = PolicySet::new();
858        pset_add.add(policy).unwrap();
859        assert_eq!(pset_singleton, pset_add);
860    }
861
862    #[test]
863    fn policy_merge_no_conflicts() {
864        let p1 = parser::parse_policy(
865            Some(PolicyID::from_string("policy0")),
866            "permit(principal,action,resource);",
867        )
868        .expect("Failed to parse");
869        let p2 = parser::parse_policy(
870            Some(PolicyID::from_string("policy1")),
871            "permit(principal,action,resource) when { false };",
872        )
873        .expect("Failed to parse");
874        let p3 = parser::parse_policy(
875            Some(PolicyID::from_string("policy0")),
876            "permit(principal,action,resource);",
877        )
878        .expect("Failed to parse");
879        let p4 = parser::parse_policy(
880            Some(PolicyID::from_string("policy2")),
881            "permit(principal,action,resource) when { true };",
882        )
883        .expect("Failed to parse");
884        let mut pset1 = PolicySet::new();
885        let mut pset2 = PolicySet::new();
886        pset1.add_static(p1).expect("Failed to add!");
887        pset1.add_static(p2).expect("Failed to add!");
888        pset2.add_static(p3).expect("Failed to add!");
889        pset2.add_static(p4).expect("Failed to add!");
890        // should not conflict because p1 == p3
891        match pset1.merge_policyset(&pset2, false) {
892            Ok(_) => (),
893            Err(PolicySetError::Occupied { id }) => {
894                panic!("There should not have been an error! Unexpected conflict for id {id}")
895            }
896        }
897    }
898
899    #[test]
900    fn policy_merge_with_conflicts() {
901        let pid0 = PolicyID::from_string("policy0");
902        let pid1 = PolicyID::from_string("policy1");
903        let pid2 = PolicyID::from_string("policy2");
904        let p1 = parser::parse_policy(Some(pid0.clone()), "permit(principal,action,resource);")
905            .expect("Failed to parse");
906        let p2 = parser::parse_policy(
907            Some(pid1.clone()),
908            "permit(principal,action,resource) when { false };",
909        )
910        .expect("Failed to parse");
911        let p3 = parser::parse_policy(Some(pid1.clone()), "permit(principal,action,resource);")
912            .expect("Failed to parse");
913        let p4 = parser::parse_policy(
914            Some(pid2.clone()),
915            "permit(principal,action,resource) when { true };",
916        )
917        .expect("Failed to parse");
918        let mut pset1 = PolicySet::new();
919        let mut pset2 = PolicySet::new();
920        pset1.add_static(p1.clone()).expect("Failed to add!");
921        pset1.add_static(p2.clone()).expect("Failed to add!");
922        pset2.add_static(p3.clone()).expect("Failed to add!");
923        pset2.add_static(p4.clone()).expect("Failed to add!");
924        // should conclict on pid "policy1"
925        match pset1.merge_policyset(&pset2, false) {
926            Ok(_) => panic!("`pset1` and `pset2` should conflict for PolicyID `policy1`"),
927            Err(PolicySetError::Occupied { id }) => {
928                assert_eq!(id, PolicyID::from_string("policy1"));
929            }
930        }
931        // should not conflict because of auto-renaming of conflicting policies
932        match pset1.merge_policyset(&pset2, true) {
933            Ok(renaming) => {
934                // ensure `policy1` was renamed
935                let new_pid1 = match renaming.get(&pid1) {
936                    Some(new_pid1) => new_pid1,
937                    None => panic!("Error: `policy1` is a conflict and should be renamed"),
938                };
939                // ensure no other policy was renamed
940                assert_eq!(renaming.keys().len(), 1);
941                if let Some(new_p1) = pset1.get(&pid0) {
942                    assert_eq!(Policy::from(p1), new_p1.clone());
943                }
944                if let Some(new_p2) = pset1.get(&pid1) {
945                    assert_eq!(Policy::from(p2), new_p2.clone());
946                }
947                if let Some(new_p3) = pset1.get(new_pid1) {
948                    assert_eq!(Policy::from(p3).new_id(new_pid1.clone()), new_p3.clone());
949                }
950                if let Some(new_p4) = pset1.get(&pid2) {
951                    assert_eq!(Policy::from(p4), new_p4.clone());
952                }
953            }
954            Err(PolicySetError::Occupied { id }) => {
955                panic!("There should not have been an error! Unexpected conflict for id {id}")
956            }
957        }
958    }
959
960    #[test]
961    fn policy_conflicts() {
962        let mut pset = PolicySet::new();
963        let p1 = parser::parse_policy(
964            Some(PolicyID::from_string("id")),
965            "permit(principal,action,resource);",
966        )
967        .expect("Failed to parse");
968        let p2 = parser::parse_policy(
969            Some(PolicyID::from_string("id")),
970            "permit(principal,action,resource) when { false };",
971        )
972        .expect("Failed to parse");
973        pset.add_static(p1).expect("Failed to add!");
974        match pset.add_static(p2) {
975            Ok(_) => panic!("Should have failed to due name conflict"),
976            Err(PolicySetError::Occupied { id }) => assert_eq!(id, PolicyID::from_string("id")),
977        }
978    }
979
980    #[test]
981    fn template_filtering() {
982        let template = parser::parse_policy_or_template(
983            Some(PolicyID::from_string("template")),
984            "permit(principal == ?principal, action, resource);",
985        )
986        .expect("Template Parse Failure");
987        let static_policy = parser::parse_policy(
988            Some(PolicyID::from_string("static")),
989            "permit(principal, action, resource);",
990        )
991        .expect("Static parse failure");
992        let mut set = PolicySet::new();
993        set.add_template(template).unwrap();
994        set.add_static(static_policy).unwrap();
995
996        assert_eq!(set.all_templates().count(), 2);
997        assert_eq!(set.templates().count(), 1);
998        assert_eq!(set.static_policies().count(), 1);
999        assert_eq!(set.policies().count(), 1);
1000        set.link(
1001            PolicyID::from_string("template"),
1002            PolicyID::from_string("id"),
1003            HashMap::from([(SlotId::principal(), EntityUID::with_eid("eid"))]),
1004        )
1005        .expect("Linking failed!");
1006        assert_eq!(set.static_policies().count(), 1);
1007        assert_eq!(set.policies().count(), 2);
1008    }
1009
1010    #[test]
1011    fn linking_missing_template() {
1012        let tid = PolicyID::from_string("template");
1013        let lid = PolicyID::from_string("link");
1014        let t = Template::new(
1015            tid.clone(),
1016            None,
1017            Annotations::new(),
1018            Effect::Permit,
1019            PrincipalConstraint::any(),
1020            ActionConstraint::any(),
1021            ResourceConstraint::any(),
1022            None,
1023        );
1024
1025        let mut s = PolicySet::new();
1026        let e = s
1027            .link(tid.clone(), lid.clone(), HashMap::new())
1028            .expect_err("Should fail");
1029
1030        match e {
1031            LinkingError::NoSuchTemplate { id } => assert_eq!(tid, id),
1032            e => panic!("Wrong error {e}"),
1033        };
1034
1035        s.add_template(t).unwrap();
1036        s.link(tid, lid, HashMap::new()).expect("Should succeed");
1037    }
1038
1039    #[test]
1040    fn linkinv_valid_link() {
1041        let tid = PolicyID::from_string("template");
1042        let lid = PolicyID::from_string("link");
1043        let t = Template::new(
1044            tid.clone(),
1045            None,
1046            Annotations::new(),
1047            Effect::Permit,
1048            PrincipalConstraint::is_eq_slot(),
1049            ActionConstraint::any(),
1050            ResourceConstraint::is_in_slot(),
1051            None,
1052        );
1053
1054        let mut s = PolicySet::new();
1055        s.add_template(t).unwrap();
1056
1057        let mut vals = HashMap::new();
1058        vals.insert(SlotId::principal(), EntityUID::with_eid("p"));
1059        vals.insert(SlotId::resource(), EntityUID::with_eid("a"));
1060
1061        s.link(tid.clone(), lid.clone(), vals).expect("Should link");
1062
1063        let v: Vec<_> = s.policies().collect();
1064
1065        assert_eq!(v[0].id(), &lid);
1066        assert_eq!(v[0].template().id(), &tid);
1067    }
1068
1069    #[test]
1070    fn linking_empty_set() {
1071        let s = PolicySet::new();
1072        assert_eq!(s.policies().count(), 0);
1073    }
1074
1075    #[test]
1076    fn linking_raw_policy() {
1077        let mut s = PolicySet::new();
1078        let id = PolicyID::from_string("id");
1079        let p = StaticPolicy::new(
1080            id.clone(),
1081            None,
1082            Annotations::new(),
1083            Effect::Forbid,
1084            PrincipalConstraint::any(),
1085            ActionConstraint::any(),
1086            ResourceConstraint::any(),
1087            None,
1088        )
1089        .expect("Policy Creation Failed");
1090        s.add_static(p).unwrap();
1091
1092        let mut iter = s.policies();
1093        match iter.next() {
1094            Some(pol) => {
1095                assert_eq!(pol.id(), &id);
1096                assert_eq!(pol.effect(), Effect::Forbid);
1097                assert!(pol.env().is_empty())
1098            }
1099            None => panic!("Linked Record Not Present"),
1100        };
1101    }
1102
1103    #[test]
1104    fn link_slotmap() {
1105        let mut s = PolicySet::new();
1106        let template_id = PolicyID::from_string("template");
1107        let link_id = PolicyID::from_string("link");
1108        let t = Template::new(
1109            template_id.clone(),
1110            None,
1111            Annotations::new(),
1112            Effect::Forbid,
1113            PrincipalConstraint::is_eq_slot(),
1114            ActionConstraint::any(),
1115            ResourceConstraint::any(),
1116            None,
1117        );
1118        s.add_template(t).unwrap();
1119
1120        let mut v = HashMap::new();
1121        let entity = EntityUID::with_eid("eid");
1122        v.insert(SlotId::principal(), entity.clone());
1123        s.link(template_id.clone(), link_id.clone(), v)
1124            .expect("Linking failed!");
1125
1126        let link = s.get(&link_id).expect("Link should exist");
1127        assert_eq!(&link_id, link.id());
1128        assert_eq!(&template_id, link.template().id());
1129        assert_eq!(
1130            &entity,
1131            link.env()
1132                .get(&SlotId::principal())
1133                .expect("Mapping was incorrect")
1134        );
1135    }
1136
1137    #[test]
1138    fn policy_sets() {
1139        let mut pset = PolicySet::new();
1140        assert!(pset.is_empty());
1141        let id1 = PolicyID::from_string("id1");
1142        let tid1 = PolicyID::from_string("template");
1143        let policy1 = StaticPolicy::new(
1144            id1.clone(),
1145            None,
1146            Annotations::new(),
1147            Effect::Permit,
1148            PrincipalConstraint::any(),
1149            ActionConstraint::any(),
1150            ResourceConstraint::any(),
1151            None,
1152        )
1153        .expect("Policy Creation Failed");
1154        let template1 = Template::new(
1155            tid1.clone(),
1156            None,
1157            Annotations::new(),
1158            Effect::Permit,
1159            PrincipalConstraint::any(),
1160            ActionConstraint::any(),
1161            ResourceConstraint::any(),
1162            None,
1163        );
1164        let added = pset.add_static(policy1.clone()).is_ok();
1165        assert!(added);
1166        let added = pset.add_static(policy1).is_ok();
1167        assert!(!added);
1168        let added = pset.add_template(template1.clone()).is_ok();
1169        assert!(added);
1170        let added = pset.add_template(template1).is_ok();
1171        assert!(!added);
1172        assert!(!pset.is_empty());
1173        let id2 = PolicyID::from_string("id2");
1174        let policy2 = StaticPolicy::new(
1175            id2.clone(),
1176            None,
1177            Annotations::new(),
1178            Effect::Forbid,
1179            PrincipalConstraint::is_eq(Arc::new(EntityUID::with_eid("jane"))),
1180            ActionConstraint::any(),
1181            ResourceConstraint::any(),
1182            None,
1183        )
1184        .expect("Policy Creation Failed");
1185        let added = pset.add_static(policy2).is_ok();
1186        assert!(added);
1187
1188        let tid2 = PolicyID::from_string("template2");
1189        let template2 = Template::new(
1190            tid2.clone(),
1191            None,
1192            Annotations::new(),
1193            Effect::Permit,
1194            PrincipalConstraint::is_eq_slot(),
1195            ActionConstraint::any(),
1196            ResourceConstraint::any(),
1197            None,
1198        );
1199        let id3 = PolicyID::from_string("link");
1200        let added = pset.add_template(template2).is_ok();
1201        assert!(added);
1202
1203        let r = pset.link(
1204            tid2.clone(),
1205            id3.clone(),
1206            HashMap::from([(SlotId::principal(), EntityUID::with_eid("example"))]),
1207        );
1208        r.expect("Linking failed");
1209
1210        assert_eq!(pset.get(&id1).expect("should find the policy").id(), &id1);
1211        assert_eq!(pset.get(&id2).expect("should find the policy").id(), &id2);
1212        assert_eq!(pset.get(&id3).expect("should find link").id(), &id3);
1213        assert_eq!(
1214            pset.get(&id3).expect("should find link").template().id(),
1215            &tid2
1216        );
1217        assert!(pset.get(&tid2).is_none());
1218        assert!(pset.get_template_arc(&id1).is_some()); // Static policies are also templates
1219        assert!(pset.get_template_arc(&id2).is_some()); // Static policies are also templates
1220        assert!(pset.get_template_arc(&tid2).is_some());
1221        assert_eq!(pset.policies().count(), 3);
1222
1223        assert_eq!(
1224            pset.get_template_arc(&tid1)
1225                .expect("should find the template")
1226                .id(),
1227            &tid1
1228        );
1229        assert!(pset.get(&tid1).is_none());
1230        assert_eq!(pset.all_templates().count(), 4);
1231    }
1232
1233    #[test]
1234    fn policy_set_insertion_order() {
1235        let mut pset = PolicySet::new();
1236        assert!(pset.is_empty());
1237
1238        let src = "permit(principal, action, resource);";
1239        let ids: Vec<PolicyID> = (1..=4)
1240            .map(|i| {
1241                let id = PolicyID::from_string(format!("id{i}"));
1242                let p = parser::parse_policy(Some(id.clone()), src).unwrap();
1243                let added = pset.add(p.into()).is_ok();
1244                assert!(added);
1245                id
1246            })
1247            .collect();
1248
1249        assert_eq!(
1250            pset.into_policies()
1251                .map(|p| p.id().clone())
1252                .collect::<Vec<PolicyID>>(),
1253            ids
1254        );
1255    }
1256}