1use super::extract::{name_id_policy_from_extract, optional_endpoint, optional_u16, required_str};
2use super::identifiers::{MessageId, SamlInstant};
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;
10use crate::xml::parse_saml_utc_date_time;
11
12#[derive(Debug, Clone)]
14pub struct AuthnRequest {
15 id: MessageId,
16 issue_instant: SamlInstant,
17 issuer: EntityId,
18 destination: Option<EndpointUrl>,
19 acs_url: Option<EndpointUrl>,
20 protocol_binding: Option<SsoResponseBinding>,
21 acs_index: Option<u16>,
22 name_id_policy: Option<NameIdPolicy>,
23 raw_flow: FlowResult,
24}
25
26impl AuthnRequest {
27 pub fn id(&self) -> &MessageId {
29 &self.id
30 }
31
32 pub fn issue_instant(&self) -> &SamlInstant {
34 &self.issue_instant
35 }
36
37 pub fn issuer(&self) -> &EntityId {
39 &self.issuer
40 }
41
42 pub fn destination(&self) -> Option<&EndpointUrl> {
44 self.destination.as_ref()
45 }
46
47 pub fn acs_url(&self) -> Option<&EndpointUrl> {
49 self.acs_url.as_ref()
50 }
51
52 pub fn protocol_binding(&self) -> Option<SsoResponseBinding> {
54 self.protocol_binding
55 }
56
57 pub fn acs_index(&self) -> Option<u16> {
59 self.acs_index
60 }
61
62 pub fn name_id_policy(&self) -> Option<&NameIdPolicy> {
64 self.name_id_policy.as_ref()
65 }
66
67 pub fn check_and_store_replay(
75 &self,
76 validation: &mut SamlValidationContext<'_>,
77 ) -> Result<(), SamlError> {
78 validation.check_and_store_message_replay(ReplayKey::AuthnRequestId(self.id.clone()))
79 }
80
81 pub fn raw_flow(&self) -> &FlowResult {
83 &self.raw_flow
84 }
85}
86
87impl TryFrom<FlowResult> for AuthnRequest {
88 type Error = SamlError;
89
90 fn try_from(raw_flow: FlowResult) -> Result<Self, Self::Error> {
91 let id = MessageId::try_new(required_str(&raw_flow.extract, "request.id")?)?;
92 let issue_instant = issue_instant_from_extract(&raw_flow.extract)?;
93 let issuer = EntityId::try_new(required_str(&raw_flow.extract, "issuer")?)?;
94 let destination = optional_endpoint(&raw_flow.extract, "request.destination")?;
95 let acs_url = optional_endpoint(&raw_flow.extract, "request.assertionConsumerServiceUrl")?;
96 let protocol_binding = optional_response_binding(&raw_flow.extract)?;
97 let acs_index = optional_u16(&raw_flow.extract, "request.assertionConsumerServiceIndex")?;
98 let name_id_policy = name_id_policy_from_extract(&raw_flow.extract)?;
99 Ok(Self {
100 id,
101 issue_instant,
102 issuer,
103 destination,
104 acs_url,
105 protocol_binding,
106 acs_index,
107 name_id_policy,
108 raw_flow,
109 })
110 }
111}
112
113fn issue_instant_from_extract(extract: &crate::util::Value) -> Result<SamlInstant, SamlError> {
114 let issue_instant = extract.get_str("request.issueInstant").ok_or_else(|| {
115 SamlError::ProtocolProfile(
116 "AuthnRequest is missing required unqualified attribute IssueInstant".into(),
117 )
118 })?;
119 let issue_instant = parse_saml_utc_date_time(issue_instant).ok_or_else(|| {
120 SamlError::ProtocolProfile(
121 "AuthnRequest IssueInstant must use the SAML-conformant UTC xs:dateTime form ending in Z"
122 .into(),
123 )
124 })?;
125 SamlInstant::try_new(issue_instant)
126}
127
128fn optional_response_binding(
129 extract: &crate::util::Value,
130) -> Result<Option<SsoResponseBinding>, SamlError> {
131 let Some(protocol_binding) = extract.get_str("request.protocolBinding") else {
132 return Ok(None);
133 };
134 let binding = Binding::from_urn(protocol_binding).ok_or_else(|| {
135 SamlError::Invalid(format!(
136 "unsupported AuthnRequest ProtocolBinding {protocol_binding}"
137 ))
138 })?;
139 SsoResponseBinding::try_from(binding).map(Some)
140}