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 serde::{Deserialize, Serialize};
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, Serialize, Deserialize)]
30#[serde(try_from = "LiteralPolicySet")]
31#[serde(into = "LiteralPolicySet")]
32pub struct PolicySet {
33    /// `templates` contains all bodies of policies in the `PolicySet`.
34    /// A body is either:
35    /// - A Body of a `Template`, which has slots that need to be filled in
36    /// - A Body of a `StaticPolicy`, which has been converted into a `Template` that has zero slots.
37    ///   The static policy's [`PolicyID`] is the same in both `templates` and `links`.
38    templates: HashMap<PolicyID, Arc<Template>>,
39    /// `links` contains all of the executable policies in the `PolicySet`
40    /// A `StaticPolicy` must have exactly one `Policy` in `links`
41    ///   (this is managed by `PolicySet::add`)
42    ///   The static policy's PolicyID is the same in both `templates` and `links`
43    /// A `Template` may have zero or many links
44    links: HashMap<PolicyID, Policy>,
45
46    /// Map from a template `PolicyID` to the set of `PolicyID`s in `links` that are linked to that template.
47    /// There is a key `t` iff `templates` contains the key `t`. The value of `t` will be a (possibly empty)
48    /// set of every `p` in `links` s.t. `p.template().id() == t`.
49    template_to_links_map: HashMap<PolicyID, HashSet<PolicyID>>,
50}
51
52/// Converts a LiteralPolicySet into a PolicySet, ensuring the invariants are met
53/// Every `Policy` must point to a `Template` that exists in the set.
54impl TryFrom<LiteralPolicySet> for PolicySet {
55    type Error = ReificationError;
56    fn try_from(pset: LiteralPolicySet) -> Result<Self, Self::Error> {
57        // Allocate the templates into Arc's
58        let templates = pset
59            .templates
60            .into_iter()
61            .map(|(id, template)| (id, Arc::new(template)))
62            .collect();
63        let links = pset
64            .links
65            .into_iter()
66            .map(|(id, literal)| literal.reify(&templates).map(|linked| (id, linked)))
67            .collect::<Result<HashMap<PolicyID, Policy>, ReificationError>>()?;
68
69        let mut template_to_links_map = HashMap::new();
70        for template in &templates {
71            template_to_links_map.insert(template.0.clone(), HashSet::new());
72        }
73        for (link_id, link) in &links {
74            let template = link.template().id();
75            match template_to_links_map.entry(template.clone()) {
76                Entry::Occupied(t) => t.into_mut().insert(link_id.clone()),
77                Entry::Vacant(_) => return Err(ReificationError::NoSuchTemplate(template.clone())),
78            };
79        }
80
81        Ok(Self {
82            templates,
83            links,
84            template_to_links_map,
85        })
86    }
87}
88
89/// A Policy Set that can be serialized, but does not maintain the invariants that `PolicySet` does
90#[derive(Debug, Serialize, Deserialize)]
91struct LiteralPolicySet {
92    templates: HashMap<PolicyID, Template>,
93    links: HashMap<PolicyID, LiteralPolicy>,
94}
95
96impl From<PolicySet> for LiteralPolicySet {
97    fn from(pset: PolicySet) -> Self {
98        let templates = pset
99            .templates
100            .into_iter()
101            .map(|(id, template)| (id, template.as_ref().clone()))
102            .collect();
103        let links = pset
104            .links
105            .into_iter()
106            .map(|(id, p)| (id, p.into()))
107            .collect();
108        Self { templates, links }
109    }
110}
111
112/// Potential errors when working with `PolicySet`s.
113#[derive(Debug, Diagnostic, Error)]
114pub enum PolicySetError {
115    /// There was a duplicate [`PolicyID`] encountered in either the set of
116    /// templates or the set of policies.
117    #[error("duplicate template or policy id `{id}`")]
118    Occupied {
119        /// [`PolicyID`] that was duplicate
120        id: PolicyID,
121    },
122}
123
124/// Potential errors when working with `PolicySet`s.
125#[derive(Debug, Diagnostic, Error)]
126pub enum PolicySetGetLinksError {
127    /// There was no [`PolicyID`] in the set of templates.
128    #[error("No template `{0}`")]
129    MissingTemplate(PolicyID),
130}
131
132/// Potential errors when unlinking from a `PolicySet`.
133#[derive(Debug, Diagnostic, Error)]
134pub enum PolicySetUnlinkError {
135    /// There was no [`PolicyID`] linked policy to unlink
136    #[error("unable to unlink policy id `{0}` because it does not exist")]
137    UnlinkingError(PolicyID),
138    /// There was a template [`PolicyID`] in the list of templates, so `PolicyID` is a static policy
139    #[error("unable to remove link with policy id `{0}` because it is a static policy")]
140    NotLinkError(PolicyID),
141}
142
143/// Potential errors when removing templates from a `PolicySet`.
144#[derive(Debug, Diagnostic, Error)]
145pub enum PolicySetTemplateRemovalError {
146    /// There was no [`PolicyID`] template in the list of templates.
147    #[error("unable to remove template id `{0}` from template list because it does not exist")]
148    RemovePolicyNoTemplateError(PolicyID),
149    /// There are still active links to template [`PolicyID`].
150    #[error(
151        "unable to remove template id `{0}` from template list because it still has active links"
152    )]
153    RemoveTemplateWithLinksError(PolicyID),
154    /// There was a link [`PolicyID`] in the list of links, so `PolicyID` is a static policy
155    #[error("unable to remove template with policy id `{0}` because it is a static policy")]
156    NotTemplateError(PolicyID),
157}
158
159/// Potential errors when removing policies from a `PolicySet`.
160#[derive(Debug, Diagnostic, Error)]
161pub enum PolicySetPolicyRemovalError {
162    /// There was no link [`PolicyID`] in the list of links.
163    #[error("unable to remove static policy id `{0}` from link list because it does not exist")]
164    RemovePolicyNoLinkError(PolicyID),
165    /// There was no template [`PolicyID`] in the list of templates.
166    #[error(
167        "unable to remove static policy id `{0}` from template list because it does not exist"
168    )]
169    RemovePolicyNoTemplateError(PolicyID),
170}
171
172// The public interface of `PolicySet` is intentionally narrow, to allow us
173// maximum flexibility to change the underlying implementation in the future
174impl PolicySet {
175    /// Create a fresh empty `PolicySet`
176    pub fn new() -> Self {
177        Self {
178            templates: HashMap::new(),
179            links: HashMap::new(),
180            template_to_links_map: HashMap::new(),
181        }
182    }
183
184    /// Add a `Policy` to the `PolicySet`.
185    pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
186        let t = policy.template_arc();
187
188        // we need to check for all possible errors before making any
189        // modifications to `self`.
190        // So we just collect the `ventry` here, and we only do the insertion
191        // once we know there will be no error
192        let template_ventry = match self.templates.entry(t.id().clone()) {
193            Entry::Vacant(ventry) => Some(ventry),
194            Entry::Occupied(oentry) => {
195                if oentry.get() != &t {
196                    return Err(PolicySetError::Occupied {
197                        id: oentry.key().clone(),
198                    });
199                }
200                None
201            }
202        };
203
204        let link_ventry = match self.links.entry(policy.id().clone()) {
205            Entry::Vacant(ventry) => Some(ventry),
206            Entry::Occupied(oentry) => {
207                return Err(PolicySetError::Occupied {
208                    id: oentry.key().clone(),
209                });
210            }
211        };
212
213        // if we get here, there will be no errors.  So actually do the
214        // insertions.
215        if let Some(ventry) = template_ventry {
216            self.template_to_links_map.insert(
217                t.id().clone(),
218                vec![policy.id().clone()]
219                    .into_iter()
220                    .collect::<HashSet<PolicyID>>(),
221            );
222            ventry.insert(t);
223        } else {
224            //`template_ventry` is None, so `templates` has `t` and we never use the `HashSet::new()`
225            self.template_to_links_map
226                .entry(t.id().clone())
227                .or_default()
228                .insert(policy.id().clone());
229        }
230        if let Some(ventry) = link_ventry {
231            ventry.insert(policy);
232        }
233
234        Ok(())
235    }
236
237    /// Remove a static `Policy`` from the `PolicySet`.
238    pub fn remove_static(
239        &mut self,
240        policy_id: &PolicyID,
241    ) -> Result<Policy, PolicySetPolicyRemovalError> {
242        // Invariant: if `policy_id` is a key in both `self.links` and `self.templates`,
243        // then self.templates[policy_id] has exactly one link: self.links[policy_id]
244        let policy = match self.links.remove(policy_id) {
245            Some(p) => p,
246            None => {
247                return Err(PolicySetPolicyRemovalError::RemovePolicyNoLinkError(
248                    policy_id.clone(),
249                ))
250            }
251        };
252        //links mapped by `PolicyId`, so `policy` is unique
253        match self.templates.remove(policy_id) {
254            Some(_) => {
255                self.template_to_links_map.remove(policy_id);
256                Ok(policy)
257            }
258            None => {
259                //If we removed the link but failed to remove the template
260                //restore the link and return an error
261                self.links.insert(policy_id.clone(), policy);
262                Err(PolicySetPolicyRemovalError::RemovePolicyNoTemplateError(
263                    policy_id.clone(),
264                ))
265            }
266        }
267    }
268
269    /// Add a `StaticPolicy` to the `PolicySet`.
270    pub fn add_static(&mut self, policy: StaticPolicy) -> Result<(), PolicySetError> {
271        let (t, p) = Template::link_static_policy(policy);
272
273        match (
274            self.templates.entry(t.id().clone()),
275            self.links.entry(t.id().clone()),
276        ) {
277            (Entry::Vacant(templates_entry), Entry::Vacant(links_entry)) => {
278                self.template_to_links_map.insert(
279                    t.id().clone(),
280                    vec![p.id().clone()]
281                        .into_iter()
282                        .collect::<HashSet<PolicyID>>(),
283                );
284                templates_entry.insert(t);
285                links_entry.insert(p);
286                Ok(())
287            }
288            (Entry::Occupied(oentry), _) => Err(PolicySetError::Occupied {
289                id: oentry.key().clone(),
290            }),
291            (_, Entry::Occupied(oentry)) => Err(PolicySetError::Occupied {
292                id: oentry.key().clone(),
293            }),
294        }
295    }
296
297    /// Add a template to the policy set.
298    /// If a link, static policy or template with the same name already exists, this will error.
299    pub fn add_template(&mut self, t: Template) -> Result<(), PolicySetError> {
300        if self.links.contains_key(t.id()) {
301            return Err(PolicySetError::Occupied { id: t.id().clone() });
302        }
303
304        match self.templates.entry(t.id().clone()) {
305            Entry::Occupied(oentry) => Err(PolicySetError::Occupied {
306                id: oentry.key().clone(),
307            }),
308            Entry::Vacant(ventry) => {
309                self.template_to_links_map
310                    .insert(t.id().clone(), HashSet::new());
311                ventry.insert(Arc::new(t));
312                Ok(())
313            }
314        }
315    }
316
317    /// Remove a template from the policy set.
318    /// This will error if any policy is linked to the template.
319    /// This will error if `policy_id` is not a template.
320    pub fn remove_template(
321        &mut self,
322        policy_id: &PolicyID,
323    ) -> Result<Template, PolicySetTemplateRemovalError> {
324        //A template occurs in templates but not in links.
325        if self.links.contains_key(policy_id) {
326            return Err(PolicySetTemplateRemovalError::NotTemplateError(
327                policy_id.clone(),
328            ));
329        }
330
331        match self.template_to_links_map.get(policy_id) {
332            Some(map) => {
333                if !map.is_empty() {
334                    return Err(PolicySetTemplateRemovalError::RemoveTemplateWithLinksError(
335                        policy_id.clone(),
336                    ));
337                }
338            }
339            None => {
340                return Err(PolicySetTemplateRemovalError::RemovePolicyNoTemplateError(
341                    policy_id.clone(),
342                ))
343            }
344        };
345
346        // PANIC SAFETY: every linked policy should have a template
347        #[allow(clippy::panic)]
348        match self.templates.remove(policy_id) {
349            Some(t) => {
350                self.template_to_links_map.remove(policy_id);
351                Ok((*t).clone())
352            }
353            None => panic!("Found in template_to_links_map but not in templates"),
354        }
355    }
356
357    /// Get the list of policies linked to `template_id`.
358    /// Returns all p in `links` s.t. `p.template().id() == template_id`
359    pub fn get_linked_policies(
360        &self,
361        template_id: &PolicyID,
362    ) -> Result<impl Iterator<Item = &PolicyID>, PolicySetGetLinksError> {
363        match self.template_to_links_map.get(template_id) {
364            Some(s) => Ok(s.iter()),
365            None => Err(PolicySetGetLinksError::MissingTemplate(template_id.clone())),
366        }
367    }
368
369    /// Attempt to create a new template linked policy and add it to the policy
370    /// set. Returns a references to the new template linked policy if
371    /// successful.
372    ///
373    /// Errors for two reasons
374    ///   1) The the passed SlotEnv either does not match the slots in the templates
375    ///   2) The passed link Id conflicts with an Id already in the set
376    pub fn link(
377        &mut self,
378        template_id: PolicyID,
379        new_id: PolicyID,
380        values: HashMap<SlotId, EntityUID>,
381    ) -> Result<&Policy, LinkingError> {
382        let t =
383            self.get_template_arc(&template_id)
384                .ok_or_else(|| LinkingError::NoSuchTemplate {
385                    id: template_id.clone(),
386                })?;
387        let r = Template::link(t, new_id.clone(), values)?;
388
389        // Both maps must not contain the `new_id`
390        match (
391            self.links.entry(new_id.clone()),
392            self.templates.entry(new_id.clone()),
393        ) {
394            (Entry::Vacant(links_entry), Entry::Vacant(_)) => {
395                //We will never use the .or_default() because we just found `t` above
396                self.template_to_links_map
397                    .entry(template_id)
398                    .or_default()
399                    .insert(new_id);
400                Ok(links_entry.insert(r))
401            }
402            (Entry::Occupied(oentry), _) => Err(LinkingError::PolicyIdConflict {
403                id: oentry.key().clone(),
404            }),
405            (_, Entry::Occupied(oentry)) => Err(LinkingError::PolicyIdConflict {
406                id: oentry.key().clone(),
407            }),
408        }
409    }
410
411    /// Unlink `policy_id`
412    /// If it is not a link this will error
413    pub fn unlink(&mut self, policy_id: &PolicyID) -> Result<Policy, PolicySetUnlinkError> {
414        //A link occurs in links but not in templates.
415        if self.templates.contains_key(policy_id) {
416            return Err(PolicySetUnlinkError::NotLinkError(policy_id.clone()));
417        }
418        match self.links.remove(policy_id) {
419            Some(p) => {
420                // PANIC SAFETY: every linked policy should have a template
421                #[allow(clippy::panic)]
422                match self.template_to_links_map.entry(p.template().id().clone()) {
423                    Entry::Occupied(t) => t.into_mut().remove(policy_id),
424                    Entry::Vacant(_) => {
425                        panic!("No template found for linked policy")
426                    }
427                };
428                Ok(p)
429            }
430            None => Err(PolicySetUnlinkError::UnlinkingError(policy_id.clone())),
431        }
432    }
433
434    /// Iterate over all policies
435    pub fn policies(&self) -> impl Iterator<Item = &Policy> {
436        self.links.values()
437    }
438
439    /// Iterate over everything stored as template, including static policies.
440    /// Ie: all_templates() should equal templates() ++ static_policies().map(|p| p.template())
441    pub fn all_templates(&self) -> impl Iterator<Item = &Template> {
442        self.templates.values().map(|t| t.borrow())
443    }
444
445    /// Iterate over templates with slots
446    pub fn templates(&self) -> impl Iterator<Item = &Template> {
447        self.all_templates().filter(|t| t.slots().count() != 0)
448    }
449
450    /// Iterate over all of the static policies.
451    pub fn static_policies(&self) -> impl Iterator<Item = &Policy> {
452        self.policies().filter(|p| p.is_static())
453    }
454
455    /// Returns true iff the `PolicySet` is empty
456    pub fn is_empty(&self) -> bool {
457        self.templates.is_empty() && self.links.is_empty()
458    }
459
460    /// Lookup a template by policy id, returns [`Option<Arc<Template>>`]
461    pub fn get_template_arc(&self, id: &PolicyID) -> Option<Arc<Template>> {
462        self.templates.get(id).cloned()
463    }
464
465    /// Lookup a template by policy id, returns [`Option<&Template>`]
466    pub fn get_template(&self, id: &PolicyID) -> Option<&Template> {
467        self.templates.get(id).map(AsRef::as_ref)
468    }
469
470    /// Lookup an policy by policy id
471    pub fn get(&self, id: &PolicyID) -> Option<&Policy> {
472        self.links.get(id)
473    }
474
475    /// Attempt to collect an iterator over policies into a PolicySet
476    pub fn try_from_iter<T: IntoIterator<Item = Policy>>(iter: T) -> Result<Self, PolicySetError> {
477        let mut set = Self::new();
478        for p in iter {
479            set.add(p)?;
480        }
481        Ok(set)
482    }
483}
484
485impl std::fmt::Display for PolicySet {
486    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487        // we don't show the ID, because the Display impl for Policy itself shows the ID
488        if self.is_empty() {
489            write!(f, "<empty policyset>")
490        } else {
491            write!(
492                f,
493                "Templates:\n{}, Template Linked Policies:\n{}",
494                self.all_templates().join("\n"),
495                self.policies().join("\n")
496            )
497        }
498    }
499}
500
501// PANIC SAFETY tests
502#[allow(clippy::panic)]
503// PANIC SAFETY tests
504#[allow(clippy::indexing_slicing)]
505#[cfg(test)]
506mod test {
507    use super::*;
508    use crate::{
509        ast::{
510            ActionConstraint, Annotations, Effect, Expr, PrincipalConstraint, ResourceConstraint,
511        },
512        parser,
513    };
514    use std::collections::HashMap;
515
516    #[test]
517    fn link_conflicts() {
518        let mut pset = PolicySet::new();
519        let p1 = parser::parse_policy(
520            Some(PolicyID::from_string("id")),
521            "permit(principal,action,resource);",
522        )
523        .expect("Failed to parse");
524        pset.add_static(p1).expect("Failed to add!");
525        let template = parser::parse_policy_or_template(
526            Some(PolicyID::from_string("t")),
527            "permit(principal == ?principal, action, resource);",
528        )
529        .expect("Failed to parse");
530        pset.add_template(template).expect("Add failed");
531
532        let env: HashMap<SlotId, EntityUID> = [(
533            SlotId::principal(),
534            r#"Test::"test""#.parse().expect("Failed to parse"),
535        )]
536        .into_iter()
537        .collect();
538
539        let r = pset.link(PolicyID::from_string("t"), PolicyID::from_string("id"), env);
540
541        match r {
542            Ok(_) => panic!("Should have failed due to conflict"),
543            Err(LinkingError::PolicyIdConflict { id }) => {
544                assert_eq!(id, PolicyID::from_string("id"))
545            }
546            Err(e) => panic!("Incorrect error: {e}"),
547        };
548    }
549
550    /// This test focuses on `PolicySet::add()`, while other tests mostly use
551    /// `PolicySet::add_static()` and `PolicySet::link()`.
552    #[test]
553    fn policyset_add() {
554        let mut pset = PolicySet::new();
555        let static_policy = parser::parse_policy(
556            Some(PolicyID::from_string("id")),
557            "permit(principal,action,resource);",
558        )
559        .expect("Failed to parse");
560        let static_policy: Policy = static_policy.into();
561        pset.add(static_policy)
562            .expect("Adding static policy in Policy form should succeed");
563
564        let template = Arc::new(
565            parser::parse_policy_or_template(
566                Some(PolicyID::from_string("t")),
567                "permit(principal == ?principal, action, resource);",
568            )
569            .expect("Failed to parse"),
570        );
571        let env1: HashMap<SlotId, EntityUID> = [(
572            SlotId::principal(),
573            r#"Test::"test1""#.parse().expect("Failed to parse"),
574        )]
575        .into_iter()
576        .collect();
577
578        let p1 = Template::link(Arc::clone(&template), PolicyID::from_string("link"), env1)
579            .expect("Failed to link");
580        pset.add(p1).expect(
581            "Adding link should succeed, even though the template wasn't previously in the pset",
582        );
583        assert!(
584            pset.get_template_arc(&PolicyID::from_string("t")).is_some(),
585            "Adding link should implicitly add the template"
586        );
587
588        let env2: HashMap<SlotId, EntityUID> = [(
589            SlotId::principal(),
590            r#"Test::"test2""#.parse().expect("Failed to parse"),
591        )]
592        .into_iter()
593        .collect();
594
595        let p2 = Template::link(
596            Arc::clone(&template),
597            PolicyID::from_string("link"),
598            env2.clone(),
599        )
600        .expect("Failed to link");
601        match pset.add(p2) {
602            Ok(_) => panic!("Should have failed due to conflict with existing link id"),
603            Err(PolicySetError::Occupied { id }) => assert_eq!(id, PolicyID::from_string("link")),
604        }
605
606        let p3 = Template::link(Arc::clone(&template), PolicyID::from_string("link2"), env2)
607            .expect("Failed to link");
608        pset.add(p3).expect(
609            "Adding link should succeed, even though the template already existed in the pset",
610        );
611
612        let template2 = Arc::new(
613            parser::parse_policy_or_template(
614                Some(PolicyID::from_string("t")),
615                "forbid(principal, action, resource == ?resource);",
616            )
617            .expect("Failed to parse"),
618        );
619        let env3: HashMap<SlotId, EntityUID> = [(
620            SlotId::resource(),
621            r#"Test::"test3""#.parse().expect("Failed to parse"),
622        )]
623        .into_iter()
624        .collect();
625
626        let p4 = Template::link(
627            Arc::clone(&template2),
628            PolicyID::from_string("unique3"),
629            env3,
630        )
631        .expect("Failed to link");
632        match pset.add(p4) {
633            Ok(_) => panic!("Should have failed due to conflict on template id"),
634            Err(PolicySetError::Occupied { id }) => {
635                assert_eq!(id, PolicyID::from_string("t"))
636            }
637        }
638    }
639
640    #[test]
641    fn policy_conflicts() {
642        let mut pset = PolicySet::new();
643        let p1 = parser::parse_policy(
644            Some(PolicyID::from_string("id")),
645            "permit(principal,action,resource);",
646        )
647        .expect("Failed to parse");
648        let p2 = parser::parse_policy(
649            Some(PolicyID::from_string("id")),
650            "permit(principal,action,resource) when { false };",
651        )
652        .expect("Failed to parse");
653        pset.add_static(p1).expect("Failed to add!");
654        match pset.add_static(p2) {
655            Ok(_) => panic!("Should have failed to due name conflict"),
656            Err(PolicySetError::Occupied { id }) => assert_eq!(id, PolicyID::from_string("id")),
657        }
658    }
659
660    #[test]
661    fn template_filtering() {
662        let template = parser::parse_policy_or_template(
663            Some(PolicyID::from_string("template")),
664            "permit(principal == ?principal, action, resource);",
665        )
666        .expect("Template Parse Failure");
667        let static_policy = parser::parse_policy(
668            Some(PolicyID::from_string("static")),
669            "permit(principal, action, resource);",
670        )
671        .expect("Static parse failure");
672        let mut set = PolicySet::new();
673        set.add_template(template).unwrap();
674        set.add_static(static_policy).unwrap();
675
676        assert_eq!(set.all_templates().count(), 2);
677        assert_eq!(set.templates().count(), 1);
678        assert_eq!(set.static_policies().count(), 1);
679        assert_eq!(set.policies().count(), 1);
680        set.link(
681            PolicyID::from_string("template"),
682            PolicyID::from_string("id"),
683            [(SlotId::principal(), EntityUID::with_eid("eid"))]
684                .into_iter()
685                .collect(),
686        )
687        .expect("Linking failed!");
688        assert_eq!(set.static_policies().count(), 1);
689        assert_eq!(set.policies().count(), 2);
690    }
691
692    #[test]
693    fn linking_missing_template() {
694        let tid = PolicyID::from_string("template");
695        let lid = PolicyID::from_string("link");
696        let t = Template::new(
697            tid.clone(),
698            None,
699            Annotations::new(),
700            Effect::Permit,
701            PrincipalConstraint::any(),
702            ActionConstraint::any(),
703            ResourceConstraint::any(),
704            Expr::val(true),
705        );
706
707        let mut s = PolicySet::new();
708        let e = s
709            .link(tid.clone(), lid.clone(), HashMap::new())
710            .expect_err("Should fail");
711
712        match e {
713            LinkingError::NoSuchTemplate { id } => assert_eq!(tid, id),
714            e => panic!("Wrong error {e}"),
715        };
716
717        s.add_template(t).unwrap();
718        s.link(tid, lid, HashMap::new()).expect("Should succeed");
719    }
720
721    #[test]
722    fn linkinv_valid_link() {
723        let tid = PolicyID::from_string("template");
724        let lid = PolicyID::from_string("link");
725        let t = Template::new(
726            tid.clone(),
727            None,
728            Annotations::new(),
729            Effect::Permit,
730            PrincipalConstraint::is_eq_slot(),
731            ActionConstraint::any(),
732            ResourceConstraint::is_in_slot(),
733            Expr::val(true),
734        );
735
736        let mut s = PolicySet::new();
737        s.add_template(t).unwrap();
738
739        let mut vals = HashMap::new();
740        vals.insert(SlotId::principal(), EntityUID::with_eid("p"));
741        vals.insert(SlotId::resource(), EntityUID::with_eid("a"));
742
743        s.link(tid.clone(), lid.clone(), vals).expect("Should link");
744
745        let v: Vec<_> = s.policies().collect();
746
747        assert_eq!(v[0].id(), &lid);
748        assert_eq!(v[0].template().id(), &tid);
749    }
750
751    #[test]
752    fn linking_empty_set() {
753        let s = PolicySet::new();
754        assert_eq!(s.policies().count(), 0);
755    }
756
757    #[test]
758    fn linking_raw_policy() {
759        let mut s = PolicySet::new();
760        let id = PolicyID::from_string("id");
761        let p = StaticPolicy::new(
762            id.clone(),
763            None,
764            Annotations::new(),
765            Effect::Forbid,
766            PrincipalConstraint::any(),
767            ActionConstraint::any(),
768            ResourceConstraint::any(),
769            Expr::val(true),
770        )
771        .expect("Policy Creation Failed");
772        s.add_static(p).unwrap();
773
774        let mut iter = s.policies();
775        match iter.next() {
776            Some(pol) => {
777                assert_eq!(pol.id(), &id);
778                assert_eq!(pol.effect(), Effect::Forbid);
779                assert!(pol.env().is_empty())
780            }
781            None => panic!("Linked Record Not Present"),
782        };
783    }
784
785    #[test]
786    fn link_slotmap() {
787        let mut s = PolicySet::new();
788        let template_id = PolicyID::from_string("template");
789        let link_id = PolicyID::from_string("link");
790        let t = Template::new(
791            template_id.clone(),
792            None,
793            Annotations::new(),
794            Effect::Forbid,
795            PrincipalConstraint::is_eq_slot(),
796            ActionConstraint::any(),
797            ResourceConstraint::any(),
798            Expr::val(true),
799        );
800        s.add_template(t).unwrap();
801
802        let mut v = HashMap::new();
803        let entity = EntityUID::with_eid("eid");
804        v.insert(SlotId::principal(), entity.clone());
805        s.link(template_id.clone(), link_id.clone(), v)
806            .expect("Linking failed!");
807
808        let link = s.get(&link_id).expect("Link should exist");
809        assert_eq!(&link_id, link.id());
810        assert_eq!(&template_id, link.template().id());
811        assert_eq!(
812            &entity,
813            link.env()
814                .get(&SlotId::principal())
815                .expect("Mapping was incorrect")
816        );
817    }
818
819    #[test]
820    fn policy_sets() {
821        let mut pset = PolicySet::new();
822        assert!(pset.is_empty());
823        let id1 = PolicyID::from_string("id1");
824        let tid1 = PolicyID::from_string("template");
825        let policy1 = StaticPolicy::new(
826            id1.clone(),
827            None,
828            Annotations::new(),
829            Effect::Permit,
830            PrincipalConstraint::any(),
831            ActionConstraint::any(),
832            ResourceConstraint::any(),
833            Expr::val(true),
834        )
835        .expect("Policy Creation Failed");
836        let template1 = Template::new(
837            tid1.clone(),
838            None,
839            Annotations::new(),
840            Effect::Permit,
841            PrincipalConstraint::any(),
842            ActionConstraint::any(),
843            ResourceConstraint::any(),
844            Expr::val(true),
845        );
846        let added = pset.add_static(policy1.clone()).is_ok();
847        assert!(added);
848        let added = pset.add_static(policy1).is_ok();
849        assert!(!added);
850        let added = pset.add_template(template1.clone()).is_ok();
851        assert!(added);
852        let added = pset.add_template(template1).is_ok();
853        assert!(!added);
854        assert!(!pset.is_empty());
855        let id2 = PolicyID::from_string("id2");
856        let policy2 = StaticPolicy::new(
857            id2.clone(),
858            None,
859            Annotations::new(),
860            Effect::Forbid,
861            PrincipalConstraint::is_eq(Arc::new(EntityUID::with_eid("jane"))),
862            ActionConstraint::any(),
863            ResourceConstraint::any(),
864            Expr::val(true),
865        )
866        .expect("Policy Creation Failed");
867        let added = pset.add_static(policy2).is_ok();
868        assert!(added);
869
870        let tid2 = PolicyID::from_string("template2");
871        let template2 = Template::new(
872            tid2.clone(),
873            None,
874            Annotations::new(),
875            Effect::Permit,
876            PrincipalConstraint::is_eq_slot(),
877            ActionConstraint::any(),
878            ResourceConstraint::any(),
879            Expr::val(true),
880        );
881        let id3 = PolicyID::from_string("link");
882        let added = pset.add_template(template2).is_ok();
883        assert!(added);
884
885        let r = pset.link(
886            tid2.clone(),
887            id3.clone(),
888            HashMap::from([(SlotId::principal(), EntityUID::with_eid("example"))]),
889        );
890        r.expect("Linking failed");
891
892        assert_eq!(pset.get(&id1).expect("should find the policy").id(), &id1);
893        assert_eq!(pset.get(&id2).expect("should find the policy").id(), &id2);
894        assert_eq!(pset.get(&id3).expect("should find link").id(), &id3);
895        assert_eq!(
896            pset.get(&id3).expect("should find link").template().id(),
897            &tid2
898        );
899        assert!(pset.get(&tid2).is_none());
900        assert!(pset.get_template_arc(&id1).is_some()); // Static policies are also templates
901        assert!(pset.get_template_arc(&id2).is_some()); // Static policies are also templates
902        assert!(pset.get_template_arc(&tid2).is_some());
903        assert_eq!(pset.policies().count(), 3);
904
905        assert_eq!(
906            pset.get_template_arc(&tid1)
907                .expect("should find the template")
908                .id(),
909            &tid1
910        );
911        assert!(pset.get(&tid1).is_none());
912        assert_eq!(pset.all_templates().count(), 4);
913    }
914}