Skip to main content

saml_rs/model/
authn.rs

1use super::extract::{name_id_policy_from_extract, optional_endpoint, optional_u16, required_str};
2use super::identifiers::MessageId;
3use super::subject::NameIdPolicy;
4use super::{EndpointUrl, ReplayKey, SamlValidationContext};
5use crate::browser::SsoResponseBinding;
6use crate::config::EntityId;
7use crate::constants::Binding;
8use crate::error::SamlError;
9use crate::raw::FlowResult;
10
11/// Parsed AuthnRequest result.
12#[derive(Debug, Clone)]
13pub struct AuthnRequest {
14    id: MessageId,
15    issuer: EntityId,
16    destination: Option<EndpointUrl>,
17    acs_url: Option<EndpointUrl>,
18    protocol_binding: Option<SsoResponseBinding>,
19    acs_index: Option<u16>,
20    name_id_policy: Option<NameIdPolicy>,
21    raw_flow: FlowResult,
22}
23
24impl AuthnRequest {
25    /// Request ID.
26    pub fn id(&self) -> &MessageId {
27        &self.id
28    }
29
30    /// Request issuer.
31    pub fn issuer(&self) -> &EntityId {
32        &self.issuer
33    }
34
35    /// Destination endpoint, when present.
36    pub fn destination(&self) -> Option<&EndpointUrl> {
37        self.destination.as_ref()
38    }
39
40    /// AssertionConsumerServiceURL, when present.
41    pub fn acs_url(&self) -> Option<&EndpointUrl> {
42        self.acs_url.as_ref()
43    }
44
45    /// Requested response `ProtocolBinding`, when present.
46    pub fn protocol_binding(&self) -> Option<SsoResponseBinding> {
47        self.protocol_binding
48    }
49
50    /// Requested `AssertionConsumerServiceIndex`, when present.
51    pub fn acs_index(&self) -> Option<u16> {
52        self.acs_index
53    }
54
55    /// NameIDPolicy, when present.
56    pub fn name_id_policy(&self) -> Option<&NameIdPolicy> {
57        self.name_id_policy.as_ref()
58    }
59
60    /// Check and store this AuthnRequest's replay key using caller cache state.
61    ///
62    /// # Errors
63    ///
64    /// Returns [`SamlError::ReplayDetected`] when the request ID has already
65    /// been seen. Cache implementations may also return storage-specific
66    /// failures mapped to [`SamlError`].
67    pub fn check_and_store_replay(
68        &self,
69        validation: &mut SamlValidationContext<'_>,
70    ) -> Result<(), SamlError> {
71        validation.check_and_store_message_replay(ReplayKey::AuthnRequestId(self.id.clone()))
72    }
73
74    /// Raw validated flow result.
75    pub fn raw_flow(&self) -> &FlowResult {
76        &self.raw_flow
77    }
78}
79
80impl TryFrom<FlowResult> for AuthnRequest {
81    type Error = SamlError;
82
83    fn try_from(raw_flow: FlowResult) -> Result<Self, Self::Error> {
84        let id = MessageId::try_new(required_str(&raw_flow.extract, "request.id")?)?;
85        let issuer = EntityId::try_new(required_str(&raw_flow.extract, "issuer")?)?;
86        let destination = optional_endpoint(&raw_flow.extract, "request.destination")?;
87        let acs_url = optional_endpoint(&raw_flow.extract, "request.assertionConsumerServiceUrl")?;
88        let protocol_binding = optional_response_binding(&raw_flow.extract)?;
89        let acs_index = optional_u16(&raw_flow.extract, "request.assertionConsumerServiceIndex")?;
90        let name_id_policy = name_id_policy_from_extract(&raw_flow.extract)?;
91        Ok(Self {
92            id,
93            issuer,
94            destination,
95            acs_url,
96            protocol_binding,
97            acs_index,
98            name_id_policy,
99            raw_flow,
100        })
101    }
102}
103
104fn optional_response_binding(
105    extract: &crate::util::Value,
106) -> Result<Option<SsoResponseBinding>, SamlError> {
107    let Some(protocol_binding) = extract.get_str("request.protocolBinding") else {
108        return Ok(None);
109    };
110    let binding = Binding::from_urn(protocol_binding).ok_or_else(|| {
111        SamlError::Invalid(format!(
112            "unsupported AuthnRequest ProtocolBinding {protocol_binding}"
113        ))
114    })?;
115    SsoResponseBinding::try_from(binding).map(Some)
116}