use super::FromJsonError;
use super::Policy;
use crate::ast;
use crate::ast::EntityUID;
use crate::ast::{PolicyID, SlotId};
use crate::entities::EntityUidJson;
use crate::entities::JsonDeserializationErrorContext;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use std::collections::HashMap;
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct PolicySet {
#[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
pub templates: HashMap<PolicyID, Policy>,
#[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
pub static_policies: HashMap<PolicyID, Policy>,
pub template_links: Vec<TemplateLink>,
}
impl PolicySet {
pub fn get_policy(&self, id: &PolicyID) -> Option<Policy> {
let maybe_static_policy = self.static_policies.get(id).cloned();
let maybe_link = self
.template_links
.iter()
.filter_map(|link| {
if &link.new_id == id {
self.get_template(&link.template_id).and_then(|template| {
let unwrapped_est_vals: HashMap<SlotId, EntityUidJson> =
link.values.iter().map(|(k, v)| (*k, v.into())).collect();
template.link(&unwrapped_est_vals).ok()
})
} else {
None
}
})
.next();
maybe_static_policy.or(maybe_link)
}
pub fn get_template(&self, id: &PolicyID) -> Option<Policy> {
self.templates.get(id).cloned()
}
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct TemplateLink {
pub template_id: PolicyID,
pub new_id: PolicyID,
#[serde_as(as = "serde_with::MapPreventDuplicates<_,EntityUidJson<TemplateLinkContext>>")]
pub values: HashMap<SlotId, EntityUID>,
}
struct TemplateLinkContext;
impl crate::entities::DeserializationContext for TemplateLinkContext {
fn static_context() -> Option<JsonDeserializationErrorContext> {
Some(JsonDeserializationErrorContext::TemplateLink)
}
}
impl TryFrom<PolicySet> for ast::PolicySet {
type Error = FromJsonError;
fn try_from(value: PolicySet) -> Result<Self, Self::Error> {
let mut ast_pset = ast::PolicySet::default();
for (id, policy) in value.templates {
let ast = policy.try_into_ast_policy_or_template(Some(id))?;
ast_pset.add_template(ast)?;
}
for (id, policy) in value.static_policies {
let ast = policy.try_into_ast_policy(Some(id))?;
ast_pset.add(ast)?;
}
for TemplateLink {
template_id,
new_id,
values,
} in value.template_links
{
ast_pset.link(template_id, new_id, values)?;
}
Ok(ast_pset)
}
}
#[cfg(test)]
mod test {
use serde_json::json;
use super::*;
#[test]
fn valid_example() {
let json = json!({
"staticPolicies": {
"policy1": {
"effect": "permit",
"principal": {
"op": "==",
"entity": { "type": "User", "id": "alice" }
},
"action": {
"op": "==",
"entity": { "type": "Action", "id": "view" }
},
"resource": {
"op": "in",
"entity": { "type": "Folder", "id": "foo" }
},
"conditions": []
}
},
"templates": {
"template": {
"effect" : "permit",
"principal" : {
"op" : "==",
"slot" : "?principal"
},
"action" : {
"op" : "All"
},
"resource" : {
"op" : "All",
},
"conditions": []
}
},
"templateLinks" : [
{
"newId" : "link",
"templateId" : "template",
"values" : {
"?principal" : { "type" : "User", "id" : "bob" }
}
}
]
});
let est_policy_set: PolicySet =
serde_json::from_value(json).expect("failed to parse from JSON");
let ast_policy_set: ast::PolicySet =
est_policy_set.try_into().expect("failed to convert to AST");
assert_eq!(ast_policy_set.policies().count(), 2);
assert_eq!(ast_policy_set.templates().count(), 1);
assert!(ast_policy_set
.get_template(&PolicyID::from_string("template"))
.is_some());
let link = ast_policy_set.get(&PolicyID::from_string("link")).unwrap();
assert_eq!(link.template().id(), &PolicyID::from_string("template"));
assert_eq!(
link.env(),
&HashMap::from_iter([(SlotId::principal(), r#"User::"bob""#.parse().unwrap())])
);
assert_eq!(
ast_policy_set
.get_linked_policies(&PolicyID::from_string("template"))
.unwrap()
.count(),
1
);
}
#[test]
fn unknown_field() {
let json = json!({
"staticPolicies": {
"policy1": {
"effect": "permit",
"principal": {
"op": "==",
"entity": { "type": "User", "id": "alice" }
},
"action": {
"op" : "All"
},
"resource": {
"op" : "All"
},
"conditions": []
}
},
"templates": {},
"links" : []
});
let err = serde_json::from_value::<PolicySet>(json)
.expect_err("should have failed to parse from JSON");
assert_eq!(
err.to_string(),
"unknown field `links`, expected one of `templates`, `staticPolicies`, `templateLinks`"
);
}
#[test]
fn duplicate_policy_ids() {
let str = r#"{
"staticPolicies" : {
"policy0": {
"effect": "permit",
"principal": {
"op": "==",
"entity": { "type": "User", "id": "alice" }
},
"action": {
"op" : "All"
},
"resource": {
"op" : "All"
},
"conditions": []
},
"policy0": {
"effect": "permit",
"principal": {
"op": "==",
"entity": { "type": "User", "id": "alice" }
},
"action": {
"op" : "All"
},
"resource": {
"op" : "All"
},
"conditions": []
}
},
"templates" : {},
"templateLinks" : []
}"#;
let err = serde_json::from_str::<PolicySet>(str)
.expect_err("should have failed to parse from JSON");
assert_eq!(
err.to_string(),
"invalid entry: found duplicate key at line 31 column 13"
);
}
#[test]
fn duplicate_slot_ids() {
let str = r#"{
"newId" : "foo",
"templateId" : "bar",
"values" : {
"?principal" : { "type" : "User", "id" : "John" },
"?principal" : { "type" : "User", "id" : "John" },
}
}"#;
let err = serde_json::from_str::<TemplateLink>(str)
.expect_err("should have failed to parse from JSON");
assert_eq!(
err.to_string(),
"invalid entry: found duplicate key at line 6 column 65"
);
}
}