Skip to main content

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}