delegated 0.2.1

Minimal fail-closed capability-token evaluation core
Documentation
//! Builder APIs for issuer-side capability creation.

use crate::contracts::{
    KIND_DELEGATION_TOKEN, KIND_REQUEST_ENVELOPE, SPEC_VERSION_CURRENT, validate_token,
};
use crate::crypto::{SIGNATURE_ALG_ED25519, sign_token};
use crate::models::{DelegationToken, RequestEnvelope, Violation};
use chrono::{DateTime, Utc};
use ed25519_dalek::SigningKey;

/// Builds and signs a [`DelegationToken`] after validating all required claims.
#[derive(Default)]
pub struct DelegationTokenBuilder {
    token_id: Option<String>,
    issuer: Option<String>,
    agent_id: Option<String>,
    delegator_id: Option<String>,
    audience: Vec<String>,
    allowed_actions: Vec<String>,
    allowed_resources: Option<Vec<String>>,
    max_delegation_depth: Option<u16>,
    issued_at: Option<DateTime<Utc>>,
    expires_at: Option<DateTime<Utc>>,
    nonce: Option<String>,
    key_id: Option<String>,
}

impl DelegationTokenBuilder {
    /// Creates an empty builder.
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets the issuer-scoped token identifier used for revocation.
    pub fn token_id(mut self, value: impl Into<String>) -> Self {
        self.token_id = Some(value.into());
        self
    }
    /// Sets the issuer identifier used to resolve the verification key.
    pub fn issuer(mut self, value: impl Into<String>) -> Self {
        self.issuer = Some(value.into());
        self
    }
    /// Sets the agent identifier asserted by the issuer.
    pub fn agent_id(mut self, value: impl Into<String>) -> Self {
        self.agent_id = Some(value.into());
        self
    }
    /// Sets the delegating principal identifier asserted by the issuer.
    pub fn delegator_id(mut self, value: impl Into<String>) -> Self {
        self.delegator_id = Some(value.into());
        self
    }
    /// Adds an audience in which the capability may be accepted.
    pub fn audience(mut self, value: impl Into<String>) -> Self {
        self.audience.push(value.into());
        self
    }
    /// Adds an action the capability authorizes.
    pub fn allowed_action(mut self, value: impl Into<String>) -> Self {
        self.allowed_actions.push(value.into());
        self
    }
    /// Adds an exact resource identifier the capability authorizes.
    pub fn allowed_resource(mut self, value: impl Into<String>) -> Self {
        self.allowed_resources
            .get_or_insert_with(Vec::new)
            .push(value.into());
        self
    }
    /// Requires trusted host delegation depth to be no greater than `value`.
    pub fn max_delegation_depth(mut self, value: u16) -> Self {
        self.max_delegation_depth = Some(value);
        self
    }
    /// Sets the capability activation time.
    pub fn issued_at(mut self, value: DateTime<Utc>) -> Self {
        self.issued_at = Some(value);
        self
    }
    /// Sets the capability expiry time.
    pub fn expires_at(mut self, value: DateTime<Utc>) -> Self {
        self.expires_at = Some(value);
        self
    }
    /// Sets the issuer-unique, single-use replay nonce.
    pub fn nonce(mut self, value: impl Into<String>) -> Self {
        self.nonce = Some(value.into());
        self
    }
    /// Sets the issuer key identifier used by the verifier.
    pub fn key_id(mut self, value: impl Into<String>) -> Self {
        self.key_id = Some(value.into());
        self
    }

    /// Validates all claims and signs the resulting capability with `issuer_key`.
    pub fn build_and_sign(self, issuer_key: &SigningKey) -> Result<DelegationToken, Violation> {
        let mut token = DelegationToken {
            spec_version: SPEC_VERSION_CURRENT.to_owned(),
            kind: KIND_DELEGATION_TOKEN.to_owned(),
            token_id: required(self.token_id, "token_id")?,
            issuer: required(self.issuer, "issuer")?,
            agent_id: required(self.agent_id, "agent_id")?,
            delegator_id: required(self.delegator_id, "delegator_id")?,
            audience: self.audience,
            allowed_actions: self.allowed_actions,
            allowed_resources: self.allowed_resources,
            max_delegation_depth: self.max_delegation_depth,
            issued_at: required(self.issued_at, "issued_at")?,
            expires_at: required(self.expires_at, "expires_at")?,
            nonce: required(self.nonce, "nonce")?,
            key_id: required(self.key_id, "key_id")?,
            signature_alg: SIGNATURE_ALG_ED25519.to_owned(),
            signature: String::new(),
        };
        token.signature = "pending".to_owned();
        validate_token(&token)?;
        token.signature.clear();
        token.signature = sign_token(&token, issuer_key)?;
        Ok(token)
    }
}

/// Wraps a signed capability in the strict 0.2 request envelope.
pub fn envelope(token: DelegationToken, request_id: Option<String>) -> RequestEnvelope {
    RequestEnvelope {
        spec_version: SPEC_VERSION_CURRENT.to_owned(),
        kind: KIND_REQUEST_ENVELOPE.to_owned(),
        request_id,
        delegation_token: token,
    }
}

fn required<T>(value: Option<T>, field: &str) -> Result<T, Violation> {
    value.ok_or_else(|| Violation::new("issuance", format!("{field} is required")))
}