use std::cell::RefCell;
use std::collections::BTreeMap;
use serde_json::Value as JsonValue;
use starlark::values::ProvidesStaticType;
#[derive(Debug, Clone, serde::Serialize)]
pub struct ShadowedRule {
pub path: Vec<String>,
pub winner: String,
pub shadowed: String,
}
#[derive(Debug, Clone)]
pub struct SettingsValue {
pub default_effect: String,
pub default_sandbox: Option<String>,
pub on_sandbox_violation: Option<String>,
pub harness_defaults: Option<bool>,
}
#[derive(Debug, Clone)]
pub struct PolicyRegistration {
pub name: String,
pub default_effect: Option<String>,
pub tree_nodes: Vec<JsonValue>,
pub sandboxes: Vec<JsonValue>,
}
#[derive(Debug, ProvidesStaticType)]
pub struct EvalContext {
pub policy: RefCell<Option<PolicyRegistration>>,
pub settings: RefCell<Option<SettingsValue>>,
pub sandboxes: RefCell<BTreeMap<String, JsonValue>>,
pub shadows: RefCell<Vec<ShadowedRule>>,
}
impl EvalContext {
pub fn new() -> Self {
EvalContext {
policy: RefCell::new(None),
settings: RefCell::new(None),
sandboxes: RefCell::new(BTreeMap::new()),
shadows: RefCell::new(Vec::new()),
}
}
pub fn register_sandbox(&self, name: &str, sb_json: JsonValue) -> anyhow::Result<()> {
let mut map = self.sandboxes.borrow_mut();
if map.contains_key(name) {
anyhow::bail!("sandbox `{name}` is already registered");
}
map.insert(name.to_string(), sb_json);
Ok(())
}
pub fn register_settings(&self, settings: SettingsValue) -> anyhow::Result<()> {
let mut current = self.settings.borrow_mut();
if current.is_some() {
anyhow::bail!("settings() can only be called once per policy file");
}
*current = Some(settings);
Ok(())
}
pub fn register_policy(&self, registration: PolicyRegistration) -> anyhow::Result<()> {
let mut current = self.policy.borrow_mut();
if current.is_some() {
anyhow::bail!("policy() can only be called once per policy file");
}
*current = Some(registration);
Ok(())
}
pub fn assemble_document(&self) -> anyhow::Result<JsonValue> {
let policy = self
.policy
.borrow()
.clone()
.ok_or_else(|| anyhow::anyhow!("policy file must call policy()"))?;
let settings = self.settings.borrow();
let default_effect = policy
.default_effect
.clone()
.or_else(|| settings.as_ref().map(|s| s.default_effect.clone()))
.unwrap_or_else(|| "deny".to_string());
let mut sandbox_map = serde_json::Map::new();
for sb in &policy.sandboxes {
if let Some(name) = sb.get("name").and_then(|n| n.as_str()) {
sandbox_map
.entry(name.to_string())
.or_insert_with(|| sb.clone());
}
}
for (name, sb) in self.sandboxes.borrow().iter() {
sandbox_map
.entry(name.clone())
.or_insert_with(|| sb.clone());
}
let mut doc = serde_json::json!({
"schema_version": 5,
"default_effect": default_effect,
"sandboxes": sandbox_map,
"tree": policy.tree_nodes,
});
if let Some(ref ds) = settings.as_ref().and_then(|s| s.default_sandbox.clone()) {
doc.as_object_mut()
.unwrap()
.insert("default_sandbox".to_string(), serde_json::json!(ds));
}
if let Some(ref action) = settings
.as_ref()
.and_then(|s| s.on_sandbox_violation.clone())
{
doc.as_object_mut().unwrap().insert(
"on_sandbox_violation".to_string(),
serde_json::json!(action),
);
}
if let Some(hd) = settings.as_ref().and_then(|s| s.harness_defaults) {
if !hd {
doc.as_object_mut()
.unwrap()
.insert("harness_defaults".to_string(), serde_json::json!(false));
}
}
Ok(doc)
}
}