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