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#[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 pub fn id(&self) -> &MessageId {
27 &self.id
28 }
29
30 pub fn issuer(&self) -> &EntityId {
32 &self.issuer
33 }
34
35 pub fn destination(&self) -> Option<&EndpointUrl> {
37 self.destination.as_ref()
38 }
39
40 pub fn acs_url(&self) -> Option<&EndpointUrl> {
42 self.acs_url.as_ref()
43 }
44
45 pub fn protocol_binding(&self) -> Option<SsoResponseBinding> {
47 self.protocol_binding
48 }
49
50 pub fn acs_index(&self) -> Option<u16> {
52 self.acs_index
53 }
54
55 pub fn name_id_policy(&self) -> Option<&NameIdPolicy> {
57 self.name_id_policy.as_ref()
58 }
59
60 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 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}