liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! The admitted Lys pass as a connection holds it.
//!
//! An object-safe session over any [`LysDeliberateConsumer`], so the
//! connection code names no consumer type and the real `lys-pass` adapter
//! plugs in behind one seam.
//!
//! Admission runs once at `Connect`; every hot message is then decided
//! through [`LysSession::decide`] from the admitted pass's rights, offline,
//! with no call to Lys on the message path (ACCESS-001 R2). A link crossing
//! is decided the same way through [`LysSession::decide_link`]; opening a
//! link is deliberate and asks Lys live through [`LysSession::open_link`]
//! (ACCESS-002 R1).

use super::{
    AccessAction, AccessDecisionError, AccessResource, AdmittedLysPass, DeliberateDecision,
    GrantReceipt, HotAction, LysDeliberateConsumer, LysPassConsumer, SubjectBinding,
    admit_lys_pass, decide_deliberate, decide_message,
};
use crate::link::LinkAction;
use std::sync::Arc;

impl SubjectBinding {
    /// Binds a crossing's subject to the link resource used at open.
    ///
    /// # Errors
    /// Missing identifiers, whitespace, controls, or a subject pattern.
    pub fn link(
        network_id: String,
        link_id: String,
        subject: String,
    ) -> Result<Self, super::SubjectBindingError> {
        let mut binding = Self::new(network_id, link_id, subject)?;
        binding.resource.kind = super::ResourceKind::Link;
        Ok(binding)
    }
}

/// Why a Lys pass was not admitted at `Connect`, as the wire names it.
#[derive(Debug)]
pub struct LysAdmissionRefusal {
    /// The consumer's own admission error, named but never carrying pass bytes.
    pub reason: String,
}

impl std::fmt::Display for LysAdmissionRefusal {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "lys pass admission refused: {}", self.reason)
    }
}

impl std::error::Error for LysAdmissionRefusal {}

/// Admits Lys pass bytes into a session the connection keeps.
pub trait LysAdmitter: Send + Sync + std::fmt::Debug {
    /// Verifies the pass offline for audience liminal at `now` and returns
    /// the session that decides its messages.
    ///
    /// # Errors
    /// The consumer's admission refusal, by name.
    fn admit(&self, pass: &[u8], now: u64) -> Result<Box<dyn LysSession>, LysAdmissionRefusal>;
}

/// An admitted pass deciding hot actions for one connection.
pub trait LysSession: Send + Sync + std::fmt::Debug {
    /// Holds a generic host permission through this session's consumer.
    ///
    /// # Errors
    /// Missing prepared support or an authenticated refusal.
    fn hold_permission(
        &self,
        _request: super::prepared::PermissionRequest<'_>,
    ) -> Result<serde_json::Value, lys_pass::Error> {
        Err(lys_pass::Error::CannotAsk(
            "prepared permission unavailable",
        ))
    }

    /// Prepares through the admitted credential, borrowed only by its consumer.
    ///
    /// # Errors
    /// Missing prepared support or an authenticated refusal.
    fn hold_site(
        &self,
        _request: super::prepared::SiteRequest<'_>,
    ) -> Result<serde_json::Value, lys_pass::Error> {
        Err(lys_pass::Error::CannotAsk(
            "prepared construction unavailable",
        ))
    }

    /// The holder the one verification proved, as Lys names it.
    fn holder(&self) -> &str;

    /// The directory kind of that holder as the signed pass states it, for
    /// the membership question (ACCESS-006 R1); `None` when unknown, which a
    /// membership-protected channel refuses by name.
    fn holder_kind(&self) -> Option<&str> {
        None
    }

    /// When the admitted pass was issued, in Unix seconds; `None` when the
    /// consumer cannot name it, and nothing is then asked live for want of it.
    fn issued_at(&self) -> Option<u64> {
        None
    }

    /// Decides one hot action on one bound subject at `now`, offline.
    ///
    /// # Errors
    /// The one refusal shape with its cause.
    fn decide(
        &self,
        subject: &SubjectBinding,
        action: HotAction,
        now: u64,
    ) -> Result<GrantReceipt, AccessDecisionError>;

    /// Decides one link grant (`link_export` or `link_import`) on one bound
    /// link at `now`, offline from the admitted pass (ACCESS-002 R1).
    ///
    /// # Errors
    /// The one refusal shape with its cause.
    fn decide_link(
        &self,
        subject: &SubjectBinding,
        action: LinkAction,
        now: u64,
    ) -> Result<GrantReceipt, AccessDecisionError>;

    /// Asks Lys, live, whether this holder may open `end` of `link`: never
    /// decided from the pass alone, and `DeliberateUnavailable` when Lys
    /// cannot be asked (ACCESS-002 R1).
    ///
    /// # Errors
    /// The one refusal shape with its cause.
    fn open_link(
        &self,
        link: &AccessResource,
        end: LinkAction,
        now: u64,
    ) -> Result<DeliberateDecision, AccessDecisionError>;

    /// Asks Lys live for `action` on `resource` with this connection's pass
    /// (ACCESS-003 R1): the deliberate decision a service of class
    /// deliberate is served on. A held answer carries the grant and draft;
    /// without a live seam the answer is `DeliberateUnavailable`, never a
    /// hot fallback.
    ///
    /// # Errors
    /// Refused, by cause, with the resource and action named.
    fn decide_live(
        &self,
        resource: &AccessResource,
        action: AccessAction,
        now: u64,
    ) -> Result<DeliberateDecision, AccessDecisionError>;
}

/// Wraps a [`LysDeliberateConsumer`] as a [`LysAdmitter`].
#[derive(Debug)]
pub struct ConsumerAdmitter<C> {
    consumer: Arc<C>,
}

impl<C> ConsumerAdmitter<C> {
    /// Shares one consumer across every connection it admits.
    #[must_use]
    pub const fn new(consumer: Arc<C>) -> Self {
        Self { consumer }
    }
}

struct ConsumerSession<C: LysPassConsumer> {
    consumer: Arc<C>,
    pass: AdmittedLysPass<C::Pass>,
    holder: String,
    /// The holder's directory kind as the same verification proved it
    /// (ACCESS-006 R1).
    holder_kind: Option<String>,
}

impl<C: LysPassConsumer> std::fmt::Debug for ConsumerSession<C> {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("ConsumerSession")
            .field("holder", &self.holder)
            .finish_non_exhaustive()
    }
}

impl<C> LysAdmitter for ConsumerAdmitter<C>
where
    C: LysDeliberateConsumer + Send + Sync + std::fmt::Debug + 'static,
    C::Pass: Send + Sync + 'static,
    C::Holder: AsRef<str>,
{
    fn admit(&self, pass: &[u8], now: u64) -> Result<Box<dyn LysSession>, LysAdmissionRefusal> {
        let admitted = admit_lys_pass(self.consumer.as_ref(), pass, now).map_err(|error| {
            LysAdmissionRefusal {
                reason: error.to_string(),
            }
        })?;
        let holder = self
            .consumer
            .verified_holder(&admitted.pass)
            .as_ref()
            .to_owned();
        let holder_kind = self
            .consumer
            .verified_holder_kind(&admitted.pass)
            .map(str::to_owned);
        Ok(Box::new(ConsumerSession {
            consumer: Arc::clone(&self.consumer),
            pass: admitted,
            holder,
            holder_kind,
        }))
    }
}

impl<C> LysSession for ConsumerSession<C>
where
    C: LysDeliberateConsumer + Send + Sync + 'static,
    C::Pass: Send + Sync + 'static,
{
    fn hold_permission(
        &self,
        request: super::prepared::PermissionRequest<'_>,
    ) -> Result<serde_json::Value, lys_pass::Error> {
        self.consumer.hold_permission(&self.pass.pass, request)
    }
    fn hold_site(
        &self,
        request: super::prepared::SiteRequest<'_>,
    ) -> Result<serde_json::Value, lys_pass::Error> {
        self.consumer.hold_site(&self.pass.pass, request)
    }
    fn holder(&self) -> &str {
        &self.holder
    }

    fn holder_kind(&self) -> Option<&str> {
        self.holder_kind.as_deref()
    }

    fn issued_at(&self) -> Option<u64> {
        self.consumer.verified_issued_at(&self.pass.pass)
    }

    fn decide(
        &self,
        subject: &SubjectBinding,
        action: HotAction,
        now: u64,
    ) -> Result<GrantReceipt, AccessDecisionError> {
        decide_message(self.consumer.as_ref(), &self.pass, subject, action, now)
    }

    fn decide_link(
        &self,
        subject: &SubjectBinding,
        action: LinkAction,
        now: u64,
    ) -> Result<GrantReceipt, AccessDecisionError> {
        self.consumer
            .decide_link(&self.pass.pass, subject, action, now)
    }

    fn open_link(
        &self,
        link: &AccessResource,
        end: LinkAction,
        now: u64,
    ) -> Result<DeliberateDecision, AccessDecisionError> {
        self.decide_live(link, end.access_action(), now)
    }

    fn decide_live(
        &self,
        resource: &AccessResource,
        action: AccessAction,
        now: u64,
    ) -> Result<DeliberateDecision, AccessDecisionError> {
        decide_deliberate(self.consumer.as_ref(), &self.pass, resource, action, now)
    }
}