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    /// Add a `Policy` to the `PolicySet`.
219    pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
220        let t = policy.template_arc();
221
222        // we need to check for all possible errors before making any
223        // modifications to `self`.
224        // So we just collect the `ventry` here, and we only do the insertion
225        // once we know there will be no error
226        let template_ventry = match self.templates.entry(t.id().clone()) {
227            Entry::Vacant(ventry) => Some(ventry),
228            Entry::Occupied(oentry) => {
229                if oentry.get() != &t {
230                    return Err(PolicySetError::Occupied {
231                        id: oentry.key().clone(),
232                    });
233                }
234                None
235            }
236        };
237
238        let link_ventry = match self.links.entry(policy.id().clone()) {
239            Entry::Vacant(ventry) => Some(ventry),
240            Entry::Occupied(oentry) => {
241                return Err(PolicySetError::Occupied {
242                    id: oentry.key().clone(),
243                });
244            }
245        };
246
247        // if we get here, there will be no errors.  So actually do the
248        // insertions.
249        if let Some(ventry) = template_ventry {
250            self.template_to_links_map.insert(
251                t.id().clone(),
252                vec![policy.id().clone()]
253                    .into_iter()
254                    .collect::<LinkedHashSet<PolicyID>>(),
255            );
256            ventry.insert(t);
257        } else {
258            //`template_ventry` is None, so `templates` has `t` and we never use the `HashSet::new()`
259            self.template_to_links_map
260                .entry(t.id().clone())
261                .or_default()
262                .insert(policy.id().clone());
263        }
264        if let Some(ventry) = link_ventry {
265            ventry.insert(policy);
266        }
267
268        Ok(())
269    }
270
271    /// Helper function for `merge_policyset` to check if the `PolicyID` pid
272    /// appears in this `PolicySet`'s links or templates.
273    fn policy_id_is_bound(&self, pid: &PolicyID) -> bool {
274        self.templates.contains_key(pid) || self.links.contains_key(pid)
275    }
276
277    /// Get a `PolicyId` that is unoccupied in `self` and `other`.
278    fn get_fresh_id(&self, other: &Self, start_ind: &mut u32) -> PolicyID {
279        let mut new_pid = PolicyID::from_smolstr(format_smolstr!("policy{}", start_ind));
280        *start_ind += 1;
281        while self.policy_id_is_bound(&new_pid) || other.policy_id_is_bound(&new_pid) {
282            new_pid = PolicyID::from_smolstr(format_smolstr!("policy{}", start_ind));
283            *start_ind += 1;
284        }
285        new_pid
286    }
287
288    /// Helper function for `merge_policyset` to construct a renaming
289    /// that would resolve any conflicting `PolicyID`s. We use the type parameter `T`
290    /// to allow this code to be applied to both Templates and Policies.
291    fn update_renaming<T>(
292        &self,
293        this_contents: &LinkedHashMap<PolicyID, T>,
294        other: &Self,
295        other_contents: &LinkedHashMap<PolicyID, T>,
296        renaming: &mut LinkedHashMap<PolicyID, PolicyID>,
297        start_ind: &mut u32,
298    ) where
299        T: PartialEq + Clone,
300    {
301        for (pid, ot) in other_contents {
302            if let Some(tt) = this_contents.get(pid) {
303                if tt != ot && !renaming.contains_key(pid) {
304                    let new_pid = self.get_fresh_id(other, start_ind);
305                    renaming.insert(pid.clone(), new_pid);
306                }
307            }
308        }
309    }
310
311    /// Merges this `PolicySet` with another `PolicySet`.
312    /// This `PolicySet` is modified while the other `PolicySet`
313    /// remains unchanged.
314    ///
315    /// The flag `rename_duplicates` controls the expected behavior
316    /// when a `PolicyID` in this and the other `PolicySet` conflict.
317    ///
318    /// When `rename_duplicates` is false, conflicting `PolicyID`s result
319    /// in a occupied `PolicySetError`.
320    ///
321    /// Otherwise, when `rename_duplicates` is true, conflicting `PolicyID`s from
322    /// the other `PolicySet` are automatically renamed to avoid conflict.
323    /// This renaming is returned as a Hashmap from the old `PolicyID` to the
324    /// renamed `PolicyID`.
325    pub fn merge_policyset(
326        &mut self,
327        other: &PolicySet,
328        rename_duplicates: bool,
329    ) -> Result<LinkedHashMap<PolicyID, PolicyID>, PolicySetError> {
330        // Check for conflicting policy ids. If there is a conflict either
331        // throw an error or construct a renaming (if `rename_duplicates` is true)
332        let mut min_id = 0;
333        let mut renaming = LinkedHashMap::new();
334        self.update_renaming(
335            &self.templates,
336            other,
337            &other.templates,
338            &mut renaming,
339            &mut min_id,
340        );
341        self.update_renaming(&self.links, other, &other.links, &mut renaming, &mut min_id);
342        // We also need to check for any non-static template that collide with a link.
343        // A static template is expected to collide with the single link for that static policy.
344        for (pid, template) in &other.templates {
345            if !template.is_static() && self.get(pid).is_some() && !renaming.contains_key(pid) {
346                let new_pid = self.get_fresh_id(other, &mut min_id);
347                renaming.insert(pid.clone(), new_pid);
348            }
349        }
350        // Similarly for non-static links colliding with templates
351        for (pid, link) in &other.links {
352            if !link.is_static() && self.get_template(pid).is_some() && !renaming.contains_key(pid)
353            {
354                let new_pid = self.get_fresh_id(other, &mut min_id);
355                renaming.insert(pid.clone(), new_pid);
356            }
357        }
358
359        // If `rename_duplicates` is false, then throw an error if any renaming should happen
360        if !rename_duplicates {
361            if let Some(pid) = renaming.keys().next() {
362                return Err(PolicySetError::Occupied { id: pid.clone() });
363            }
364        }
365        // either there are no conflicting policy ids
366        // or we should rename conflicting policy ids (using renaming) to avoid conflicting policy ids
367        for (pid, other_template) in &other.templates {
368            if let Some(new_pid) = renaming.get(pid) {
369                self.templates.insert(
370                    new_pid.clone(),
371                    Arc::new(other_template.new_id(new_pid.clone())),
372                );
373            } else {
374                self.templates.insert(pid.clone(), other_template.clone());
375            }
376        }
377        for (pid, other_policy) in &other.links {
378            // First update this policy's id. We need to do this for static and linked policies.
379            let (new_pid, other_policy) = if let Some(new_pid) = renaming.get(pid) {
380                (new_pid.clone(), other_policy.new_id(new_pid.clone()))
381            } else {
382                (pid.clone(), other_policy.clone())
383            };
384            // Now update the id of the referenced template if this is a link.
385            // If it's static, then we updated this already by updating the
386            // policy's own id.
387            let other_policy = match renaming.get(other_policy.template().id()) {
388                // PANIC SAFETY: `if` confirms that `other_policy` is a template link
389                #[allow(clippy::unwrap_used)]
390                Some(new_tid) if !other_policy.is_static() => {
391                    other_policy.new_template_id(new_tid.clone()).unwrap()
392                }
393                _ => other_policy,
394            };
395            self.links.insert(new_pid, other_policy);
396        }
397        for (tid, other_template_link_set) in &other.template_to_links_map {
398            let tid = renaming.get(tid).unwrap_or(tid);
399            let mut this_template_link_set =
400                self.template_to_links_map.remove(tid).unwrap_or_default();
401            for pid in other_template_link_set {
402                let pid = renaming.get(pid).unwrap_or(pid);
403                this_template_link_set.insert(pid.clone());
404            }
405            self.template_to_links_map
406                .insert(tid.clone(), this_template_link_set);
407        }
408        Ok(renaming)
409    }
410
411    /// Remove a static `Policy`` from the `PolicySet`.
412    pub fn remove_static(
413        &mut self,
414        policy_id: &PolicyID,
415    ) -> Result<Policy, PolicySetPolicyRemovalError> {
416        // Invariant: if `policy_id` is a key in both `self.links` and `self.templates`,
417        // then self.templates[policy_id] has exactly one link: self.links[policy_id]
418        let policy = match self.links.remove(policy_id) {
419            Some(p) => p,
420            None => {
421                return Err(PolicySetPolicyRemovalError::RemovePolicyNoLinkError(
422                    policy_id.clone(),
423                ))
424            }
425        };
426        //links mapped by `PolicyId`, so `policy` is unique
427        match self.templates.remove(policy_id) {
428            Some(_) => {
429                self.template_to_links_map.remove(policy_id);
430                Ok(policy)
431            }
432            None => {
433                //If we removed the link but failed to remove the template
434                //restore the link and return an error
435                self.links.insert(policy_id.clone(), policy);
436                Err(PolicySetPolicyRemovalError::RemovePolicyNoTemplateError(
437                    policy_id.clone(),
438                ))
439            }
440        }
441    }
442
443    /// Add a `StaticPolicy` to the `PolicySet`.
444    pub fn add_static(&mut self, policy: StaticPolicy) -> Result<(), PolicySetError> {
445        let (t, p) = Template::link_static_policy(policy);
446
447        match (
448            self.templates.entry(t.id().clone()),
449            self.links.entry(t.id().clone()),
450        ) {
451            (Entry::Vacant(templates_entry), Entry::Vacant(links_entry)) => {
452                self.template_to_links_map.insert(
453                    t.id().clone(),
454                    vec![p.id().clone()]
455                        .into_iter()
456                        .collect::<LinkedHashSet<PolicyID>>(),
457                );
458                templates_entry.insert(t);
459                links_entry.insert(p);
460                Ok(())
461            }
462            (Entry::Occupied(oentry), _) => Err(PolicySetError::Occupied {
463                id: oentry.key().clone(),
464            }),
465            (_, Entry::Occupied(oentry)) => Err(PolicySetError::Occupied {
466                id: oentry.key().clone(),
467            }),
468        }
469    }
470
471    /// Add a template to the policy set.
472    /// If a link, static policy or template with the same name already exists, this will error.
473    pub fn add_template(&mut self, t: Template) -> Result<(), PolicySetError> {
474        if self.links.contains_key(t.id()) {
475            return Err(PolicySetError::Occupied { id: t.id().clone() });
476        }
477
478        match self.templates.entry(t.id().clone()) {
479            Entry::Occupied(oentry) => Err(PolicySetError::Occupied {
480                id: oentry.key().clone(),
481            }),
482            Entry::Vacant(ventry) => {
483                self.template_to_links_map
484                    .insert(t.id().clone(), LinkedHashSet::new());
485                ventry.insert(Arc::new(t));
486                Ok(())
487            }
488        }
489    }
490
491    /// Remove a template from the policy set.
492    /// This will error if any policy is linked to the template.
493    /// This will error if `policy_id` is not a template.
494    pub fn remove_template(
495        &mut self,
496        policy_id: &PolicyID,
497    ) -> Result<Template, PolicySetTemplateRemovalError> {
498        //A template occurs in templates but not in links.
499        if self.links.contains_key(policy_id) {
500            return Err(PolicySetTemplateRemovalError::NotTemplateError(
501                policy_id.clone(),
502            ));
503        }
504
505        match self.template_to_links_map.get(policy_id) {
506            Some(map) => {
507                if !map.is_empty() {
508                    return Err(PolicySetTemplateRemovalError::RemoveTemplateWithLinksError(
509                        policy_id.clone(),
510                    ));
511                }
512            }
513            None => {
514                return Err(PolicySetTemplateRemovalError::RemovePolicyNoTemplateError(
515                    policy_id.clone(),
516                ))
517            }
518        };
519
520        // PANIC SAFETY: every linked policy should have a template
521        #[allow(clippy::panic)]
522        match self.templates.remove(policy_id) {
523            Some(t) => {
524                self.template_to_links_map.remove(policy_id);
525                Ok(Arc::unwrap_or_clone(t))
526            }
527            None => panic!("Found in template_to_links_map but not in templates"),
528        }
529    }
530
531    /// Get the list of policies linked to `template_id`.
532    /// Returns all p in `links` s.t. `p.template().id() == template_id`
533    pub fn get_linked_policies(
534        &self,
535        template_id: &PolicyID,
536    ) -> Result<impl Iterator<Item = &PolicyID>, PolicySetGetLinksError> {
537        match self.template_to_links_map.get(template_id) {
538            Some(s) => Ok(s.iter()),
539            None => Err(PolicySetGetLinksError::MissingTemplate(template_id.clone())),
540        }
541    }
542
543    /// Attempt to create a new template linked policy and add it to the policy
544    /// set. Returns a references to the new template linked policy if
545    /// successful.
546    ///
547    /// Errors for two reasons
548    ///   1) The the passed SlotEnv either does not match the slots in the templates
549    ///   2) The passed link Id conflicts with an Id already in the set
550    pub fn link(
551        &mut self,
552        template_id: PolicyID,
553        new_id: PolicyID,
554        values: HashMap<SlotId, EntityUID>,
555    ) -> Result<&Policy, LinkingError> {
556        let t =
557            self.get_template_arc(&template_id)
558                .ok_or_else(|| LinkingError::NoSuchTemplate {
559                    id: template_id.clone(),
560                })?;
561        let r = Template::link(t, new_id.clone(), values)?;
562
563        // Both maps must not contain the `new_id`
564        match (
565            self.links.entry(new_id.clone()),
566            self.templates.entry(new_id.clone()),
567        ) {
568            (Entry::Vacant(links_entry), Entry::Vacant(_)) => {
569                //We will never use the .or_default() because we just found `t` above
570                self.template_to_links_map
571                    .entry(template_id)
572                    .or_default()
573                    .insert(new_id);
574                Ok(links_entry.insert(r))
575            }
576            (Entry::Occupied(oentry), _) => Err(LinkingError::PolicyIdConflict {
577                id: oentry.key().clone(),
578            }),
579            (_, Entry::Occupied(oentry)) => Err(LinkingError::PolicyIdConflict {
580                id: oentry.key().clone(),
581            }),
582        }
583    }
584
585    /// Unlink `policy_id`
586    /// If it is not a link this will error
587    pub fn unlink(&mut self, policy_id: &PolicyID) -> Result<Policy, PolicySetUnlinkError> {
588        //A link occurs in links but not in templates.
589        if self.templates.contains_key(policy_id) {
590            return Err(PolicySetUnlinkError::NotLinkError(policy_id.clone()));
591        }
592        match self.links.remove(policy_id) {
593            Some(p) => {
594                // PANIC SAFETY: every linked policy should have a template
595                #[allow(clippy::panic)]
596                match self.template_to_links_map.entry(p.template().id().clone()) {
597                    Entry::Occupied(t) => t.into_mut().remove(policy_id),
598                    Entry::Vacant(_) => {
599                        panic!("No template found for linked policy")
600                    }
601                };
602                Ok(p)
603            }
604            None => Err(PolicySetUnlinkError::UnlinkingError(policy_id.clone())),
605        }
606    }
607
608    /// Iterate over all policies
609    pub fn policies(&self) -> impl Iterator<Item = &Policy> {
610        self.links.values()
611    }
612
613    /// Consume the `PolicySet`, producing an iterator of all the policies in it
614    pub fn into_policies(self) -> impl Iterator<Item = Policy> {
615        self.links.into_iter().map(|(_, p)| p)
616    }
617
618    /// Iterate over everything stored as template, including static policies.
619    /// Ie: all_templates() should equal templates() ++ static_policies().map(|p| p.template())
620    pub fn all_templates(&self) -> impl Iterator<Item = &Template> {
621        self.templates.values().map(|t| t.borrow())
622    }
623
624    /// Iterate over templates with slots
625    pub fn templates(&self) -> impl Iterator<Item = &Template> {
626        self.all_templates().filter(|t| t.slots().count() != 0)
627    }
628
629    /// Iterate over all of the static policies.
630    pub fn static_policies(&self) -> impl Iterator<Item = &Policy> {
631        self.policies().filter(|p| p.is_static())
632    }
633
634    /// Returns true iff the `PolicySet` is empty
635    pub fn is_empty(&self) -> bool {
636        self.templates.is_empty() && self.links.is_empty()
637    }
638
639    /// Lookup a template by policy id, returns [`Option<Arc<Template>>`]
640    pub fn get_template_arc(&self, id: &PolicyID) -> Option<Arc<Template>> {
641        self.templates.get(id).cloned()
642    }
643
644    /// Lookup a template by policy id, returns [`Option<&Template>`]
645    pub fn get_template(&self, id: &PolicyID) -> Option<&Template> {
646        self.templates.get(id).map(AsRef::as_ref)
647    }
648
649    /// Lookup an policy by policy id
650    pub fn get(&self, id: &PolicyID) -> Option<&Policy> {
651        self.links.get(id)
652    }
653
654    /// Attempt to collect an iterator over policies into a PolicySet
655    pub fn try_from_iter<T: IntoIterator<Item = Policy>>(iter: T) -> Result<Self, PolicySetError> {
656        let mut set = Self::new();
657        for p in iter {
658            set.add(p)?;
659        }
660        Ok(set)
661    }
662}
663
664impl std::fmt::Display for PolicySet {
665    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
666        // we don't show the ID, because the Display impl for Policy itself shows the ID
667        if self.is_empty() {
668            write!(f, "<empty policyset>")
669        } else {
670            write!(
671                f,
672                "Templates:\n{}, Template Linked Policies:\n{}",
673                self.all_templates().join("\n"),
674                self.policies().join("\n")
675            )
676        }
677    }
678}
679
680// PANIC SAFETY tests
681#[allow(clippy::panic)]
682// PANIC SAFETY tests
683#[allow(clippy::indexing_slicing)]
684#[cfg(test)]
685mod test {
686    use super::*;
687    use crate::{
688        ast::{
689            annotation::Annotations, ActionConstraint, Effect, PrincipalConstraint,
690            ResourceConstraint,
691        },
692        parser,
693    };
694
695    use similar_asserts::assert_eq;
696    use std::collections::HashMap;
697
698    #[test]
699    fn link_conflicts() {
700        let mut pset = PolicySet::new();
701        let p1 = parser::parse_policy(
702            Some(PolicyID::from_string("id")),
703            "permit(principal,action,resource);",
704        )
705        .expect("Failed to parse");
706        pset.add_static(p1).expect("Failed to add!");
707        let template = parser::parse_policy_or_template(
708            Some(PolicyID::from_string("t")),
709            "permit(principal == ?principal, action, resource);",
710        )
711        .expect("Failed to parse");
712        pset.add_template(template).expect("Add failed");
713
714        let env: HashMap<SlotId, EntityUID> = HashMap::from([(
715            SlotId::principal(),
716            r#"Test::"test""#.parse().expect("Failed to parse"),
717        )]);
718
719        let r = pset.link(PolicyID::from_string("t"), PolicyID::from_string("id"), env);
720
721        match r {
722            Ok(_) => panic!("Should have failed due to conflict"),
723            Err(LinkingError::PolicyIdConflict { id }) => {
724                assert_eq!(id, PolicyID::from_string("id"))
725            }
726            Err(e) => panic!("Incorrect error: {e}"),
727        };
728    }
729
730    /// This test focuses on `PolicySet::add()`, while other tests mostly use
731    /// `PolicySet::add_static()` and `PolicySet::link()`.
732    #[test]
733    fn policyset_add() {
734        let mut pset = PolicySet::new();
735        let static_policy = parser::parse_policy(
736            Some(PolicyID::from_string("id")),
737            "permit(principal,action,resource);",
738        )
739        .expect("Failed to parse");
740        let static_policy: Policy = static_policy.into();
741        pset.add(static_policy)
742            .expect("Adding static policy in Policy form should succeed");
743
744        let template = Arc::new(
745            parser::parse_policy_or_template(
746                Some(PolicyID::from_string("t")),
747                "permit(principal == ?principal, action, resource);",
748            )
749            .expect("Failed to parse"),
750        );
751        let env1: HashMap<SlotId, EntityUID> = HashMap::from([(
752            SlotId::principal(),
753            r#"Test::"test1""#.parse().expect("Failed to parse"),
754        )]);
755
756        let p1 = Template::link(Arc::clone(&template), PolicyID::from_string("link"), env1)
757            .expect("Failed to link");
758        pset.add(p1).expect(
759            "Adding link should succeed, even though the template wasn't previously in the pset",
760        );
761        assert!(
762            pset.get_template_arc(&PolicyID::from_string("t")).is_some(),
763            "Adding link should implicitly add the template"
764        );
765
766        let env2: HashMap<SlotId, EntityUID> = HashMap::from([(
767            SlotId::principal(),
768            r#"Test::"test2""#.parse().expect("Failed to parse"),
769        )]);
770
771        let p2 = Template::link(
772            Arc::clone(&template),
773            PolicyID::from_string("link"),
774            env2.clone(),
775        )
776        .expect("Failed to link");
777        match pset.add(p2) {
778            Ok(_) => panic!("Should have failed due to conflict with existing link id"),
779            Err(PolicySetError::Occupied { id }) => assert_eq!(id, PolicyID::from_string("link")),
780        }
781
782        let p3 = Template::link(Arc::clone(&template), PolicyID::from_string("link2"), env2)
783            .expect("Failed to link");
784        pset.add(p3).expect(
785            "Adding link should succeed, even though the template already existed in the pset",
786        );
787
788        let template2 = Arc::new(
789            parser::parse_policy_or_template(
790                Some(PolicyID::from_string("t")),
791                "forbid(principal, action, resource == ?resource);",
792            )
793            .expect("Failed to parse"),
794        );
795        let env3: HashMap<SlotId, EntityUID> = HashMap::from([(
796            SlotId::resource(),
797            r#"Test::"test3""#.parse().expect("Failed to parse"),
798        )]);
799
800        let p4 = Template::link(
801            Arc::clone(&template2),
802            PolicyID::from_string("unique3"),
803            env3,
804        )
805        .expect("Failed to link");
806        match pset.add(p4) {
807            Ok(_) => panic!("Should have failed due to conflict on template id"),
808            Err(PolicySetError::Occupied { id }) => {
809                assert_eq!(id, PolicyID::from_string("t"))
810            }
811        }
812    }
813
814    #[test]
815    fn policy_merge_no_conflicts() {
816        let p1 = parser::parse_policy(
817            Some(PolicyID::from_string("policy0")),
818            "permit(principal,action,resource);",
819        )
820        .expect("Failed to parse");
821        let p2 = parser::parse_policy(
822            Some(PolicyID::from_string("policy1")),
823            "permit(principal,action,resource) when { false };",
824        )
825        .expect("Failed to parse");
826        let p3 = parser::parse_policy(
827            Some(PolicyID::from_string("policy0")),
828            "permit(principal,action,resource);",
829        )
830        .expect("Failed to parse");
831        let p4 = parser::parse_policy(
832            Some(PolicyID::from_string("policy2")),
833            "permit(principal,action,resource) when { true };",
834        )
835        .expect("Failed to parse");
836        let mut pset1 = PolicySet::new();
837        let mut pset2 = PolicySet::new();
838        pset1.add_static(p1).expect("Failed to add!");
839        pset1.add_static(p2).expect("Failed to add!");
840        pset2.add_static(p3).expect("Failed to add!");
841        pset2.add_static(p4).expect("Failed to add!");
842        // should not conflict because p1 == p3
843        match pset1.merge_policyset(&pset2, false) {
844            Ok(_) => (),
845            Err(PolicySetError::Occupied { id }) => {
846                panic!("There should not have been an error! Unexpected conflict for id {id}")
847            }
848        }
849    }
850
851    #[test]
852    fn policy_merge_with_conflicts() {
853        let pid0 = PolicyID::from_string("policy0");
854        let pid1 = PolicyID::from_string("policy1");
855        let pid2 = PolicyID::from_string("policy2");
856        let p1 = parser::parse_policy(Some(pid0.clone()), "permit(principal,action,resource);")
857            .expect("Failed to parse");
858        let p2 = parser::parse_policy(
859            Some(pid1.clone()),
860            "permit(principal,action,resource) when { false };",
861        )
862        .expect("Failed to parse");
863        let p3 = parser::parse_policy(Some(pid1.clone()), "permit(principal,action,resource);")
864            .expect("Failed to parse");
865        let p4 = parser::parse_policy(
866            Some(pid2.clone()),
867            "permit(principal,action,resource) when { true };",
868        )
869        .expect("Failed to parse");
870        let mut pset1 = PolicySet::new();
871        let mut pset2 = PolicySet::new();
872        pset1.add_static(p1.clone()).expect("Failed to add!");
873        pset1.add_static(p2.clone()).expect("Failed to add!");
874        pset2.add_static(p3.clone()).expect("Failed to add!");
875        pset2.add_static(p4.clone()).expect("Failed to add!");
876        // should conclict on pid "policy1"
877        match pset1.merge_policyset(&pset2, false) {
878            Ok(_) => panic!("`pset1` and `pset2` should conflict for PolicyID `policy1`"),
879            Err(PolicySetError::Occupied { id }) => {
880                assert_eq!(id, PolicyID::from_string("policy1"));
881            }
882        }
883        // should not conflict because of auto-renaming of conflicting policies
884        match pset1.merge_policyset(&pset2, true) {
885            Ok(renaming) => {
886                // ensure `policy1` was renamed
887                let new_pid1 = match renaming.get(&pid1) {
888                    Some(new_pid1) => new_pid1,
889                    None => panic!("Error: `policy1` is a conflict and should be renamed"),
890                };
891                // ensure no other policy was renamed
892                assert_eq!(renaming.keys().len(), 1);
893                if let Some(new_p1) = pset1.get(&pid0) {
894                    assert_eq!(Policy::from(p1), new_p1.clone());
895                }
896                if let Some(new_p2) = pset1.get(&pid1) {
897                    assert_eq!(Policy::from(p2), new_p2.clone());
898                }
899                if let Some(new_p3) = pset1.get(new_pid1) {
900                    assert_eq!(Policy::from(p3).new_id(new_pid1.clone()), new_p3.clone());
901                }
902                if let Some(new_p4) = pset1.get(&pid2) {
903                    assert_eq!(Policy::from(p4), new_p4.clone());
904                }
905            }
906            Err(PolicySetError::Occupied { id }) => {
907                panic!("There should not have been an error! Unexpected conflict for id {id}")
908            }
909        }
910    }
911
912    #[test]
913    fn policy_conflicts() {
914        let mut pset = PolicySet::new();
915        let p1 = parser::parse_policy(
916            Some(PolicyID::from_string("id")),
917            "permit(principal,action,resource);",
918        )
919        .expect("Failed to parse");
920        let p2 = parser::parse_policy(
921            Some(PolicyID::from_string("id")),
922            "permit(principal,action,resource) when { false };",
923        )
924        .expect("Failed to parse");
925        pset.add_static(p1).expect("Failed to add!");
926        match pset.add_static(p2) {
927            Ok(_) => panic!("Should have failed to due name conflict"),
928            Err(PolicySetError::Occupied { id }) => assert_eq!(id, PolicyID::from_string("id")),
929        }
930    }
931
932    #[test]
933    fn template_filtering() {
934        let template = parser::parse_policy_or_template(
935            Some(PolicyID::from_string("template")),
936            "permit(principal == ?principal, action, resource);",
937        )
938        .expect("Template Parse Failure");
939        let static_policy = parser::parse_policy(
940            Some(PolicyID::from_string("static")),
941            "permit(principal, action, resource);",
942        )
943        .expect("Static parse failure");
944        let mut set = PolicySet::new();
945        set.add_template(template).unwrap();
946        set.add_static(static_policy).unwrap();
947
948        assert_eq!(set.all_templates().count(), 2);
949        assert_eq!(set.templates().count(), 1);
950        assert_eq!(set.static_policies().count(), 1);
951        assert_eq!(set.policies().count(), 1);
952        set.link(
953            PolicyID::from_string("template"),
954            PolicyID::from_string("id"),
955            HashMap::from([(SlotId::principal(), EntityUID::with_eid("eid"))]),
956        )
957        .expect("Linking failed!");
958        assert_eq!(set.static_policies().count(), 1);
959        assert_eq!(set.policies().count(), 2);
960    }
961
962    #[test]
963    fn linking_missing_template() {
964        let tid = PolicyID::from_string("template");
965        let lid = PolicyID::from_string("link");
966        let t = Template::new(
967            tid.clone(),
968            None,
969            Annotations::new(),
970            Effect::Permit,
971            PrincipalConstraint::any(),
972            ActionConstraint::any(),
973            ResourceConstraint::any(),
974            None,
975        );
976
977        let mut s = PolicySet::new();
978        let e = s
979            .link(tid.clone(), lid.clone(), HashMap::new())
980            .expect_err("Should fail");
981
982        match e {
983            LinkingError::NoSuchTemplate { id } => assert_eq!(tid, id),
984            e => panic!("Wrong error {e}"),
985        };
986
987        s.add_template(t).unwrap();
988        s.link(tid, lid, HashMap::new()).expect("Should succeed");
989    }
990
991    #[test]
992    fn linkinv_valid_link() {
993        let tid = PolicyID::from_string("template");
994        let lid = PolicyID::from_string("link");
995        let t = Template::new(
996            tid.clone(),
997            None,
998            Annotations::new(),
999            Effect::Permit,
1000            PrincipalConstraint::is_eq_slot(),
1001            ActionConstraint::any(),
1002            ResourceConstraint::is_in_slot(),
1003            None,
1004        );
1005
1006        let mut s = PolicySet::new();
1007        s.add_template(t).unwrap();
1008
1009        let mut vals = HashMap::new();
1010        vals.insert(SlotId::principal(), EntityUID::with_eid("p"));
1011        vals.insert(SlotId::resource(), EntityUID::with_eid("a"));
1012
1013        s.link(tid.clone(), lid.clone(), vals).expect("Should link");
1014
1015        let v: Vec<_> = s.policies().collect();
1016
1017        assert_eq!(v[0].id(), &lid);
1018        assert_eq!(v[0].template().id(), &tid);
1019    }
1020
1021    #[test]
1022    fn linking_empty_set() {
1023        let s = PolicySet::new();
1024        assert_eq!(s.policies().count(), 0);
1025    }
1026
1027    #[test]
1028    fn linking_raw_policy() {
1029        let mut s = PolicySet::new();
1030        let id = PolicyID::from_string("id");
1031        let p = StaticPolicy::new(
1032            id.clone(),
1033            None,
1034            Annotations::new(),
1035            Effect::Forbid,
1036            PrincipalConstraint::any(),
1037            ActionConstraint::any(),
1038            ResourceConstraint::any(),
1039            None,
1040        )
1041        .expect("Policy Creation Failed");
1042        s.add_static(p).unwrap();
1043
1044        let mut iter = s.policies();
1045        match iter.next() {
1046            Some(pol) => {
1047                assert_eq!(pol.id(), &id);
1048                assert_eq!(pol.effect(), Effect::Forbid);
1049                assert!(pol.env().is_empty())
1050            }
1051            None => panic!("Linked Record Not Present"),
1052        };
1053    }
1054
1055    #[test]
1056    fn link_slotmap() {
1057        let mut s = PolicySet::new();
1058        let template_id = PolicyID::from_string("template");
1059        let link_id = PolicyID::from_string("link");
1060        let t = Template::new(
1061            template_id.clone(),
1062            None,
1063            Annotations::new(),
1064            Effect::Forbid,
1065            PrincipalConstraint::is_eq_slot(),
1066            ActionConstraint::any(),
1067            ResourceConstraint::any(),
1068            None,
1069        );
1070        s.add_template(t).unwrap();
1071
1072        let mut v = HashMap::new();
1073        let entity = EntityUID::with_eid("eid");
1074        v.insert(SlotId::principal(), entity.clone());
1075        s.link(template_id.clone(), link_id.clone(), v)
1076            .expect("Linking failed!");
1077
1078        let link = s.get(&link_id).expect("Link should exist");
1079        assert_eq!(&link_id, link.id());
1080        assert_eq!(&template_id, link.template().id());
1081        assert_eq!(
1082            &entity,
1083            link.env()
1084                .get(&SlotId::principal())
1085                .expect("Mapping was incorrect")
1086        );
1087    }
1088
1089    #[test]
1090    fn policy_sets() {
1091        let mut pset = PolicySet::new();
1092        assert!(pset.is_empty());
1093        let id1 = PolicyID::from_string("id1");
1094        let tid1 = PolicyID::from_string("template");
1095        let policy1 = StaticPolicy::new(
1096            id1.clone(),
1097            None,
1098            Annotations::new(),
1099            Effect::Permit,
1100            PrincipalConstraint::any(),
1101            ActionConstraint::any(),
1102            ResourceConstraint::any(),
1103            None,
1104        )
1105        .expect("Policy Creation Failed");
1106        let template1 = Template::new(
1107            tid1.clone(),
1108            None,
1109            Annotations::new(),
1110            Effect::Permit,
1111            PrincipalConstraint::any(),
1112            ActionConstraint::any(),
1113            ResourceConstraint::any(),
1114            None,
1115        );
1116        let added = pset.add_static(policy1.clone()).is_ok();
1117        assert!(added);
1118        let added = pset.add_static(policy1).is_ok();
1119        assert!(!added);
1120        let added = pset.add_template(template1.clone()).is_ok();
1121        assert!(added);
1122        let added = pset.add_template(template1).is_ok();
1123        assert!(!added);
1124        assert!(!pset.is_empty());
1125        let id2 = PolicyID::from_string("id2");
1126        let policy2 = StaticPolicy::new(
1127            id2.clone(),
1128            None,
1129            Annotations::new(),
1130            Effect::Forbid,
1131            PrincipalConstraint::is_eq(Arc::new(EntityUID::with_eid("jane"))),
1132            ActionConstraint::any(),
1133            ResourceConstraint::any(),
1134            None,
1135        )
1136        .expect("Policy Creation Failed");
1137        let added = pset.add_static(policy2).is_ok();
1138        assert!(added);
1139
1140        let tid2 = PolicyID::from_string("template2");
1141        let template2 = Template::new(
1142            tid2.clone(),
1143            None,
1144            Annotations::new(),
1145            Effect::Permit,
1146            PrincipalConstraint::is_eq_slot(),
1147            ActionConstraint::any(),
1148            ResourceConstraint::any(),
1149            None,
1150        );
1151        let id3 = PolicyID::from_string("link");
1152        let added = pset.add_template(template2).is_ok();
1153        assert!(added);
1154
1155        let r = pset.link(
1156            tid2.clone(),
1157            id3.clone(),
1158            HashMap::from([(SlotId::principal(), EntityUID::with_eid("example"))]),
1159        );
1160        r.expect("Linking failed");
1161
1162        assert_eq!(pset.get(&id1).expect("should find the policy").id(), &id1);
1163        assert_eq!(pset.get(&id2).expect("should find the policy").id(), &id2);
1164        assert_eq!(pset.get(&id3).expect("should find link").id(), &id3);
1165        assert_eq!(
1166            pset.get(&id3).expect("should find link").template().id(),
1167            &tid2
1168        );
1169        assert!(pset.get(&tid2).is_none());
1170        assert!(pset.get_template_arc(&id1).is_some()); // Static policies are also templates
1171        assert!(pset.get_template_arc(&id2).is_some()); // Static policies are also templates
1172        assert!(pset.get_template_arc(&tid2).is_some());
1173        assert_eq!(pset.policies().count(), 3);
1174
1175        assert_eq!(
1176            pset.get_template_arc(&tid1)
1177                .expect("should find the template")
1178                .id(),
1179            &tid1
1180        );
1181        assert!(pset.get(&tid1).is_none());
1182        assert_eq!(pset.all_templates().count(), 4);
1183    }
1184
1185    #[test]
1186    fn policy_set_insertion_order() {
1187        let mut pset = PolicySet::new();
1188        assert!(pset.is_empty());
1189
1190        let src = "permit(principal, action, resource);";
1191        let ids: Vec<PolicyID> = (1..=4)
1192            .map(|i| {
1193                let id = PolicyID::from_string(format!("id{i}"));
1194                let p = parser::parse_policy(Some(id.clone()), src).unwrap();
1195                let added = pset.add(p.into()).is_ok();
1196                assert!(added);
1197                id
1198            })
1199            .collect();
1200
1201        assert_eq!(
1202            pset.into_policies()
1203                .map(|p| p.id().clone())
1204                .collect::<Vec<PolicyID>>(),
1205            ids
1206        );
1207    }
1208}