1use std::collections::HashMap;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum SamlBinding {
8 HttpRedirect,
10 HttpPost,
12 HttpArtifact,
14 Soap,
16}
17
18impl SamlBinding {
19 pub fn uri(&self) -> &'static str {
21 match self {
22 SamlBinding::HttpRedirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
23 SamlBinding::HttpPost => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
24 SamlBinding::HttpArtifact => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact",
25 SamlBinding::Soap => "urn:oasis:names:tc:SAML:2.0:bindings:SOAP",
26 }
27 }
28
29 pub fn from_uri(uri: &str) -> Option<Self> {
31 match uri {
32 "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" => Some(SamlBinding::HttpRedirect),
33 "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" => Some(SamlBinding::HttpPost),
34 "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" => Some(SamlBinding::HttpArtifact),
35 "urn:oasis:names:tc:SAML:2.0:bindings:SOAP" => Some(SamlBinding::Soap),
36 _ => None,
37 }
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum NameIdFormat {
44 Unspecified,
46 EmailAddress,
48 X509SubjectName,
50 WindowsDomainQualifiedName,
52 KerberosPrincipalName,
54 EntityIdentifier,
56 Persistent,
58 Transient,
60 Custom(String),
62}
63
64impl NameIdFormat {
65 pub fn uri(&self) -> &str {
67 match self {
68 NameIdFormat::Unspecified => "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
69 NameIdFormat::EmailAddress => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
70 NameIdFormat::X509SubjectName => "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName",
71 NameIdFormat::WindowsDomainQualifiedName => "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName",
72 NameIdFormat::KerberosPrincipalName => "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos",
73 NameIdFormat::EntityIdentifier => "urn:oasis:names:tc:SAML:2.0:nameid-format:entity",
74 NameIdFormat::Persistent => "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
75 NameIdFormat::Transient => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
76 NameIdFormat::Custom(uri) => uri,
77 }
78 }
79
80 pub fn from_uri(uri: &str) -> Self {
82 match uri {
83 "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" => NameIdFormat::Unspecified,
84 "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" => NameIdFormat::EmailAddress,
85 "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" => NameIdFormat::X509SubjectName,
86 "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName" => NameIdFormat::WindowsDomainQualifiedName,
87 "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos" => NameIdFormat::KerberosPrincipalName,
88 "urn:oasis:names:tc:SAML:2.0:nameid-format:entity" => NameIdFormat::EntityIdentifier,
89 "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" => NameIdFormat::Persistent,
90 "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" => NameIdFormat::Transient,
91 _ => NameIdFormat::Custom(uri.to_string()),
92 }
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct ServiceProviderConfig {
99 pub entity_id: String,
101
102 pub acs_url: String,
104
105 pub slo_url: Option<String>,
107
108 pub name_id_format: NameIdFormat,
110
111 pub required_attributes: Vec<String>,
113
114 pub optional_attributes: Vec<String>,
116
117 pub want_assertions_signed: bool,
119
120 pub want_response_signed: bool,
122
123 pub private_key: Option<String>,
125
126 pub certificate: Option<String>,
128}
129
130impl ServiceProviderConfig {
131 pub fn new(entity_id: impl Into<String>, acs_url: impl Into<String>) -> Self {
133 Self {
134 entity_id: entity_id.into(),
135 acs_url: acs_url.into(),
136 slo_url: None,
137 name_id_format: NameIdFormat::EmailAddress,
138 required_attributes: Vec::new(),
139 optional_attributes: Vec::new(),
140 want_assertions_signed: true,
141 want_response_signed: true,
142 private_key: None,
143 certificate: None,
144 }
145 }
146
147 pub fn with_slo_url(mut self, url: impl Into<String>) -> Self {
149 self.slo_url = Some(url.into());
150 self
151 }
152
153 pub fn with_name_id_format(mut self, format: NameIdFormat) -> Self {
155 self.name_id_format = format;
156 self
157 }
158
159 pub fn add_required_attribute(mut self, attr: impl Into<String>) -> Self {
161 self.required_attributes.push(attr.into());
162 self
163 }
164
165 pub fn add_optional_attribute(mut self, attr: impl Into<String>) -> Self {
167 self.optional_attributes.push(attr.into());
168 self
169 }
170
171 pub fn with_signing_key(mut self, private_key: impl Into<String>, certificate: impl Into<String>) -> Self {
173 self.private_key = Some(private_key.into());
174 self.certificate = Some(certificate.into());
175 self
176 }
177}
178
179#[derive(Debug, Clone)]
181pub struct IdentityProviderConfig {
182 pub entity_id: String,
184
185 pub sso_url: String,
187
188 pub slo_url: Option<String>,
190
191 pub sso_binding: SamlBinding,
193
194 pub slo_binding: SamlBinding,
196
197 pub signing_certificate: String,
199
200 pub encryption_certificate: Option<String>,
202
203 pub extra: HashMap<String, String>,
205}
206
207impl IdentityProviderConfig {
208 pub fn new(entity_id: impl Into<String>, sso_url: impl Into<String>, certificate: impl Into<String>) -> Self {
210 Self {
211 entity_id: entity_id.into(),
212 sso_url: sso_url.into(),
213 slo_url: None,
214 sso_binding: SamlBinding::HttpRedirect,
215 slo_binding: SamlBinding::HttpRedirect,
216 signing_certificate: certificate.into(),
217 encryption_certificate: None,
218 extra: HashMap::new(),
219 }
220 }
221
222 pub fn with_slo_url(mut self, url: impl Into<String>) -> Self {
224 self.slo_url = Some(url.into());
225 self
226 }
227
228 pub fn with_sso_binding(mut self, binding: SamlBinding) -> Self {
230 self.sso_binding = binding;
231 self
232 }
233
234 pub fn with_slo_binding(mut self, binding: SamlBinding) -> Self {
236 self.slo_binding = binding;
237 self
238 }
239
240 pub fn with_encryption_certificate(mut self, cert: impl Into<String>) -> Self {
242 self.encryption_certificate = Some(cert.into());
243 self
244 }
245}
246
247#[derive(Debug, Clone)]
249pub struct SamlConfig {
250 pub sp: ServiceProviderConfig,
252
253 pub idp: IdentityProviderConfig,
255
256 pub clock_skew_seconds: i64,
258
259 pub assertion_validity_seconds: i64,
261
262 pub validate_audience: bool,
264
265 pub validate_issuer: bool,
267
268 pub validate_destination: bool,
270
271 pub detect_replay: bool,
273}
274
275impl SamlConfig {
276 pub fn new(sp: ServiceProviderConfig, idp: IdentityProviderConfig) -> Self {
278 Self {
279 sp,
280 idp,
281 clock_skew_seconds: 60,
282 assertion_validity_seconds: 300,
283 validate_audience: true,
284 validate_issuer: true,
285 validate_destination: true,
286 detect_replay: true,
287 }
288 }
289
290 pub fn with_clock_skew(mut self, seconds: i64) -> Self {
292 self.clock_skew_seconds = seconds;
293 self
294 }
295
296 pub fn with_assertion_validity(mut self, seconds: i64) -> Self {
298 self.assertion_validity_seconds = seconds;
299 self
300 }
301}