Skip to main content

saml_rs/browser/
endpoints.rs

1//! Browser endpoint wrappers for SAML metadata locations.
2//!
3//! References: SAML Metadata 2.0 <https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf>.
4
5use super::bindings::{LogoutBinding, SsoRequestBinding, SsoResponseBinding};
6use crate::error::SamlError;
7use crate::metadata::Endpoint;
8use crate::model::EndpointUrl;
9
10/// Single Sign-On service endpoint.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct SsoEndpoint {
13    binding: SsoRequestBinding,
14    url: EndpointUrl,
15}
16
17impl SsoEndpoint {
18    /// Create an SSO endpoint from an already validated URL.
19    pub fn new(binding: SsoRequestBinding, url: EndpointUrl) -> Self {
20        Self { binding, url }
21    }
22
23    /// Create an HTTP-Redirect SSO endpoint.
24    ///
25    /// # Errors
26    ///
27    /// Returns [`SamlError`] if `url` fails [`EndpointUrl::try_new`]
28    /// validation.
29    pub fn redirect(url: impl Into<String>) -> Result<Self, SamlError> {
30        Ok(Self::new(
31            SsoRequestBinding::Redirect,
32            EndpointUrl::try_new(url)?,
33        ))
34    }
35
36    /// Create an HTTP-POST SSO endpoint.
37    ///
38    /// # Errors
39    ///
40    /// Returns [`SamlError`] if `url` fails [`EndpointUrl::try_new`]
41    /// validation.
42    pub fn post(url: impl Into<String>) -> Result<Self, SamlError> {
43        Ok(Self::new(
44            SsoRequestBinding::Post,
45            EndpointUrl::try_new(url)?,
46        ))
47    }
48
49    /// Create an HTTP-POST-SimpleSign SSO endpoint.
50    ///
51    /// # Errors
52    ///
53    /// Returns [`SamlError`] if `url` fails [`EndpointUrl::try_new`]
54    /// validation.
55    pub fn simple_sign(url: impl Into<String>) -> Result<Self, SamlError> {
56        Ok(Self::new(
57            SsoRequestBinding::SimpleSign,
58            EndpointUrl::try_new(url)?,
59        ))
60    }
61
62    /// Narrow a raw metadata endpoint into an SSO endpoint.
63    ///
64    /// # Errors
65    ///
66    /// Returns [`SamlError`] if the raw binding is not valid for SSO requests
67    /// or if the endpoint location fails [`EndpointUrl::try_new`] validation.
68    pub fn try_from_raw(endpoint: Endpoint) -> Result<Self, SamlError> {
69        Ok(Self::new(
70            SsoRequestBinding::try_from(endpoint.binding)?,
71            EndpointUrl::try_new(endpoint.location)?,
72        ))
73    }
74
75    /// Convert to the raw metadata endpoint shape.
76    pub fn to_raw(&self) -> Endpoint {
77        Endpoint {
78            binding: self.binding.as_binding(),
79            location: self.url.as_str().to_string(),
80            index: None,
81            is_default: false,
82        }
83    }
84
85    /// Endpoint binding.
86    pub fn binding(&self) -> SsoRequestBinding {
87        self.binding
88    }
89
90    /// Metadata endpoint location.
91    pub fn location(&self) -> &EndpointUrl {
92        &self.url
93    }
94}
95
96/// Assertion Consumer Service endpoint.
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct AcsEndpoint {
99    binding: SsoResponseBinding,
100    url: EndpointUrl,
101    index: Option<u16>,
102    is_default: bool,
103}
104
105impl AcsEndpoint {
106    /// Create an ACS endpoint from an already validated URL.
107    pub fn new(binding: SsoResponseBinding, url: EndpointUrl) -> Self {
108        Self {
109            binding,
110            url,
111            index: None,
112            is_default: false,
113        }
114    }
115
116    /// Create an HTTP-POST ACS endpoint.
117    ///
118    /// # Errors
119    ///
120    /// Returns [`SamlError`] if `url` fails [`EndpointUrl::try_new`]
121    /// validation.
122    pub fn post(url: impl Into<String>) -> Result<Self, SamlError> {
123        Ok(Self::new(
124            SsoResponseBinding::Post,
125            EndpointUrl::try_new(url)?,
126        ))
127    }
128
129    /// Create an HTTP-POST-SimpleSign ACS endpoint.
130    ///
131    /// # Errors
132    ///
133    /// Returns [`SamlError`] if `url` fails [`EndpointUrl::try_new`]
134    /// validation.
135    pub fn simple_sign(url: impl Into<String>) -> Result<Self, SamlError> {
136        Ok(Self::new(
137            SsoResponseBinding::SimpleSign,
138            EndpointUrl::try_new(url)?,
139        ))
140    }
141
142    /// Set the ACS index advertised in metadata.
143    pub fn with_index(mut self, index: u16) -> Self {
144        self.index = Some(index);
145        self
146    }
147
148    /// Mark this ACS endpoint as the default endpoint in metadata.
149    pub fn mark_default(mut self) -> Self {
150        self.is_default = true;
151        self
152    }
153
154    pub(crate) fn with_default_flag(mut self, is_default: bool) -> Self {
155        self.is_default = is_default;
156        self
157    }
158
159    /// Narrow a raw metadata endpoint into an ACS endpoint.
160    ///
161    /// # Errors
162    ///
163    /// Returns [`SamlError`] if the raw binding is not valid for SSO responses
164    /// or if the endpoint location fails [`EndpointUrl::try_new`] validation.
165    pub fn try_from_raw(endpoint: Endpoint) -> Result<Self, SamlError> {
166        Ok(Self {
167            binding: SsoResponseBinding::try_from(endpoint.binding)?,
168            url: EndpointUrl::try_new(endpoint.location)?,
169            index: endpoint.index,
170            is_default: endpoint.is_default,
171        })
172    }
173
174    /// Convert to the raw metadata endpoint shape.
175    pub fn to_raw(&self) -> Endpoint {
176        Endpoint {
177            binding: self.binding.as_binding(),
178            location: self.url.as_str().to_string(),
179            index: self.index,
180            is_default: self.is_default,
181        }
182    }
183
184    /// Endpoint binding.
185    pub fn binding(&self) -> SsoResponseBinding {
186        self.binding
187    }
188
189    /// Metadata endpoint location.
190    pub fn location(&self) -> &EndpointUrl {
191        &self.url
192    }
193
194    /// ACS index advertised in metadata.
195    pub fn index(&self) -> Option<u16> {
196        self.index
197    }
198
199    /// Whether this ACS endpoint is the default metadata endpoint.
200    pub fn is_default(&self) -> bool {
201        self.is_default
202    }
203}
204
205/// Single Logout service endpoint.
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub struct SloEndpoint {
208    binding: LogoutBinding,
209    url: EndpointUrl,
210}
211
212impl SloEndpoint {
213    /// Create an SLO endpoint from an already validated URL.
214    pub fn new(binding: LogoutBinding, url: EndpointUrl) -> Self {
215        Self { binding, url }
216    }
217
218    /// Create an HTTP-Redirect SLO endpoint.
219    ///
220    /// # Errors
221    ///
222    /// Returns [`SamlError`] if `url` fails [`EndpointUrl::try_new`]
223    /// validation.
224    pub fn redirect(url: impl Into<String>) -> Result<Self, SamlError> {
225        Ok(Self::new(
226            LogoutBinding::Redirect,
227            EndpointUrl::try_new(url)?,
228        ))
229    }
230
231    /// Create an HTTP-POST SLO endpoint.
232    ///
233    /// # Errors
234    ///
235    /// Returns [`SamlError`] if `url` fails [`EndpointUrl::try_new`]
236    /// validation.
237    pub fn post(url: impl Into<String>) -> Result<Self, SamlError> {
238        Ok(Self::new(LogoutBinding::Post, EndpointUrl::try_new(url)?))
239    }
240
241    /// Create an HTTP-POST-SimpleSign SLO endpoint.
242    ///
243    /// # Errors
244    ///
245    /// Returns [`SamlError`] if `url` fails [`EndpointUrl::try_new`]
246    /// validation.
247    pub fn simple_sign(url: impl Into<String>) -> Result<Self, SamlError> {
248        Ok(Self::new(
249            LogoutBinding::SimpleSign,
250            EndpointUrl::try_new(url)?,
251        ))
252    }
253
254    /// Narrow a raw metadata endpoint into an SLO endpoint.
255    ///
256    /// # Errors
257    ///
258    /// Returns [`SamlError`] if the raw binding is not valid for logout or if
259    /// the endpoint location fails [`EndpointUrl::try_new`] validation.
260    pub fn try_from_raw(endpoint: Endpoint) -> Result<Self, SamlError> {
261        Ok(Self::new(
262            LogoutBinding::try_from(endpoint.binding)?,
263            EndpointUrl::try_new(endpoint.location)?,
264        ))
265    }
266
267    /// Convert to the raw metadata endpoint shape.
268    pub fn to_raw(&self) -> Endpoint {
269        Endpoint {
270            binding: self.binding.as_binding(),
271            location: self.url.as_str().to_string(),
272            index: None,
273            is_default: false,
274        }
275    }
276
277    /// Endpoint binding.
278    pub fn binding(&self) -> LogoutBinding {
279        self.binding
280    }
281
282    /// Metadata endpoint location.
283    pub fn location(&self) -> &EndpointUrl {
284        &self.url
285    }
286}