authority-delegation 0.1.0

Typed delegated-authority surface crate with bounded lease and policy artifacts; not an access-control runtime
Documentation
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::error::{
    require_non_empty, require_non_empty_slice, AuthorityValidationError, AuthorityValidationResult,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CapabilityBlastRadiusCeilingV1 {
    SingleTenant,
    SingleService,
    Regional,
    Global,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AuthorityChainValidityStateV1 {
    Valid,
    Expired,
    Revoked,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CapabilityDisclosureCeilingV1 {
    Internal,
    RedactedStructuredOnly,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CapabilityClassV1 {
    pub schema_version: String,
    pub capability_class_id: String,
    pub governed_effect_families: Vec<String>,
    pub blast_radius_ceiling: CapabilityBlastRadiusCeilingV1,
    pub disclosure_ceiling: CapabilityDisclosureCeilingV1,
    pub required_review_class: String,
    pub dual_control_required: bool,
    pub max_delegation_depth: i64,
}

impl CapabilityClassV1 {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        capability_class_id: impl Into<String>,
        governed_effect_families: Vec<String>,
        blast_radius_ceiling: CapabilityBlastRadiusCeilingV1,
        disclosure_ceiling: CapabilityDisclosureCeilingV1,
        required_review_class: impl Into<String>,
        dual_control_required: bool,
        max_delegation_depth: i64,
    ) -> Result<Self, AuthorityValidationError> {
        let value = Self {
            schema_version: "CapabilityClassV1".to_string(),
            capability_class_id: capability_class_id.into(),
            governed_effect_families,
            blast_radius_ceiling,
            disclosure_ceiling,
            required_review_class: required_review_class.into(),
            dual_control_required,
            max_delegation_depth,
        };
        value.validate()?;
        Ok(value)
    }

    pub fn validate(&self) -> AuthorityValidationResult {
        require_non_empty(&self.capability_class_id, "capability_class_id")?;
        require_non_empty_slice(&self.governed_effect_families, "governed_effect_families")?;
        require_non_empty(&self.required_review_class, "required_review_class")?;
        if self.max_delegation_depth < 0 {
            return Err(AuthorityValidationError::InvalidState(
                "max_delegation_depth",
            ));
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AuthorityLeaseV1 {
    pub schema_version: String,
    pub authority_lease_id: String,
    pub capability_class_id: String,
    pub grantor: String,
    pub grantee: String,
    pub scope: String,
    pub time_window: String,
    pub allowed_effect_families: Vec<String>,
    pub hard_ceilings: Vec<String>,
    pub revocation_conditions: Vec<String>,
    pub required_receipts: Vec<String>,
}

impl AuthorityLeaseV1 {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        authority_lease_id: impl Into<String>,
        capability_class_id: impl Into<String>,
        grantor: impl Into<String>,
        grantee: impl Into<String>,
        scope: impl Into<String>,
        time_window: impl Into<String>,
        allowed_effect_families: Vec<String>,
        hard_ceilings: Vec<String>,
        revocation_conditions: Vec<String>,
        required_receipts: Vec<String>,
    ) -> Result<Self, AuthorityValidationError> {
        let value = Self {
            schema_version: "AuthorityLeaseV1".to_string(),
            authority_lease_id: authority_lease_id.into(),
            capability_class_id: capability_class_id.into(),
            grantor: grantor.into(),
            grantee: grantee.into(),
            scope: scope.into(),
            time_window: time_window.into(),
            allowed_effect_families,
            hard_ceilings,
            revocation_conditions,
            required_receipts,
        };
        value.validate()?;
        Ok(value)
    }

    pub fn validate(&self) -> AuthorityValidationResult {
        require_non_empty(&self.authority_lease_id, "authority_lease_id")?;
        require_non_empty(&self.capability_class_id, "capability_class_id")?;
        require_non_empty(&self.grantor, "grantor")?;
        require_non_empty(&self.grantee, "grantee")?;
        require_non_empty(&self.scope, "scope")?;
        require_non_empty(&self.time_window, "time_window")?;
        require_non_empty_slice(&self.allowed_effect_families, "allowed_effect_families")?;
        require_non_empty_slice(&self.required_receipts, "required_receipts")?;
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct DelegationBundleV1 {
    pub schema_version: String,
    pub delegation_bundle_id: String,
    pub authority_lease_id: String,
    pub delegated_rights: Vec<String>,
    pub reduced_ceilings: Vec<String>,
    pub replay_requirements: Vec<String>,
    pub further_delegation_permitted: bool,
}

impl DelegationBundleV1 {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        delegation_bundle_id: impl Into<String>,
        authority_lease_id: impl Into<String>,
        delegated_rights: Vec<String>,
        reduced_ceilings: Vec<String>,
        replay_requirements: Vec<String>,
        further_delegation_permitted: bool,
    ) -> Result<Self, AuthorityValidationError> {
        let value = Self {
            schema_version: "DelegationBundleV1".to_string(),
            delegation_bundle_id: delegation_bundle_id.into(),
            authority_lease_id: authority_lease_id.into(),
            delegated_rights,
            reduced_ceilings,
            replay_requirements,
            further_delegation_permitted,
        };
        value.validate()?;
        Ok(value)
    }

    pub fn validate(&self) -> AuthorityValidationResult {
        require_non_empty(&self.delegation_bundle_id, "delegation_bundle_id")?;
        require_non_empty(&self.authority_lease_id, "authority_lease_id")?;
        require_non_empty_slice(&self.delegated_rights, "delegated_rights")?;
        require_non_empty_slice(&self.replay_requirements, "replay_requirements")?;
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AuthorityChainV1 {
    pub schema_version: String,
    pub authority_chain_id: String,
    pub local_principal: String,
    pub delegated_principals: Vec<String>,
    pub external_principals: Vec<String>,
    pub approval_refs: Vec<String>,
    pub current_validity_state: AuthorityChainValidityStateV1,
}

impl AuthorityChainV1 {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        authority_chain_id: impl Into<String>,
        local_principal: impl Into<String>,
        delegated_principals: Vec<String>,
        external_principals: Vec<String>,
        approval_refs: Vec<String>,
        current_validity_state: AuthorityChainValidityStateV1,
    ) -> Result<Self, AuthorityValidationError> {
        let value = Self {
            schema_version: "AuthorityChainV1".to_string(),
            authority_chain_id: authority_chain_id.into(),
            local_principal: local_principal.into(),
            delegated_principals,
            external_principals,
            approval_refs,
            current_validity_state,
        };
        value.validate()?;
        Ok(value)
    }

    pub fn validate(&self) -> AuthorityValidationResult {
        require_non_empty(&self.authority_chain_id, "authority_chain_id")?;
        require_non_empty(&self.local_principal, "local_principal")?;
        require_non_empty_slice(&self.approval_refs, "approval_refs")?;
        Ok(())
    }
}