saml_rs/api/idp.rs
1use crate::browser::{BrowserInput, Outbound, SsoRequestBinding};
2use crate::config::SpDescriptor;
3use crate::error::SamlError as Error;
4use crate::flow::HttpRequest;
5use crate::idp::{IdentityProvider, LoginResponseOptions, LoginResponseOverrides};
6use crate::model::{
7 AuthnRequest, Received, RelayStateParam, ReplayKey, SamlValidationContext, SsoResponse, Subject,
8};
9
10use super::raw_mapping::{
11 ensure_entity_id, input_binding, raw_sp_descriptor, relay_state_from_input, response_target,
12};
13use super::{Idp, RespondSso, Saml, SamlError};
14
15impl Saml<Idp> {
16 /// Local IdP metadata XML.
17 pub fn metadata_xml(&self) -> &str {
18 self.raw_identity_provider().metadata_xml()
19 }
20
21 /// Raw compatibility Identity Provider.
22 pub fn raw_identity_provider(&self) -> &IdentityProvider {
23 &self.0.identity_provider
24 }
25
26 /// Receive an SP AuthnRequest.
27 ///
28 /// # Errors
29 ///
30 /// Returns [`SamlError`] when browser input or relay state is invalid, the
31 /// request binding is unsupported, SP metadata cannot be parsed, XML
32 /// parsing or signature/trust validation fails, the request destination
33 /// does not match local metadata, or replay validation detects a duplicate
34 /// or expired request.
35 ///
36 /// # Examples
37 ///
38 /// ```no_run
39 /// use saml_rs::{
40 /// AuthnRequest, BrowserInput, FormField, ReplayPolicy, RespondSso, Saml,
41 /// SamlValidationContext, SpDescriptor, Subject,
42 /// };
43 /// use std::time::SystemTime;
44 ///
45 /// # fn respond(
46 /// # idp: &Saml<saml_rs::Idp>,
47 /// # sp: &SpDescriptor,
48 /// # fields: Vec<FormField>,
49 /// # subject: Subject,
50 /// # ) -> Result<(), saml_rs::SamlError> {
51 /// let validation = SamlValidationContext::new(
52 /// SystemTime::now(),
53 /// ReplayPolicy::DisabledForCompatibility,
54 /// );
55 /// let input = BrowserInput::<AuthnRequest>::post(fields);
56 /// let request = idp.receive_sso(sp, input, validation)?;
57 /// let response = idp.respond_sso(sp, &request, subject, RespondSso::post())?;
58 ///
59 /// let form = response.post_form()?;
60 /// # let _ = form;
61 /// # Ok(()) }
62 /// ```
63 pub fn receive_sso(
64 &self,
65 sp: &SpDescriptor,
66 input: BrowserInput<AuthnRequest>,
67 validation: SamlValidationContext<'_>,
68 ) -> Result<Received<AuthnRequest>, SamlError> {
69 let relay_state = relay_state_from_input(&input)?;
70 let binding = SsoRequestBinding::try_from(input_binding(&input))?;
71 let raw_sp = raw_sp_descriptor(sp)?;
72 let request = HttpRequest::try_from(input)?;
73 let flow = self.raw_identity_provider().parse_login_request_at(
74 &raw_sp,
75 binding.as_binding(),
76 &request,
77 validation.now(),
78 validation.clock_skew().as_millis(),
79 )?;
80 let authn = AuthnRequest::try_from(flow)?;
81 if let Some(destination) = authn.destination() {
82 let expected = self
83 .raw_identity_provider()
84 .metadata
85 .get_single_sign_on_service(binding.as_binding())
86 .ok_or_else(|| Error::MissingMetadata("SingleSignOnService".into()))?;
87 if destination.as_str() != expected {
88 return Err(Error::destination_mismatch(
89 &expected,
90 Some(destination.as_str()),
91 ));
92 }
93 }
94 let mut validation = validation;
95 validation.check_and_store_message_replay(ReplayKey::AuthnRequestId(authn.id().clone()))?;
96 Ok(Received::new(authn).with_relay_state(relay_state))
97 }
98
99 /// Respond to a received SP AuthnRequest.
100 ///
101 /// # Errors
102 ///
103 /// Returns [`SamlError`] when the request issuer does not match the SP
104 /// descriptor, relay state is invalid, the request ACS selection conflicts
105 /// with the response binding or SP metadata, required metadata or signing
106 /// keys are missing, the configured issuance expiration overflows the
107 /// supported timestamp range, or response creation fails.
108 pub fn respond_sso(
109 &self,
110 sp: &SpDescriptor,
111 request: &Received<AuthnRequest>,
112 subject: Subject,
113 options: RespondSso,
114 ) -> Result<Outbound<SsoResponse>, SamlError> {
115 ensure_entity_id(request.message().issuer(), sp.entity_id())?;
116 self.issue_sso(sp, Some(request), subject, options)
117 }
118
119 /// Initiate IdP-initiated SSO.
120 ///
121 /// # Errors
122 ///
123 /// Returns [`SamlError`] when relay state is invalid, SP metadata cannot be
124 /// parsed, a compatible ACS endpoint or signing key is missing, the
125 /// selected binding is unsupported, the configured issuance expiration
126 /// overflows the supported timestamp range, or response creation fails.
127 ///
128 /// # Examples
129 ///
130 /// ```no_run
131 /// use saml_rs::{
132 /// BrowserInput, FormField, IdpDescriptor, ReplayPolicy, RespondSso, Saml,
133 /// SamlValidationContext, SpDescriptor, SsoResponse, Subject,
134 /// };
135 /// use std::time::SystemTime;
136 ///
137 /// # fn initiate(
138 /// # idp: &Saml<saml_rs::Idp>,
139 /// # sp: &Saml<saml_rs::Sp>,
140 /// # sp_descriptor: &SpDescriptor,
141 /// # idp_descriptor: &IdpDescriptor,
142 /// # subject: Subject,
143 /// # form_fields: Vec<FormField>,
144 /// # ) -> Result<(), saml_rs::SamlError> {
145 /// let response = idp.initiate_sso(sp_descriptor, subject, RespondSso::post())?;
146 /// let form = response.post_form()?;
147 /// # let _ = form;
148 ///
149 /// let validation = SamlValidationContext::new(
150 /// SystemTime::now(),
151 /// ReplayPolicy::DisabledForCompatibility,
152 /// );
153 /// let session = sp.accept_unsolicited_sso(
154 /// idp_descriptor,
155 /// BrowserInput::<SsoResponse>::post(form_fields),
156 /// validation,
157 /// )?;
158 /// let issuer = session.issuer().as_str();
159 /// # let _ = issuer;
160 /// # Ok(()) }
161 /// ```
162 pub fn initiate_sso(
163 &self,
164 sp: &SpDescriptor,
165 subject: Subject,
166 options: RespondSso,
167 ) -> Result<Outbound<SsoResponse>, SamlError> {
168 self.issue_sso(sp, None, subject, options)
169 }
170 fn issue_sso(
171 &self,
172 sp: &SpDescriptor,
173 request: Option<&Received<AuthnRequest>>,
174 subject: Subject,
175 options: RespondSso,
176 ) -> Result<Outbound<SsoResponse>, SamlError> {
177 let idp_setting = &self.raw_identity_provider().setting;
178 let sign_response = options.should_sign_response(
179 idp_setting.is_assertion_encrypted,
180 &idp_setting.data_encryption_algorithm,
181 );
182 let relay_state = options.relay_state.unwrap_or_else(|| {
183 request.map_or_else(RelayStateParam::absent, |request| {
184 request.relay_state().clone()
185 })
186 });
187 relay_state.validate()?;
188 let mut raw_sp = raw_sp_descriptor(sp)?;
189 raw_sp.setting.want_message_signed = sign_response;
190 let (binding, explicit_acs) = match request {
191 Some(request) => response_target(&raw_sp, request.message(), options.binding)?,
192 None => (options.binding, None),
193 };
194 let name_id_format = subject
195 .name_id()
196 .format()
197 .map(|format| format.as_uri().to_string());
198 let user = user_from_subject(subject);
199 let raw_options = LoginResponseOptions {
200 in_response_to: request.map(|request| request.message().id().as_str()),
201 relay_state: relay_state.as_deref(),
202 encrypt_then_sign: false,
203 custom: None,
204 };
205 let context = self
206 .raw_identity_provider()
207 .create_login_response_with_overrides(
208 &raw_sp,
209 binding.as_binding(),
210 &user,
211 &raw_options,
212 LoginResponseOverrides {
213 acs: explicit_acs.as_deref(),
214 name_id_format: name_id_format.as_deref(),
215 issuance_lifetime: Some(self.0.issuance_lifetime),
216 },
217 )?;
218 Outbound::<SsoResponse>::try_from(context)
219 }
220}
221fn user_from_subject(subject: Subject) -> crate::entity::User {
222 let name_id = subject.name_id().value().to_string();
223 crate::entity::User::new(name_id)
224}