use super::{
EntityUID, LinkingError, LiteralPolicy, Policy, PolicyID, ReificationError, SlotId,
StaticPolicy, Template,
};
use itertools::Itertools;
use linked_hash_map::{Entry, LinkedHashMap};
use linked_hash_set::LinkedHashSet;
use miette::Diagnostic;
use smol_str::format_smolstr;
use std::{borrow::Borrow, collections::HashMap, sync::Arc};
use thiserror::Error;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct PolicySet {
templates: LinkedHashMap<PolicyID, Arc<Template>>,
links: LinkedHashMap<PolicyID, Policy>,
template_to_links_map: LinkedHashMap<PolicyID, LinkedHashSet<PolicyID>>,
}
#[derive(Debug)]
pub struct LiteralPolicySet {
templates: LinkedHashMap<PolicyID, Template>,
links: LinkedHashMap<PolicyID, LiteralPolicy>,
}
impl LiteralPolicySet {
pub fn new(
templates: impl IntoIterator<Item = (PolicyID, Template)>,
links: impl IntoIterator<Item = (PolicyID, LiteralPolicy)>,
) -> Self {
Self {
templates: templates.into_iter().collect(),
links: links.into_iter().collect(),
}
}
pub fn templates(&self) -> impl Iterator<Item = &Template> {
self.templates.values()
}
pub fn policies(&self) -> impl Iterator<Item = &LiteralPolicy> {
self.links.values()
}
}
impl TryFrom<LiteralPolicySet> for PolicySet {
type Error = ReificationError;
fn try_from(pset: LiteralPolicySet) -> Result<Self, Self::Error> {
let templates = pset
.templates
.into_iter()
.map(|(id, template)| (id, Arc::new(template)))
.collect::<LinkedHashMap<PolicyID, Arc<Template>>>();
let links = pset
.links
.into_iter()
.map(|(id, literal)| literal.reify(&templates).map(|linked| (id, linked)))
.collect::<Result<LinkedHashMap<PolicyID, Policy>, ReificationError>>()?;
let mut template_to_links_map = LinkedHashMap::new();
for template in &templates {
template_to_links_map.insert(template.0.clone(), LinkedHashSet::new());
}
for (link_id, link) in &links {
let template = link.template().id();
match template_to_links_map.entry(template.clone()) {
Entry::Occupied(t) => t.into_mut().insert(link_id.clone()),
Entry::Vacant(_) => return Err(ReificationError::NoSuchTemplate(template.clone())),
};
}
Ok(Self {
templates,
links,
template_to_links_map,
})
}
}
impl From<PolicySet> for LiteralPolicySet {
fn from(pset: PolicySet) -> Self {
let templates = pset
.templates
.into_iter()
.map(|(id, template)| (id, template.as_ref().clone()))
.collect();
let links = pset
.links
.into_iter()
.map(|(id, p)| (id, p.into()))
.collect();
Self { templates, links }
}
}
#[derive(Debug, Diagnostic, Error)]
pub enum PolicySetError {
#[error("duplicate template or policy id `{id}`")]
Occupied {
id: PolicyID,
},
}
#[derive(Debug, Diagnostic, Error)]
pub enum PolicySetGetLinksError {
#[error("No template `{0}`")]
MissingTemplate(PolicyID),
}
#[derive(Debug, Diagnostic, Error)]
pub enum PolicySetUnlinkError {
#[error("unable to unlink policy id `{0}` because it does not exist")]
UnlinkingError(PolicyID),
#[error("unable to remove link with policy id `{0}` because it is a static policy")]
NotLinkError(PolicyID),
}
#[derive(Debug, Diagnostic, Error)]
pub enum PolicySetTemplateRemovalError {
#[error("unable to remove template id `{0}` from template list because it does not exist")]
RemovePolicyNoTemplateError(PolicyID),
#[error(
"unable to remove template id `{0}` from template list because it still has active links"
)]
RemoveTemplateWithLinksError(PolicyID),
#[error("unable to remove template with policy id `{0}` because it is a static policy")]
NotTemplateError(PolicyID),
}
#[derive(Debug, Diagnostic, Error)]
pub enum PolicySetPolicyRemovalError {
#[error("unable to remove static policy id `{0}` from link list because it does not exist")]
RemovePolicyNoLinkError(PolicyID),
#[error(
"unable to remove static policy id `{0}` from template list because it does not exist"
)]
RemovePolicyNoTemplateError(PolicyID),
}
impl PolicySet {
pub fn new() -> Self {
Self {
templates: LinkedHashMap::new(),
links: LinkedHashMap::new(),
template_to_links_map: LinkedHashMap::new(),
}
}
pub fn singleton(p: Policy) -> Self {
let t = p.template_arc();
let templates = [(t.id().clone(), t.clone())].into_iter().collect();
let template_to_links_map = [(t.id().clone(), [p.id().clone()].into_iter().collect())]
.into_iter()
.collect();
let links = [(p.id().clone(), p)].into_iter().collect();
Self {
templates,
links,
template_to_links_map,
}
}
pub fn add(&mut self, policy: Policy) -> Result<(), PolicySetError> {
let t = policy.template_arc();
let template_ventry = match self.templates.entry(t.id().clone()) {
Entry::Vacant(ventry) => Some(ventry),
Entry::Occupied(oentry) => {
if oentry.get() != &t {
return Err(PolicySetError::Occupied {
id: oentry.key().clone(),
});
}
None
}
};
let link_ventry = match self.links.entry(policy.id().clone()) {
Entry::Vacant(ventry) => Some(ventry),
Entry::Occupied(oentry) => {
return Err(PolicySetError::Occupied {
id: oentry.key().clone(),
});
}
};
if let Some(ventry) = template_ventry {
self.template_to_links_map
.insert(t.id().clone(), [policy.id().clone()].into_iter().collect());
ventry.insert(t);
} else {
self.template_to_links_map
.entry(t.id().clone())
.or_default()
.insert(policy.id().clone());
}
if let Some(ventry) = link_ventry {
ventry.insert(policy);
}
Ok(())
}
fn policy_id_is_bound(&self, pid: &PolicyID) -> bool {
self.templates.contains_key(pid) || self.links.contains_key(pid)
}
fn get_fresh_id(&self, other: &Self, start_ind: &mut u32) -> PolicyID {
let mut new_pid = PolicyID::from_smolstr(format_smolstr!("policy{}", start_ind));
*start_ind += 1;
while self.policy_id_is_bound(&new_pid) || other.policy_id_is_bound(&new_pid) {
new_pid = PolicyID::from_smolstr(format_smolstr!("policy{}", start_ind));
*start_ind += 1;
}
new_pid
}
fn update_renaming<T>(
&self,
this_contents: &LinkedHashMap<PolicyID, T>,
other: &Self,
other_contents: &LinkedHashMap<PolicyID, T>,
renaming: &mut LinkedHashMap<PolicyID, PolicyID>,
start_ind: &mut u32,
) where
T: PartialEq + Clone,
{
for (pid, ot) in other_contents {
if let Some(tt) = this_contents.get(pid) {
if tt != ot && !renaming.contains_key(pid) {
let new_pid = self.get_fresh_id(other, start_ind);
renaming.insert(pid.clone(), new_pid);
}
}
}
}
pub fn merge_policyset(
&mut self,
other: &PolicySet,
rename_duplicates: bool,
) -> Result<LinkedHashMap<PolicyID, PolicyID>, PolicySetError> {
let mut min_id = 0;
let mut renaming = LinkedHashMap::new();
self.update_renaming(
&self.templates,
other,
&other.templates,
&mut renaming,
&mut min_id,
);
self.update_renaming(&self.links, other, &other.links, &mut renaming, &mut min_id);
for (pid, template) in &other.templates {
if !template.is_static() && self.get(pid).is_some() && !renaming.contains_key(pid) {
let new_pid = self.get_fresh_id(other, &mut min_id);
renaming.insert(pid.clone(), new_pid);
}
}
for (pid, link) in &other.links {
if !link.is_static() && self.get_template(pid).is_some() && !renaming.contains_key(pid)
{
let new_pid = self.get_fresh_id(other, &mut min_id);
renaming.insert(pid.clone(), new_pid);
}
}
if !rename_duplicates {
if let Some(pid) = renaming.keys().next() {
return Err(PolicySetError::Occupied { id: pid.clone() });
}
}
for (pid, other_template) in &other.templates {
if let Some(new_pid) = renaming.get(pid) {
self.templates.insert(
new_pid.clone(),
Arc::new(other_template.new_id(new_pid.clone())),
);
} else {
self.templates.insert(pid.clone(), other_template.clone());
}
}
for (pid, other_policy) in &other.links {
let (new_pid, other_policy) = if let Some(new_pid) = renaming.get(pid) {
(new_pid.clone(), other_policy.new_id(new_pid.clone()))
} else {
(pid.clone(), other_policy.clone())
};
let other_policy = match renaming.get(other_policy.template().id()) {
#[expect(
clippy::unwrap_used,
reason = "`if` confirms that `other_policy` is a template link"
)]
Some(new_tid) if !other_policy.is_static() => {
other_policy.new_template_id(new_tid.clone()).unwrap()
}
_ => other_policy,
};
self.links.insert(new_pid, other_policy);
}
for (tid, other_template_link_set) in &other.template_to_links_map {
let tid = renaming.get(tid).unwrap_or(tid);
let mut this_template_link_set =
self.template_to_links_map.remove(tid).unwrap_or_default();
for pid in other_template_link_set {
let pid = renaming.get(pid).unwrap_or(pid);
this_template_link_set.insert(pid.clone());
}
self.template_to_links_map
.insert(tid.clone(), this_template_link_set);
}
Ok(renaming)
}
pub fn remove_static(
&mut self,
policy_id: &PolicyID,
) -> Result<Policy, PolicySetPolicyRemovalError> {
let policy = match self.links.remove(policy_id) {
Some(p) => p,
None => {
return Err(PolicySetPolicyRemovalError::RemovePolicyNoLinkError(
policy_id.clone(),
))
}
};
match self.templates.remove(policy_id) {
Some(_) => {
self.template_to_links_map.remove(policy_id);
Ok(policy)
}
None => {
self.links.insert(policy_id.clone(), policy);
Err(PolicySetPolicyRemovalError::RemovePolicyNoTemplateError(
policy_id.clone(),
))
}
}
}
pub fn add_static(&mut self, policy: StaticPolicy) -> Result<(), PolicySetError> {
let (t, p) = Template::link_static_policy(policy);
match (
self.templates.entry(t.id().clone()),
self.links.entry(t.id().clone()),
) {
(Entry::Vacant(templates_entry), Entry::Vacant(links_entry)) => {
self.template_to_links_map.insert(
t.id().clone(),
vec![p.id().clone()]
.into_iter()
.collect::<LinkedHashSet<PolicyID>>(),
);
templates_entry.insert(t);
links_entry.insert(p);
Ok(())
}
(Entry::Occupied(oentry), _) => Err(PolicySetError::Occupied {
id: oentry.key().clone(),
}),
(_, Entry::Occupied(oentry)) => Err(PolicySetError::Occupied {
id: oentry.key().clone(),
}),
}
}
pub fn add_template(&mut self, t: Template) -> Result<(), PolicySetError> {
if self.links.contains_key(t.id()) {
return Err(PolicySetError::Occupied { id: t.id().clone() });
}
match self.templates.entry(t.id().clone()) {
Entry::Occupied(oentry) => Err(PolicySetError::Occupied {
id: oentry.key().clone(),
}),
Entry::Vacant(ventry) => {
self.template_to_links_map
.insert(t.id().clone(), LinkedHashSet::new());
ventry.insert(Arc::new(t));
Ok(())
}
}
}
pub fn remove_template(
&mut self,
policy_id: &PolicyID,
) -> Result<Template, PolicySetTemplateRemovalError> {
if self.links.contains_key(policy_id) {
return Err(PolicySetTemplateRemovalError::NotTemplateError(
policy_id.clone(),
));
}
match self.template_to_links_map.get(policy_id) {
Some(map) => {
if !map.is_empty() {
return Err(PolicySetTemplateRemovalError::RemoveTemplateWithLinksError(
policy_id.clone(),
));
}
}
None => {
return Err(PolicySetTemplateRemovalError::RemovePolicyNoTemplateError(
policy_id.clone(),
))
}
};
#[expect(clippy::panic, reason = "every linked policy should have a template")]
match self.templates.remove(policy_id) {
Some(t) => {
self.template_to_links_map.remove(policy_id);
Ok(Arc::unwrap_or_clone(t))
}
None => panic!("Found in template_to_links_map but not in templates"),
}
}
pub fn get_linked_policies(
&self,
template_id: &PolicyID,
) -> Result<impl Iterator<Item = &PolicyID>, PolicySetGetLinksError> {
match self.template_to_links_map.get(template_id) {
Some(s) => Ok(s.iter()),
None => Err(PolicySetGetLinksError::MissingTemplate(template_id.clone())),
}
}
pub fn link(
&mut self,
template_id: PolicyID,
new_id: PolicyID,
values: HashMap<SlotId, EntityUID>,
) -> Result<&Policy, LinkingError> {
let t =
self.get_template_arc(&template_id)
.ok_or_else(|| LinkingError::NoSuchTemplate {
id: template_id.clone(),
})?;
let r = Template::link(t, new_id.clone(), values)?;
match (
self.links.entry(new_id.clone()),
self.templates.entry(new_id.clone()),
) {
(Entry::Vacant(links_entry), Entry::Vacant(_)) => {
self.template_to_links_map
.entry(template_id)
.or_default()
.insert(new_id);
Ok(links_entry.insert(r))
}
(Entry::Occupied(oentry), _) => Err(LinkingError::PolicyIdConflict {
id: oentry.key().clone(),
}),
(_, Entry::Occupied(oentry)) => Err(LinkingError::PolicyIdConflict {
id: oentry.key().clone(),
}),
}
}
pub fn unlink(&mut self, policy_id: &PolicyID) -> Result<Policy, PolicySetUnlinkError> {
if self.templates.contains_key(policy_id) {
return Err(PolicySetUnlinkError::NotLinkError(policy_id.clone()));
}
match self.links.remove(policy_id) {
Some(p) => {
#[expect(clippy::panic, reason = "every linked policy should have a template")]
match self.template_to_links_map.entry(p.template().id().clone()) {
Entry::Occupied(t) => t.into_mut().remove(policy_id),
Entry::Vacant(_) => {
panic!("No template found for linked policy")
}
};
Ok(p)
}
None => Err(PolicySetUnlinkError::UnlinkingError(policy_id.clone())),
}
}
pub fn policies(&self) -> impl Iterator<Item = &Policy> {
self.links.values()
}
pub fn into_policies(self) -> impl Iterator<Item = Policy> {
self.links.into_iter().map(|(_, p)| p)
}
pub fn all_templates(&self) -> impl Iterator<Item = &Template> {
self.templates.values().map(|t| t.borrow())
}
pub fn templates(&self) -> impl Iterator<Item = &Template> {
self.all_templates().filter(|t| t.slots().count() != 0)
}
pub fn static_policies(&self) -> impl Iterator<Item = &Policy> {
self.policies().filter(|p| p.is_static())
}
pub fn is_empty(&self) -> bool {
self.templates.is_empty() && self.links.is_empty()
}
pub fn get_template_arc(&self, id: &PolicyID) -> Option<Arc<Template>> {
self.templates.get(id).cloned()
}
pub fn get_template(&self, id: &PolicyID) -> Option<&Template> {
self.templates.get(id).map(AsRef::as_ref)
}
pub fn get(&self, id: &PolicyID) -> Option<&Policy> {
self.links.get(id)
}
pub fn try_from_iter<T: IntoIterator<Item = Policy>>(iter: T) -> Result<Self, PolicySetError> {
let mut set = Self::new();
for p in iter {
set.add(p)?;
}
Ok(set)
}
}
impl std::fmt::Display for PolicySet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_empty() {
write!(f, "<empty policyset>")
} else {
write!(
f,
"Templates:\n{}, Template Linked Policies:\n{}",
self.all_templates().join("\n"),
self.policies().join("\n")
)
}
}
}
#[expect(clippy::panic, clippy::indexing_slicing, reason = "tests")]
#[cfg(test)]
mod test {
use super::*;
use crate::{
ast::{
annotation::Annotations, ActionConstraint, Effect, PrincipalConstraint,
ResourceConstraint,
},
parser,
};
use similar_asserts::assert_eq;
use std::collections::HashMap;
#[test]
fn link_conflicts() {
let mut pset = PolicySet::new();
let p1 = parser::parse_policy(
Some(PolicyID::from_string("id")),
"permit(principal,action,resource);",
)
.expect("Failed to parse");
pset.add_static(p1).expect("Failed to add!");
let template = parser::parse_policy_or_template(
Some(PolicyID::from_string("t")),
"permit(principal == ?principal, action, resource);",
)
.expect("Failed to parse");
pset.add_template(template).expect("Add failed");
let env: HashMap<SlotId, EntityUID> = HashMap::from([(
SlotId::principal(),
r#"Test::"test""#.parse().expect("Failed to parse"),
)]);
let r = pset.link(PolicyID::from_string("t"), PolicyID::from_string("id"), env);
match r {
Ok(_) => panic!("Should have failed due to conflict"),
Err(LinkingError::PolicyIdConflict { id }) => {
assert_eq!(id, PolicyID::from_string("id"))
}
Err(e) => panic!("Incorrect error: {e}"),
};
}
#[test]
fn policyset_add() {
let mut pset = PolicySet::new();
let static_policy = parser::parse_policy(
Some(PolicyID::from_string("id")),
"permit(principal,action,resource);",
)
.expect("Failed to parse");
let static_policy: Policy = static_policy.into();
pset.add(static_policy)
.expect("Adding static policy in Policy form should succeed");
let template = Arc::new(
parser::parse_policy_or_template(
Some(PolicyID::from_string("t")),
"permit(principal == ?principal, action, resource);",
)
.expect("Failed to parse"),
);
let env1: HashMap<SlotId, EntityUID> = HashMap::from([(
SlotId::principal(),
r#"Test::"test1""#.parse().expect("Failed to parse"),
)]);
let p1 = Template::link(Arc::clone(&template), PolicyID::from_string("link"), env1)
.expect("Failed to link");
pset.add(p1).expect(
"Adding link should succeed, even though the template wasn't previously in the pset",
);
assert!(
pset.get_template_arc(&PolicyID::from_string("t")).is_some(),
"Adding link should implicitly add the template"
);
let env2: HashMap<SlotId, EntityUID> = HashMap::from([(
SlotId::principal(),
r#"Test::"test2""#.parse().expect("Failed to parse"),
)]);
let p2 = Template::link(
Arc::clone(&template),
PolicyID::from_string("link"),
env2.clone(),
)
.expect("Failed to link");
match pset.add(p2) {
Ok(_) => panic!("Should have failed due to conflict with existing link id"),
Err(PolicySetError::Occupied { id }) => assert_eq!(id, PolicyID::from_string("link")),
}
let p3 = Template::link(Arc::clone(&template), PolicyID::from_string("link2"), env2)
.expect("Failed to link");
pset.add(p3).expect(
"Adding link should succeed, even though the template already existed in the pset",
);
let template2 = Arc::new(
parser::parse_policy_or_template(
Some(PolicyID::from_string("t")),
"forbid(principal, action, resource == ?resource);",
)
.expect("Failed to parse"),
);
let env3: HashMap<SlotId, EntityUID> = HashMap::from([(
SlotId::resource(),
r#"Test::"test3""#.parse().expect("Failed to parse"),
)]);
let p4 = Template::link(
Arc::clone(&template2),
PolicyID::from_string("unique3"),
env3,
)
.expect("Failed to link");
match pset.add(p4) {
Ok(_) => panic!("Should have failed due to conflict on template id"),
Err(PolicySetError::Occupied { id }) => {
assert_eq!(id, PolicyID::from_string("t"))
}
}
}
#[test]
fn policy_set_singleton_static() {
let policy: Policy = parser::parse_policy(
Some(PolicyID::from_string("id")),
"permit(principal,action,resource);",
)
.unwrap()
.into();
let pset_singleton = PolicySet::singleton(policy.clone());
let mut pset_add = PolicySet::new();
pset_add.add(policy).unwrap();
assert_eq!(pset_singleton, pset_add);
}
#[test]
fn policy_set_singleton_link() {
let template = Arc::new(
parser::parse_policy_or_template(
Some(PolicyID::from_string("t")),
"permit(principal == ?principal, action, resource);",
)
.expect("Failed to parse"),
);
let env1 = HashMap::from([(
SlotId::principal(),
r#"Test::"test1""#.parse().expect("Failed to parse"),
)]);
let policy =
Template::link(template, PolicyID::from_string("link"), env1).expect("Failed to link");
let pset_singleton = PolicySet::singleton(policy.clone());
let mut pset_add = PolicySet::new();
pset_add.add(policy).unwrap();
assert_eq!(pset_singleton, pset_add);
}
#[test]
fn policy_merge_no_conflicts() {
let p1 = parser::parse_policy(
Some(PolicyID::from_string("policy0")),
"permit(principal,action,resource);",
)
.expect("Failed to parse");
let p2 = parser::parse_policy(
Some(PolicyID::from_string("policy1")),
"permit(principal,action,resource) when { false };",
)
.expect("Failed to parse");
let p3 = parser::parse_policy(
Some(PolicyID::from_string("policy0")),
"permit(principal,action,resource);",
)
.expect("Failed to parse");
let p4 = parser::parse_policy(
Some(PolicyID::from_string("policy2")),
"permit(principal,action,resource) when { true };",
)
.expect("Failed to parse");
let mut pset1 = PolicySet::new();
let mut pset2 = PolicySet::new();
pset1.add_static(p1).expect("Failed to add!");
pset1.add_static(p2).expect("Failed to add!");
pset2.add_static(p3).expect("Failed to add!");
pset2.add_static(p4).expect("Failed to add!");
match pset1.merge_policyset(&pset2, false) {
Ok(_) => (),
Err(PolicySetError::Occupied { id }) => {
panic!("There should not have been an error! Unexpected conflict for id {id}")
}
}
}
#[test]
fn policy_merge_with_conflicts() {
let pid0 = PolicyID::from_string("policy0");
let pid1 = PolicyID::from_string("policy1");
let pid2 = PolicyID::from_string("policy2");
let p1 = parser::parse_policy(Some(pid0.clone()), "permit(principal,action,resource);")
.expect("Failed to parse");
let p2 = parser::parse_policy(
Some(pid1.clone()),
"permit(principal,action,resource) when { false };",
)
.expect("Failed to parse");
let p3 = parser::parse_policy(Some(pid1.clone()), "permit(principal,action,resource);")
.expect("Failed to parse");
let p4 = parser::parse_policy(
Some(pid2.clone()),
"permit(principal,action,resource) when { true };",
)
.expect("Failed to parse");
let mut pset1 = PolicySet::new();
let mut pset2 = PolicySet::new();
pset1.add_static(p1.clone()).expect("Failed to add!");
pset1.add_static(p2.clone()).expect("Failed to add!");
pset2.add_static(p3.clone()).expect("Failed to add!");
pset2.add_static(p4.clone()).expect("Failed to add!");
match pset1.merge_policyset(&pset2, false) {
Ok(_) => panic!("`pset1` and `pset2` should conflict for PolicyID `policy1`"),
Err(PolicySetError::Occupied { id }) => {
assert_eq!(id, PolicyID::from_string("policy1"));
}
}
match pset1.merge_policyset(&pset2, true) {
Ok(renaming) => {
let new_pid1 = match renaming.get(&pid1) {
Some(new_pid1) => new_pid1,
None => panic!("Error: `policy1` is a conflict and should be renamed"),
};
assert_eq!(renaming.keys().len(), 1);
if let Some(new_p1) = pset1.get(&pid0) {
assert_eq!(Policy::from(p1), new_p1.clone());
}
if let Some(new_p2) = pset1.get(&pid1) {
assert_eq!(Policy::from(p2), new_p2.clone());
}
if let Some(new_p3) = pset1.get(new_pid1) {
assert_eq!(Policy::from(p3).new_id(new_pid1.clone()), new_p3.clone());
}
if let Some(new_p4) = pset1.get(&pid2) {
assert_eq!(Policy::from(p4), new_p4.clone());
}
}
Err(PolicySetError::Occupied { id }) => {
panic!("There should not have been an error! Unexpected conflict for id {id}")
}
}
}
#[test]
fn policy_conflicts() {
let mut pset = PolicySet::new();
let p1 = parser::parse_policy(
Some(PolicyID::from_string("id")),
"permit(principal,action,resource);",
)
.expect("Failed to parse");
let p2 = parser::parse_policy(
Some(PolicyID::from_string("id")),
"permit(principal,action,resource) when { false };",
)
.expect("Failed to parse");
pset.add_static(p1).expect("Failed to add!");
match pset.add_static(p2) {
Ok(_) => panic!("Should have failed to due name conflict"),
Err(PolicySetError::Occupied { id }) => assert_eq!(id, PolicyID::from_string("id")),
}
}
#[test]
fn template_filtering() {
let template = parser::parse_policy_or_template(
Some(PolicyID::from_string("template")),
"permit(principal == ?principal, action, resource);",
)
.expect("Template Parse Failure");
let static_policy = parser::parse_policy(
Some(PolicyID::from_string("static")),
"permit(principal, action, resource);",
)
.expect("Static parse failure");
let mut set = PolicySet::new();
set.add_template(template).unwrap();
set.add_static(static_policy).unwrap();
assert_eq!(set.all_templates().count(), 2);
assert_eq!(set.templates().count(), 1);
assert_eq!(set.static_policies().count(), 1);
assert_eq!(set.policies().count(), 1);
set.link(
PolicyID::from_string("template"),
PolicyID::from_string("id"),
HashMap::from([(SlotId::principal(), EntityUID::with_eid("eid"))]),
)
.expect("Linking failed!");
assert_eq!(set.static_policies().count(), 1);
assert_eq!(set.policies().count(), 2);
}
#[test]
fn linking_missing_template() {
let tid = PolicyID::from_string("template");
let lid = PolicyID::from_string("link");
let t = Template::new(
tid.clone(),
None,
Annotations::new(),
Effect::Permit,
PrincipalConstraint::any(),
ActionConstraint::any(),
ResourceConstraint::any(),
None,
);
let mut s = PolicySet::new();
let e = s
.link(tid.clone(), lid.clone(), HashMap::new())
.expect_err("Should fail");
match e {
LinkingError::NoSuchTemplate { id } => assert_eq!(tid, id),
e => panic!("Wrong error {e}"),
};
s.add_template(t).unwrap();
s.link(tid, lid, HashMap::new()).expect("Should succeed");
}
#[test]
fn linkinv_valid_link() {
let tid = PolicyID::from_string("template");
let lid = PolicyID::from_string("link");
let t = Template::new(
tid.clone(),
None,
Annotations::new(),
Effect::Permit,
PrincipalConstraint::is_eq_slot(),
ActionConstraint::any(),
ResourceConstraint::is_in_slot(),
None,
);
let mut s = PolicySet::new();
s.add_template(t).unwrap();
let mut vals = HashMap::new();
vals.insert(SlotId::principal(), EntityUID::with_eid("p"));
vals.insert(SlotId::resource(), EntityUID::with_eid("a"));
s.link(tid.clone(), lid.clone(), vals).expect("Should link");
let v: Vec<_> = s.policies().collect();
assert_eq!(v[0].id(), &lid);
assert_eq!(v[0].template().id(), &tid);
}
#[test]
fn linking_empty_set() {
let s = PolicySet::new();
assert_eq!(s.policies().count(), 0);
}
#[test]
fn linking_raw_policy() {
let mut s = PolicySet::new();
let id = PolicyID::from_string("id");
let p = StaticPolicy::new(
id.clone(),
None,
Annotations::new(),
Effect::Forbid,
PrincipalConstraint::any(),
ActionConstraint::any(),
ResourceConstraint::any(),
None,
)
.expect("Policy Creation Failed");
s.add_static(p).unwrap();
let mut iter = s.policies();
match iter.next() {
Some(pol) => {
assert_eq!(pol.id(), &id);
assert_eq!(pol.effect(), Effect::Forbid);
assert!(pol.env().is_empty())
}
None => panic!("Linked Record Not Present"),
};
}
#[test]
fn link_slotmap() {
let mut s = PolicySet::new();
let template_id = PolicyID::from_string("template");
let link_id = PolicyID::from_string("link");
let t = Template::new(
template_id.clone(),
None,
Annotations::new(),
Effect::Forbid,
PrincipalConstraint::is_eq_slot(),
ActionConstraint::any(),
ResourceConstraint::any(),
None,
);
s.add_template(t).unwrap();
let mut v = HashMap::new();
let entity = EntityUID::with_eid("eid");
v.insert(SlotId::principal(), entity.clone());
s.link(template_id.clone(), link_id.clone(), v)
.expect("Linking failed!");
let link = s.get(&link_id).expect("Link should exist");
assert_eq!(&link_id, link.id());
assert_eq!(&template_id, link.template().id());
assert_eq!(
&entity,
link.env()
.get(&SlotId::principal())
.expect("Mapping was incorrect")
);
}
#[test]
fn policy_sets() {
let mut pset = PolicySet::new();
assert!(pset.is_empty());
let id1 = PolicyID::from_string("id1");
let tid1 = PolicyID::from_string("template");
let policy1 = StaticPolicy::new(
id1.clone(),
None,
Annotations::new(),
Effect::Permit,
PrincipalConstraint::any(),
ActionConstraint::any(),
ResourceConstraint::any(),
None,
)
.expect("Policy Creation Failed");
let template1 = Template::new(
tid1.clone(),
None,
Annotations::new(),
Effect::Permit,
PrincipalConstraint::any(),
ActionConstraint::any(),
ResourceConstraint::any(),
None,
);
let added = pset.add_static(policy1.clone()).is_ok();
assert!(added);
let added = pset.add_static(policy1).is_ok();
assert!(!added);
let added = pset.add_template(template1.clone()).is_ok();
assert!(added);
let added = pset.add_template(template1).is_ok();
assert!(!added);
assert!(!pset.is_empty());
let id2 = PolicyID::from_string("id2");
let policy2 = StaticPolicy::new(
id2.clone(),
None,
Annotations::new(),
Effect::Forbid,
PrincipalConstraint::is_eq(Arc::new(EntityUID::with_eid("jane"))),
ActionConstraint::any(),
ResourceConstraint::any(),
None,
)
.expect("Policy Creation Failed");
let added = pset.add_static(policy2).is_ok();
assert!(added);
let tid2 = PolicyID::from_string("template2");
let template2 = Template::new(
tid2.clone(),
None,
Annotations::new(),
Effect::Permit,
PrincipalConstraint::is_eq_slot(),
ActionConstraint::any(),
ResourceConstraint::any(),
None,
);
let id3 = PolicyID::from_string("link");
let added = pset.add_template(template2).is_ok();
assert!(added);
let r = pset.link(
tid2.clone(),
id3.clone(),
HashMap::from([(SlotId::principal(), EntityUID::with_eid("example"))]),
);
r.expect("Linking failed");
assert_eq!(pset.get(&id1).expect("should find the policy").id(), &id1);
assert_eq!(pset.get(&id2).expect("should find the policy").id(), &id2);
assert_eq!(pset.get(&id3).expect("should find link").id(), &id3);
assert_eq!(
pset.get(&id3).expect("should find link").template().id(),
&tid2
);
assert!(pset.get(&tid2).is_none());
assert!(pset.get_template_arc(&id1).is_some()); assert!(pset.get_template_arc(&id2).is_some()); assert!(pset.get_template_arc(&tid2).is_some());
assert_eq!(pset.policies().count(), 3);
assert_eq!(
pset.get_template_arc(&tid1)
.expect("should find the template")
.id(),
&tid1
);
assert!(pset.get(&tid1).is_none());
assert_eq!(pset.all_templates().count(), 4);
}
#[test]
fn policy_set_insertion_order() {
let mut pset = PolicySet::new();
assert!(pset.is_empty());
let src = "permit(principal, action, resource);";
let ids: Vec<PolicyID> = (1..=4)
.map(|i| {
let id = PolicyID::from_string(format!("id{i}"));
let p = parser::parse_policy(Some(id.clone()), src).unwrap();
let added = pset.add(p.into()).is_ok();
assert!(added);
id
})
.collect();
assert_eq!(
pset.into_policies()
.map(|p| p.id().clone())
.collect::<Vec<PolicyID>>(),
ids
);
}
}