Skip to main content

saml_rs/model/
session.rs

1use super::identifiers::{SamlInstant, SessionIndex};
2use crate::error::{SamlError, TimeWindowField};
3use time::{format_description::well_known::Rfc3339, OffsetDateTime};
4
5/// Session data from one `AuthnStatement`.
6///
7/// Each value preserves the statement's `SessionIndex`, `AuthnInstant`, and
8/// `SessionNotOnOrAfter` as one tuple. Assertions with repeated statements are
9/// represented by multiple values in document order through
10/// [`crate::SsoSession::authn_sessions`].
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct AuthnSession {
13    session_index: Option<SessionIndex>,
14    authn_instant: Option<SamlInstant>,
15    not_on_or_after: Option<SamlInstant>,
16}
17
18pub(super) static EMPTY_AUTHN_SESSION: AuthnSession = AuthnSession {
19    session_index: None,
20    authn_instant: None,
21    not_on_or_after: None,
22};
23
24pub(crate) fn earliest_authn_session_expiration<'a>(
25    values: impl IntoIterator<Item = &'a str>,
26    error_field: TimeWindowField,
27) -> Result<Option<OffsetDateTime>, SamlError> {
28    let mut earliest = None;
29    for value in values {
30        let candidate = OffsetDateTime::parse(value, &Rfc3339)
31            .map_err(|_| SamlError::TimeWindowInvalid { field: error_field })?;
32        if earliest.is_none_or(|current| candidate < current) {
33            earliest = Some(candidate);
34        }
35    }
36    Ok(earliest)
37}
38
39impl AuthnSession {
40    /// Create session data.
41    pub fn new(
42        session_index: Option<SessionIndex>,
43        authn_instant: Option<SamlInstant>,
44        not_on_or_after: Option<SamlInstant>,
45    ) -> Self {
46        Self {
47            session_index,
48            authn_instant,
49            not_on_or_after,
50        }
51    }
52
53    /// SessionIndex, when present.
54    pub fn session_index(&self) -> Option<&SessionIndex> {
55        self.session_index.as_ref()
56    }
57
58    /// AuthnInstant, when present.
59    pub fn authn_instant(&self) -> Option<&SamlInstant> {
60        self.authn_instant.as_ref()
61    }
62
63    /// SessionNotOnOrAfter, when present.
64    pub fn not_on_or_after(&self) -> Option<&SamlInstant> {
65        self.not_on_or_after.as_ref()
66    }
67}