chio-governance 0.1.2

Chio generic governance charters and case evaluation
Documentation
use serde::{Deserialize, Serialize};

use crate::error::GovernanceAuthorizationError;
use crate::receipt::lineage::SignedExportEnvelope;
use crate::validation::{validate_non_empty, validate_sha256_hex};

pub const CAPABILITY_LEASE_SCHEMA_V1: &str = "chio.capability-lease.v1";

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum CapabilityLeaseActionClass {
    ScopedObservation,
    DelegatedAction,
    NarrowDestructive,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CapabilityLeaseArtifact {
    pub schema: String,
    pub lease_id: String,
    pub issuer: String,
    pub subject: String,
    pub scope_digest: String,
    pub action_class: CapabilityLeaseActionClass,
    pub issued_at_unix_ms: u64,
    pub expires_at_unix_ms: u64,
}

impl CapabilityLeaseArtifact {
    pub fn validate(&self) -> Result<(), GovernanceAuthorizationError> {
        if self.schema != CAPABILITY_LEASE_SCHEMA_V1 {
            return Err(GovernanceAuthorizationError::UnsupportedSchema(
                self.schema.clone(),
            ));
        }
        validate_non_empty(&self.lease_id, "lease_id")
            .map_err(GovernanceAuthorizationError::InvalidArtifact)?;
        validate_non_empty(&self.issuer, "issuer")
            .map_err(GovernanceAuthorizationError::InvalidArtifact)?;
        validate_non_empty(&self.subject, "subject")
            .map_err(GovernanceAuthorizationError::InvalidArtifact)?;
        validate_sha256_hex(&self.scope_digest, "scope_digest")?;
        if self.expires_at_unix_ms <= self.issued_at_unix_ms {
            return Err(GovernanceAuthorizationError::InvalidArtifact(
                "expires_at_unix_ms must be greater than issued_at_unix_ms".to_string(),
            ));
        }
        Ok(())
    }
}

pub type SignedCapabilityLease = SignedExportEnvelope<CapabilityLeaseArtifact>;

pub fn verify_capability_lease(
    lease: &SignedCapabilityLease,
    now_unix_ms: u64,
    expected_scope_digest: Option<String>,
) -> Result<(), GovernanceAuthorizationError> {
    lease.body.validate()?;
    if !lease
        .verify_signature()
        .map_err(|e| GovernanceAuthorizationError::Crypto(e.to_string()))?
    {
        return Err(GovernanceAuthorizationError::InvalidSignature);
    }
    if lease.body.issued_at_unix_ms > now_unix_ms {
        return Err(GovernanceAuthorizationError::LeaseNotYetValid(
            lease.body.lease_id.clone(),
        ));
    }
    if lease.body.expires_at_unix_ms <= now_unix_ms {
        return Err(GovernanceAuthorizationError::LeaseExpiredOrUnknown(
            lease.body.lease_id.clone(),
        ));
    }
    if let Some(expected) = expected_scope_digest {
        validate_sha256_hex(&expected, "expected_scope_digest")?;
        if lease.body.scope_digest != expected {
            return Err(GovernanceAuthorizationError::ScopeDigestMismatch);
        }
    }
    Ok(())
}