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