use cedar_policy::{EntityUid, PolicyId, PolicySet, SlotId};
use miette::{IntoDiagnostic, Result, WrapErr};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::Path, str::FromStr};
pub(crate) fn add_template_links_to_set(
path: impl AsRef<Path>,
policy_set: &mut PolicySet,
) -> Result<()> {
for template_linked in load_links_from_file(path)? {
let slot_env = create_slot_env(&template_linked.args)?;
policy_set.link(
PolicyId::new(&template_linked.template_id),
PolicyId::new(&template_linked.link_id),
slot_env,
)?;
}
Ok(())
}
pub(crate) fn create_slot_env(
data: &HashMap<SlotId, String>,
) -> Result<HashMap<SlotId, EntityUid>> {
data.iter()
.map(|(key, value)| Ok(EntityUid::from_str(value).map(|euid| (key.clone(), euid))?))
.collect::<Result<HashMap<SlotId, EntityUid>>>()
}
#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(try_from = "LiteralTemplateLinked")]
#[serde(into = "LiteralTemplateLinked")]
pub(crate) struct TemplateLinked {
pub(crate) template_id: String,
pub(crate) link_id: String,
pub(crate) args: HashMap<SlotId, String>,
}
impl TryFrom<LiteralTemplateLinked> for TemplateLinked {
type Error = String;
fn try_from(value: LiteralTemplateLinked) -> Result<Self, Self::Error> {
Ok(Self {
template_id: value.template_id,
link_id: value.link_id,
args: value
.args
.into_iter()
.map(|(k, v)| parse_slot_id(k).map(|slot_id| (slot_id, v)))
.collect::<Result<HashMap<SlotId, String>, Self::Error>>()?,
})
}
}
#[derive(Serialize, Deserialize)]
struct LiteralTemplateLinked {
template_id: String,
link_id: String,
args: HashMap<String, String>,
}
impl From<TemplateLinked> for LiteralTemplateLinked {
fn from(i: TemplateLinked) -> Self {
Self {
template_id: i.template_id,
link_id: i.link_id,
args: i
.args
.into_iter()
.map(|(k, v)| (format!("{k}"), v))
.collect(),
}
}
}
pub(crate) fn load_links_from_file(path: impl AsRef<Path>) -> Result<Vec<TemplateLinked>> {
let f = match std::fs::File::open(&path) {
Ok(f) => f,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Ok(vec![]);
}
Err(e) => {
return Err(miette::miette!(
"failed to open links file '{}': {}",
path.as_ref().display(),
e
));
}
};
if f.metadata()
.into_diagnostic()
.wrap_err("Failed to read metadata")?
.len()
== 0
{
Ok(vec![])
} else {
serde_json::from_reader(f)
.into_diagnostic()
.wrap_err("Deserialization error")
}
}
pub(crate) fn parse_slot_id<S: AsRef<str>>(s: S) -> Result<SlotId, String> {
match s.as_ref() {
"?principal" => Ok(SlotId::principal()),
"?resource" => Ok(SlotId::resource()),
_ => Err(format!(
"Invalid SlotId! Expected ?principal|?resource, got: {}",
s.as_ref()
)),
}
}