Skip to main content

liminal_server/auth_lys/
session.rs

1//! The admitted Lys pass as a connection holds it.
2//!
3//! An object-safe session over any [`LysDeliberateConsumer`], so the
4//! connection code names no consumer type and the real `lys-pass` adapter
5//! plugs in behind one seam.
6//!
7//! Admission runs once at `Connect`; every hot message is then decided
8//! through [`LysSession::decide`] from the admitted pass's rights, offline,
9//! with no call to Lys on the message path (ACCESS-001 R2). A link crossing
10//! is decided the same way through [`LysSession::decide_link`]; opening a
11//! link is deliberate and asks Lys live through [`LysSession::open_link`]
12//! (ACCESS-002 R1).
13
14use super::{
15    AccessAction, AccessDecisionError, AccessResource, AdmittedLysPass, DeliberateDecision,
16    GrantReceipt, HotAction, LysDeliberateConsumer, LysPassConsumer, SubjectBinding,
17    admit_lys_pass, decide_deliberate, decide_message,
18};
19use crate::link::LinkAction;
20use std::sync::Arc;
21
22impl SubjectBinding {
23    /// Binds a crossing's subject to the link resource used at open.
24    ///
25    /// # Errors
26    /// Missing identifiers, whitespace, controls, or a subject pattern.
27    pub fn link(
28        network_id: String,
29        link_id: String,
30        subject: String,
31    ) -> Result<Self, super::SubjectBindingError> {
32        let mut binding = Self::new(network_id, link_id, subject)?;
33        binding.resource.kind = super::ResourceKind::Link;
34        Ok(binding)
35    }
36}
37
38/// Why a Lys pass was not admitted at `Connect`, as the wire names it.
39#[derive(Debug)]
40pub struct LysAdmissionRefusal {
41    /// The consumer's own admission error, named but never carrying pass bytes.
42    pub reason: String,
43}
44
45impl std::fmt::Display for LysAdmissionRefusal {
46    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        write!(formatter, "lys pass admission refused: {}", self.reason)
48    }
49}
50
51impl std::error::Error for LysAdmissionRefusal {}
52
53/// Admits Lys pass bytes into a session the connection keeps.
54pub trait LysAdmitter: Send + Sync + std::fmt::Debug {
55    /// Verifies the pass offline for audience liminal at `now` and returns
56    /// the session that decides its messages.
57    ///
58    /// # Errors
59    /// The consumer's admission refusal, by name.
60    fn admit(&self, pass: &[u8], now: u64) -> Result<Box<dyn LysSession>, LysAdmissionRefusal>;
61}
62
63/// An admitted pass deciding hot actions for one connection.
64pub trait LysSession: Send + Sync + std::fmt::Debug {
65    /// The holder the one verification proved, as Lys names it.
66    fn holder(&self) -> &str;
67
68    /// The directory kind of that holder as the signed pass states it, for
69    /// the membership question (ACCESS-006 R1); `None` when unknown, which a
70    /// membership-protected channel refuses by name.
71    fn holder_kind(&self) -> Option<&str> {
72        None
73    }
74
75    /// When the admitted pass was issued, in Unix seconds; `None` when the
76    /// consumer cannot name it, and nothing is then asked live for want of it.
77    fn issued_at(&self) -> Option<u64> {
78        None
79    }
80
81    /// Decides one hot action on one bound subject at `now`, offline.
82    ///
83    /// # Errors
84    /// The one refusal shape with its cause.
85    fn decide(
86        &self,
87        subject: &SubjectBinding,
88        action: HotAction,
89        now: u64,
90    ) -> Result<GrantReceipt, AccessDecisionError>;
91
92    /// Decides one link grant (`link_export` or `link_import`) on one bound
93    /// link at `now`, offline from the admitted pass (ACCESS-002 R1).
94    ///
95    /// # Errors
96    /// The one refusal shape with its cause.
97    fn decide_link(
98        &self,
99        subject: &SubjectBinding,
100        action: LinkAction,
101        now: u64,
102    ) -> Result<GrantReceipt, AccessDecisionError>;
103
104    /// Asks Lys, live, whether this holder may open `end` of `link`: never
105    /// decided from the pass alone, and `DeliberateUnavailable` when Lys
106    /// cannot be asked (ACCESS-002 R1).
107    ///
108    /// # Errors
109    /// The one refusal shape with its cause.
110    fn open_link(
111        &self,
112        link: &AccessResource,
113        end: LinkAction,
114        now: u64,
115    ) -> Result<DeliberateDecision, AccessDecisionError>;
116
117    /// Asks Lys live for `action` on `resource` with this connection's pass
118    /// (ACCESS-003 R1): the deliberate decision a service of class
119    /// deliberate is served on. A held answer carries the grant and draft;
120    /// without a live seam the answer is `DeliberateUnavailable`, never a
121    /// hot fallback.
122    ///
123    /// # Errors
124    /// Refused, by cause, with the resource and action named.
125    fn decide_live(
126        &self,
127        resource: &AccessResource,
128        action: AccessAction,
129        now: u64,
130    ) -> Result<DeliberateDecision, AccessDecisionError>;
131}
132
133/// Wraps a [`LysDeliberateConsumer`] as a [`LysAdmitter`].
134#[derive(Debug)]
135pub struct ConsumerAdmitter<C> {
136    consumer: Arc<C>,
137}
138
139impl<C> ConsumerAdmitter<C> {
140    /// Shares one consumer across every connection it admits.
141    #[must_use]
142    pub const fn new(consumer: Arc<C>) -> Self {
143        Self { consumer }
144    }
145}
146
147struct ConsumerSession<C: LysPassConsumer> {
148    consumer: Arc<C>,
149    pass: AdmittedLysPass<C::Pass>,
150    holder: String,
151    /// The holder's directory kind as the same verification proved it
152    /// (ACCESS-006 R1).
153    holder_kind: Option<String>,
154}
155
156impl<C: LysPassConsumer> std::fmt::Debug for ConsumerSession<C> {
157    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        formatter
159            .debug_struct("ConsumerSession")
160            .field("holder", &self.holder)
161            .finish_non_exhaustive()
162    }
163}
164
165impl<C> LysAdmitter for ConsumerAdmitter<C>
166where
167    C: LysDeliberateConsumer + Send + Sync + std::fmt::Debug + 'static,
168    C::Pass: Send + Sync + 'static,
169    C::Holder: AsRef<str>,
170{
171    fn admit(&self, pass: &[u8], now: u64) -> Result<Box<dyn LysSession>, LysAdmissionRefusal> {
172        let admitted = admit_lys_pass(self.consumer.as_ref(), pass, now).map_err(|error| {
173            LysAdmissionRefusal {
174                reason: error.to_string(),
175            }
176        })?;
177        let holder = self
178            .consumer
179            .verified_holder(&admitted.pass)
180            .as_ref()
181            .to_owned();
182        let holder_kind = self
183            .consumer
184            .verified_holder_kind(&admitted.pass)
185            .map(str::to_owned);
186        Ok(Box::new(ConsumerSession {
187            consumer: Arc::clone(&self.consumer),
188            pass: admitted,
189            holder,
190            holder_kind,
191        }))
192    }
193}
194
195impl<C> LysSession for ConsumerSession<C>
196where
197    C: LysDeliberateConsumer + Send + Sync + 'static,
198    C::Pass: Send + Sync + 'static,
199{
200    fn holder(&self) -> &str {
201        &self.holder
202    }
203
204    fn holder_kind(&self) -> Option<&str> {
205        self.holder_kind.as_deref()
206    }
207
208    fn issued_at(&self) -> Option<u64> {
209        self.consumer.verified_issued_at(&self.pass.pass)
210    }
211
212    fn decide(
213        &self,
214        subject: &SubjectBinding,
215        action: HotAction,
216        now: u64,
217    ) -> Result<GrantReceipt, AccessDecisionError> {
218        decide_message(self.consumer.as_ref(), &self.pass, subject, action, now)
219    }
220
221    fn decide_link(
222        &self,
223        subject: &SubjectBinding,
224        action: LinkAction,
225        now: u64,
226    ) -> Result<GrantReceipt, AccessDecisionError> {
227        self.consumer
228            .decide_link(&self.pass.pass, subject, action, now)
229    }
230
231    fn open_link(
232        &self,
233        link: &AccessResource,
234        end: LinkAction,
235        now: u64,
236    ) -> Result<DeliberateDecision, AccessDecisionError> {
237        self.decide_live(link, end.access_action(), now)
238    }
239
240    fn decide_live(
241        &self,
242        resource: &AccessResource,
243        action: AccessAction,
244        now: u64,
245    ) -> Result<DeliberateDecision, AccessDecisionError> {
246        decide_deliberate(self.consumer.as_ref(), &self.pass, resource, action, now)
247    }
248}