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