delegated 0.2.0

Minimal fail-closed capability-token evaluation core
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::{Display, Formatter};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Violation {
    pub stage: &'static str,
    pub reason: String,
}

impl Violation {
    pub fn new(stage: &'static str, reason: impl Into<String>) -> Self {
        Self {
            stage,
            reason: reason.into(),
        }
    }
}

impl Display for Violation {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}: {}", self.stage, self.reason)
    }
}

impl Error for Violation {}

/// A bearer capability issued by a configured, trusted issuer.
///
/// Every authorization-relevant field is covered by `signature`. The issuer key is
/// resolved from host configuration; no key material supplied by the caller is trusted.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DelegationToken {
    pub spec_version: String,
    pub kind: String,
    pub token_id: String,
    pub issuer: String,
    pub agent_id: String,
    pub delegator_id: String,
    pub audience: Vec<String>,
    pub allowed_actions: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub allowed_resources: Option<Vec<String>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_delegation_depth: Option<u16>,
    pub issued_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
    pub nonce: String,
    pub key_id: String,
    pub signature_alg: String,
    pub signature: String,
}

/// The untrusted wire wrapper. It intentionally does not contain action, audience,
/// resource, or policy context; those values must come from the host operation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequestEnvelope {
    pub spec_version: String,
    pub kind: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub request_id: Option<String>,
    pub delegation_token: DelegationToken,
}

/// Authorization facts derived by the host from the operation that will actually run.
/// Never construct this from fields in the request envelope.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OperationContext {
    pub audience: String,
    pub action: String,
    pub resource: Option<String>,
    pub delegation_depth: Option<u16>,
}

impl OperationContext {
    pub fn new(audience: impl Into<String>, action: impl Into<String>) -> Self {
        Self {
            audience: audience.into(),
            action: action.into(),
            resource: None,
            delegation_depth: None,
        }
    }

    pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
        self.resource = Some(resource.into());
        self
    }

    pub fn with_delegation_depth(mut self, depth: u16) -> Self {
        self.delegation_depth = Some(depth);
        self
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Decision {
    pub allowed: bool,
    pub stage: String,
    pub reason: String,
}

impl Decision {
    pub fn allow(stage: impl Into<String>, reason: impl Into<String>) -> Self {
        Self {
            allowed: true,
            stage: stage.into(),
            reason: reason.into(),
        }
    }

    pub fn deny(stage: impl Into<String>, reason: impl Into<String>) -> Self {
        Self {
            allowed: false,
            stage: stage.into(),
            reason: reason.into(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuditEvent {
    pub occurred_at: DateTime<Utc>,
    pub allowed: bool,
    pub stage: String,
    pub reason: String,
    /// False means identity fields were copied from untrusted input for diagnostics only.
    pub claims_verified: bool,
    pub request_id: Option<String>,
    pub token_id: Option<String>,
    pub issuer: Option<String>,
    pub agent_id: Option<String>,
    pub delegator_id: Option<String>,
    pub audience: String,
    pub action: String,
    pub resource: Option<String>,
}