use serde_json::Value;
use crate::core::{
ACTION_ADMIT, ACTION_PERFORM, ACTION_RELEASE, Delegation, Label, PolicyRequest, Release, RunId,
StepId,
};
use crate::journal::AgentIdentity;
pub const RELEASE_RESOURCE: &str = "information_flow.label";
#[derive(Debug, Clone, Copy)]
pub struct Acting<'a> {
pub tenant: &'a str,
pub capability: &'a str,
pub agent: Option<&'a AgentIdentity>,
pub chain: Option<&'a Delegation>,
}
impl Acting<'_> {
#[must_use]
pub fn principal(&self) -> &str {
self.chain
.map_or(self.capability, |chain| chain.subject().id.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GatedRequest {
pub principal: String,
pub action: &'static str,
pub resource: String,
pub context: Value,
}
impl GatedRequest {
#[must_use]
pub fn as_request(&self) -> PolicyRequest<'_> {
PolicyRequest {
principal: &self.principal,
action: self.action,
resource: &self.resource,
context: &self.context,
}
}
}
#[must_use]
pub fn effect(
acting: &Acting<'_>,
run: RunId,
step: StepId,
kind: &str,
args: &Value,
mutates: bool,
label: Option<&Label>,
) -> GatedRequest {
let mut context = serde_json::json!({
"run": run.to_string(),
"step": step.0,
"tenant": acting.tenant,
"mutates": mutates,
"args": args,
});
if let Some(label) = label {
context["label"] = serde_json::to_value(label).unwrap_or(Value::Null);
}
finish(acting, ACTION_PERFORM, kind, context)
}
#[must_use]
pub fn release(
acting: &Acting<'_>,
run: RunId,
step: StepId,
release: &Release,
label: &Label,
) -> GatedRequest {
let context = serde_json::json!({
"run": run.to_string(),
"step": step.0,
"tenant": acting.tenant,
"release": release,
"label": label,
});
finish(acting, ACTION_RELEASE, RELEASE_RESOURCE, context)
}
#[must_use]
pub fn admission(acting: &Acting<'_>, input: &Value) -> GatedRequest {
let context = serde_json::json!({ "input": input, "tenant": acting.tenant });
finish(acting, ACTION_ADMIT, acting.capability, context)
}
fn finish(
acting: &Acting<'_>,
action: &'static str,
resource: &str,
mut context: Value,
) -> GatedRequest {
if let Some(id) = acting.agent {
context["agent"] = agent_context(id);
}
merge_identity(&mut context, acting.chain);
GatedRequest {
principal: acting.principal().to_owned(),
action,
resource: resource.to_owned(),
context,
}
}
#[must_use]
pub fn agent_context(id: &AgentIdentity) -> Value {
let mut agent = serde_json::json!({
"name": id.name,
"version": id.version,
"digest": id.digest.to_hex(),
});
if let Some(value) = id
.publisher
.as_ref()
.and_then(|p| serde_json::to_value(p).ok())
{
agent["publisher"] = value;
}
agent
}
pub fn merge_identity(context: &mut Value, identity: Option<&Delegation>) {
let (Some(chain), Some(obj)) = (identity, context.as_object_mut()) else {
return;
};
if let Some(extra) = chain.as_context().as_object() {
for (k, v) in extra {
obj.insert(k.clone(), v.clone());
}
}
}