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