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    /// Holds a generic host permission through this session's consumer.
66    ///
67    /// # Errors
68    /// Missing prepared support or an authenticated refusal.
69    fn hold_permission(
70        &self,
71        _request: super::prepared::PermissionRequest<'_>,
72    ) -> Result<serde_json::Value, lys_pass::Error> {
73        Err(lys_pass::Error::CannotAsk(
74            "prepared permission unavailable",
75        ))
76    }
77
78    /// Prepares through the admitted credential, borrowed only by its consumer.
79    ///
80    /// # Errors
81    /// Missing prepared support or an authenticated refusal.
82    fn hold_site(
83        &self,
84        _request: super::prepared::SiteRequest<'_>,
85    ) -> Result<serde_json::Value, lys_pass::Error> {
86        Err(lys_pass::Error::CannotAsk(
87            "prepared construction unavailable",
88        ))
89    }
90
91    /// The holder the one verification proved, as Lys names it.
92    fn holder(&self) -> &str;
93
94    /// The directory kind of that holder as the signed pass states it, for
95    /// the membership question (ACCESS-006 R1); `None` when unknown, which a
96    /// membership-protected channel refuses by name.
97    fn holder_kind(&self) -> Option<&str> {
98        None
99    }
100
101    /// When the admitted pass was issued, in Unix seconds; `None` when the
102    /// consumer cannot name it, and nothing is then asked live for want of it.
103    fn issued_at(&self) -> Option<u64> {
104        None
105    }
106
107    /// Decides one hot action on one bound subject at `now`, offline.
108    ///
109    /// # Errors
110    /// The one refusal shape with its cause.
111    fn decide(
112        &self,
113        subject: &SubjectBinding,
114        action: HotAction,
115        now: u64,
116    ) -> Result<GrantReceipt, AccessDecisionError>;
117
118    /// Decides one link grant (`link_export` or `link_import`) on one bound
119    /// link at `now`, offline from the admitted pass (ACCESS-002 R1).
120    ///
121    /// # Errors
122    /// The one refusal shape with its cause.
123    fn decide_link(
124        &self,
125        subject: &SubjectBinding,
126        action: LinkAction,
127        now: u64,
128    ) -> Result<GrantReceipt, AccessDecisionError>;
129
130    /// Asks Lys, live, whether this holder may open `end` of `link`: never
131    /// decided from the pass alone, and `DeliberateUnavailable` when Lys
132    /// cannot be asked (ACCESS-002 R1).
133    ///
134    /// # Errors
135    /// The one refusal shape with its cause.
136    fn open_link(
137        &self,
138        link: &AccessResource,
139        end: LinkAction,
140        now: u64,
141    ) -> Result<DeliberateDecision, AccessDecisionError>;
142
143    /// Asks Lys live for `action` on `resource` with this connection's pass
144    /// (ACCESS-003 R1): the deliberate decision a service of class
145    /// deliberate is served on. A held answer carries the grant and draft;
146    /// without a live seam the answer is `DeliberateUnavailable`, never a
147    /// hot fallback.
148    ///
149    /// # Errors
150    /// Refused, by cause, with the resource and action named.
151    fn decide_live(
152        &self,
153        resource: &AccessResource,
154        action: AccessAction,
155        now: u64,
156    ) -> Result<DeliberateDecision, AccessDecisionError>;
157}
158
159/// Wraps a [`LysDeliberateConsumer`] as a [`LysAdmitter`].
160#[derive(Debug)]
161pub struct ConsumerAdmitter<C> {
162    consumer: Arc<C>,
163}
164
165impl<C> ConsumerAdmitter<C> {
166    /// Shares one consumer across every connection it admits.
167    #[must_use]
168    pub const fn new(consumer: Arc<C>) -> Self {
169        Self { consumer }
170    }
171}
172
173struct ConsumerSession<C: LysPassConsumer> {
174    consumer: Arc<C>,
175    pass: AdmittedLysPass<C::Pass>,
176    holder: String,
177    /// The holder's directory kind as the same verification proved it
178    /// (ACCESS-006 R1).
179    holder_kind: Option<String>,
180}
181
182impl<C: LysPassConsumer> std::fmt::Debug for ConsumerSession<C> {
183    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        formatter
185            .debug_struct("ConsumerSession")
186            .field("holder", &self.holder)
187            .finish_non_exhaustive()
188    }
189}
190
191impl<C> LysAdmitter for ConsumerAdmitter<C>
192where
193    C: LysDeliberateConsumer + Send + Sync + std::fmt::Debug + 'static,
194    C::Pass: Send + Sync + 'static,
195    C::Holder: AsRef<str>,
196{
197    fn admit(&self, pass: &[u8], now: u64) -> Result<Box<dyn LysSession>, LysAdmissionRefusal> {
198        let admitted = admit_lys_pass(self.consumer.as_ref(), pass, now).map_err(|error| {
199            LysAdmissionRefusal {
200                reason: error.to_string(),
201            }
202        })?;
203        let holder = self
204            .consumer
205            .verified_holder(&admitted.pass)
206            .as_ref()
207            .to_owned();
208        let holder_kind = self
209            .consumer
210            .verified_holder_kind(&admitted.pass)
211            .map(str::to_owned);
212        Ok(Box::new(ConsumerSession {
213            consumer: Arc::clone(&self.consumer),
214            pass: admitted,
215            holder,
216            holder_kind,
217        }))
218    }
219}
220
221impl<C> LysSession for ConsumerSession<C>
222where
223    C: LysDeliberateConsumer + Send + Sync + 'static,
224    C::Pass: Send + Sync + 'static,
225{
226    fn hold_permission(
227        &self,
228        request: super::prepared::PermissionRequest<'_>,
229    ) -> Result<serde_json::Value, lys_pass::Error> {
230        self.consumer.hold_permission(&self.pass.pass, request)
231    }
232    fn hold_site(
233        &self,
234        request: super::prepared::SiteRequest<'_>,
235    ) -> Result<serde_json::Value, lys_pass::Error> {
236        self.consumer.hold_site(&self.pass.pass, request)
237    }
238    fn holder(&self) -> &str {
239        &self.holder
240    }
241
242    fn holder_kind(&self) -> Option<&str> {
243        self.holder_kind.as_deref()
244    }
245
246    fn issued_at(&self) -> Option<u64> {
247        self.consumer.verified_issued_at(&self.pass.pass)
248    }
249
250    fn decide(
251        &self,
252        subject: &SubjectBinding,
253        action: HotAction,
254        now: u64,
255    ) -> Result<GrantReceipt, AccessDecisionError> {
256        decide_message(self.consumer.as_ref(), &self.pass, subject, action, now)
257    }
258
259    fn decide_link(
260        &self,
261        subject: &SubjectBinding,
262        action: LinkAction,
263        now: u64,
264    ) -> Result<GrantReceipt, AccessDecisionError> {
265        self.consumer
266            .decide_link(&self.pass.pass, subject, action, now)
267    }
268
269    fn open_link(
270        &self,
271        link: &AccessResource,
272        end: LinkAction,
273        now: u64,
274    ) -> Result<DeliberateDecision, AccessDecisionError> {
275        self.decide_live(link, end.access_action(), now)
276    }
277
278    fn decide_live(
279        &self,
280        resource: &AccessResource,
281        action: AccessAction,
282        now: u64,
283    ) -> Result<DeliberateDecision, AccessDecisionError> {
284        decide_deliberate(self.consumer.as_ref(), &self.pass, resource, action, now)
285    }
286}