1use crate::constants::{elements_order, name_id_format, namespace, Binding};
4use crate::error::SamlError;
5use crate::metadata::write::MetadataWriter;
6use crate::util::{is_non_empty_array, normalize_cert_string};
7
8#[derive(Debug, Clone)]
10pub struct Endpoint {
11 pub binding: Binding,
13 pub location: String,
15 pub index: Option<u16>,
17 pub is_default: bool,
19}
20
21impl Endpoint {
22 pub fn new(binding: Binding, location: impl Into<String>) -> Self {
24 Self {
25 binding,
26 location: location.into(),
27 index: None,
28 is_default: false,
29 }
30 }
31}
32
33#[derive(Debug, Clone, Default)]
35pub struct SpMetadataConfig {
36 pub entity_id: String,
38 pub signing_certs: Vec<String>,
40 pub encrypt_certs: Vec<String>,
42 pub authn_requests_signed: bool,
44 pub want_assertions_signed: bool,
46 pub name_id_format: Vec<String>,
48 pub single_logout_service: Vec<Endpoint>,
50 pub assertion_consumer_service: Vec<Endpoint>,
52 pub elements_order: Option<Vec<String>>,
54}
55
56#[derive(Debug, Clone, Default)]
58pub struct IdpMetadataConfig {
59 pub entity_id: String,
61 pub signing_certs: Vec<String>,
63 pub encrypt_certs: Vec<String>,
65 pub want_authn_requests_signed: bool,
67 pub name_id_format: Vec<String>,
69 pub single_sign_on_service: Vec<Endpoint>,
71 pub single_logout_service: Vec<Endpoint>,
73 pub elements_order: Option<Vec<String>>,
75}
76
77fn write_key_descriptor(w: &mut MetadataWriter, use_: &str, cert: &str) {
78 let cert = normalize_cert_string(cert);
79 w.start("KeyDescriptor", &[("use", use_)]);
80 w.start("ds:KeyInfo", &[("xmlns:ds", namespace::DSIG)]);
81 w.start("ds:X509Data", &[]);
82 w.text_element("ds:X509Certificate", &cert);
83 w.end("ds:X509Data");
84 w.end("ds:KeyInfo");
85 w.end("KeyDescriptor");
86}
87
88fn write_key_descriptors(w: &mut MetadataWriter, signing: &[String], encrypt: &[String]) {
89 for cert in signing {
90 write_key_descriptor(w, "signing", cert);
91 }
92 for cert in encrypt {
93 write_key_descriptor(w, "encryption", cert);
94 }
95}
96
97fn write_name_id_formats(w: &mut MetadataWriter, formats: &[String], default_to_email: bool) {
98 if is_non_empty_array(formats) {
99 for format in formats {
100 w.text_element("NameIDFormat", format);
101 }
102 } else if default_to_email {
103 w.text_element("NameIDFormat", name_id_format::EMAIL_ADDRESS);
104 }
105}
106
107fn write_endpoint_attrs(w: &mut MetadataWriter, name: &str, e: &Endpoint, index: Option<String>) {
108 let mut attrs = Vec::with_capacity(4);
109 if let Some(index) = index.as_deref() {
110 attrs.push(("index", index));
111 }
112 if e.is_default {
113 attrs.push(("isDefault", "true"));
114 }
115 attrs.push(("Binding", e.binding.urn()));
116 attrs.push(("Location", e.location.as_str()));
117 w.empty(name, &attrs);
118}
119
120fn write_single_logout(w: &mut MetadataWriter, endpoints: &[Endpoint]) {
121 for endpoint in endpoints {
122 write_endpoint_attrs(w, "SingleLogoutService", endpoint, None);
123 }
124}
125
126fn write_assertion_consumer_service(w: &mut MetadataWriter, endpoints: &[Endpoint]) {
127 for (i, endpoint) in endpoints.iter().enumerate() {
128 let index = endpoint
129 .index
130 .map(|index| index.to_string())
131 .unwrap_or_else(|| i.to_string());
132 write_endpoint_attrs(w, "AssertionConsumerService", endpoint, Some(index));
133 }
134}
135
136fn write_single_sign_on_service(w: &mut MetadataWriter, endpoints: &[Endpoint]) {
137 for endpoint in endpoints {
138 write_endpoint_attrs(w, "SingleSignOnService", endpoint, None);
139 }
140}
141
142fn write_sp_group(w: &mut MetadataWriter, cfg: &SpMetadataConfig, name: &str) {
143 match name {
144 "KeyDescriptor" => write_key_descriptors(w, &cfg.signing_certs, &cfg.encrypt_certs),
145 "NameIDFormat" => write_name_id_formats(w, &cfg.name_id_format, true),
146 "SingleLogoutService" => write_single_logout(w, &cfg.single_logout_service),
147 "AssertionConsumerService" => {
148 write_assertion_consumer_service(w, &cfg.assertion_consumer_service)
149 }
150 _ => {}
151 }
152}
153
154fn idp_group_has_content(cfg: &IdpMetadataConfig, name: &str) -> bool {
155 match name {
156 "KeyDescriptor" => {
157 is_non_empty_array(&cfg.signing_certs) || is_non_empty_array(&cfg.encrypt_certs)
158 }
159 "NameIDFormat" => is_non_empty_array(&cfg.name_id_format),
160 "SingleSignOnService" => is_non_empty_array(&cfg.single_sign_on_service),
161 "SingleLogoutService" => is_non_empty_array(&cfg.single_logout_service),
162 _ => false,
163 }
164}
165
166fn write_idp_group(w: &mut MetadataWriter, cfg: &IdpMetadataConfig, name: &str) {
167 match name {
168 "KeyDescriptor" => write_key_descriptors(w, &cfg.signing_certs, &cfg.encrypt_certs),
169 "NameIDFormat" => write_name_id_formats(w, &cfg.name_id_format, false),
170 "SingleSignOnService" => write_single_sign_on_service(w, &cfg.single_sign_on_service),
171 "SingleLogoutService" => write_single_logout(w, &cfg.single_logout_service),
172 _ => {}
173 }
174}
175
176fn elements_order_or_default(order: &Option<Vec<String>>, default: &[&str]) -> Vec<String> {
177 if let Some(order) = order {
178 order.clone()
179 } else {
180 default.iter().map(|s| s.to_string()).collect()
181 }
182}
183
184fn bool_str(value: bool) -> &'static str {
185 if value {
186 "true"
187 } else {
188 "false"
189 }
190}
191
192pub fn generate_sp_metadata(cfg: &SpMetadataConfig) -> String {
194 let is_custom_order = cfg.elements_order.is_some();
195 let order = elements_order_or_default(&cfg.elements_order, elements_order::DEFAULT);
196 let mut w = MetadataWriter::new();
197 w.start(
198 "EntityDescriptor",
199 &[
200 ("entityID", cfg.entity_id.as_str()),
201 ("xmlns", namespace::METADATA),
202 ("xmlns:assertion", namespace::ASSERTION),
203 ("xmlns:ds", namespace::DSIG),
204 ],
205 );
206 w.start(
207 "SPSSODescriptor",
208 &[
209 ("AuthnRequestsSigned", bool_str(cfg.authn_requests_signed)),
210 ("WantAssertionsSigned", bool_str(cfg.want_assertions_signed)),
211 ("protocolSupportEnumeration", namespace::PROTOCOL),
212 ],
213 );
214 for name in &order {
215 write_sp_group(&mut w, cfg, name);
216 }
217 if is_custom_order
218 && !order
219 .iter()
220 .any(|ordered| ordered == "AssertionConsumerService")
221 {
222 write_sp_group(&mut w, cfg, "AssertionConsumerService");
223 }
224 w.end("SPSSODescriptor");
225 w.end("EntityDescriptor");
226 w.finish()
227}
228
229pub fn generate_idp_metadata(cfg: &IdpMetadataConfig) -> String {
231 let is_custom_order = cfg.elements_order.is_some();
232 let order = elements_order_or_default(&cfg.elements_order, elements_order::idp::DEFAULT);
233 let descriptors = [
234 "KeyDescriptor",
235 "NameIDFormat",
236 "SingleSignOnService",
237 "SingleLogoutService",
238 ];
239 let mut w = MetadataWriter::new();
240 w.start(
241 "EntityDescriptor",
242 &[
243 ("entityID", cfg.entity_id.as_str()),
244 ("xmlns", namespace::METADATA),
245 ("xmlns:assertion", namespace::ASSERTION),
246 ("xmlns:ds", namespace::DSIG),
247 ],
248 );
249 w.start(
250 "IDPSSODescriptor",
251 &[
252 (
253 "WantAuthnRequestsSigned",
254 bool_str(cfg.want_authn_requests_signed),
255 ),
256 ("protocolSupportEnumeration", namespace::PROTOCOL),
257 ],
258 );
259 for name in &order {
260 if descriptors.contains(&name.as_str()) && idp_group_has_content(cfg, name) {
261 write_idp_group(&mut w, cfg, name);
262 }
263 }
264 if is_custom_order {
265 if !order.iter().any(|ordered| ordered == "SingleSignOnService") {
266 write_idp_group(&mut w, cfg, "SingleSignOnService");
267 }
268 } else {
269 for name in descriptors {
270 if idp_group_has_content(cfg, name) && !order.iter().any(|ordered| ordered == name) {
271 write_idp_group(&mut w, cfg, name);
272 }
273 }
274 }
275 w.end("IDPSSODescriptor");
276 w.end("EntityDescriptor");
277 w.finish()
278}
279
280pub fn try_generate_idp_metadata(cfg: &IdpMetadataConfig) -> Result<String, SamlError> {
287 if !is_non_empty_array(&cfg.single_sign_on_service) {
288 return Err(SamlError::MissingMetadata(
289 "SingleSignOnService".to_string(),
290 ));
291 }
292 Ok(generate_idp_metadata(cfg))
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use crate::constants::CertUse;
299 use crate::entity::EntitySetting;
300 use crate::metadata::{IdpMetadata, SpMetadata};
301 use crate::sp::ServiceProvider;
302
303 type TestResult = Result<(), Box<dyn std::error::Error>>;
304
305 #[test]
306 fn sp_metadata_round_trips() -> Result<(), Box<dyn std::error::Error>> {
307 let cfg = SpMetadataConfig {
308 entity_id: "https://sp.example.com/metadata".into(),
309 signing_certs: vec!["MIIBsigning".into()],
310 encrypt_certs: vec!["MIIBencrypt".into()],
311 authn_requests_signed: true,
312 want_assertions_signed: true,
313 name_id_format: vec![name_id_format::EMAIL_ADDRESS.to_string()],
314 single_logout_service: vec![Endpoint::new(
315 Binding::Redirect,
316 "https://sp.example.com/slo",
317 )],
318 assertion_consumer_service: vec![
319 Endpoint {
320 binding: Binding::Post,
321 location: "https://sp.example.com/acs".into(),
322 index: None,
323 is_default: true,
324 },
325 Endpoint::new(Binding::Redirect, "https://sp.example.com/acs-redirect"),
326 ],
327 elements_order: None,
328 };
329 let xml = generate_sp_metadata(&cfg);
330 let parsed = SpMetadata::from_xml(&xml)?;
331 assert_eq!(
332 parsed.get_entity_id(),
333 Some("https://sp.example.com/metadata")
334 );
335 assert!(parsed.is_authn_request_signed());
336 assert!(parsed.is_want_assertions_signed());
337 assert_eq!(
338 parsed
339 .get_assertion_consumer_service(Binding::Post)
340 .as_deref(),
341 Some("https://sp.example.com/acs")
342 );
343 assert_eq!(
344 parsed
345 .get_single_logout_service(Binding::Redirect)
346 .as_deref(),
347 Some("https://sp.example.com/slo")
348 );
349 assert_eq!(
350 parsed.get_x509_certificate(CertUse::Signing).as_deref(),
351 Some("MIIBsigning")
352 );
353 assert_eq!(
354 parsed.get_x509_certificate(CertUse::Encryption).as_deref(),
355 Some("MIIBencrypt")
356 );
357 Ok(())
358 }
359
360 #[test]
361 fn sp_elements_order_respected() {
362 let cfg = SpMetadataConfig {
363 entity_id: "x".into(),
364 single_logout_service: vec![Endpoint::new(Binding::Redirect, "https://sp/slo")],
365 assertion_consumer_service: vec![Endpoint::new(Binding::Post, "https://sp/acs")],
366 ..Default::default()
367 };
368 let xml = generate_sp_metadata(&cfg);
369 let slo = xml.find("SingleLogoutService").unwrap_or(usize::MAX);
370 let acs = xml.find("AssertionConsumerService").unwrap_or(0);
371 assert!(slo < acs);
373 }
374
375 #[test]
376 fn idp_metadata_round_trips() -> Result<(), Box<dyn std::error::Error>> {
377 let cfg = IdpMetadataConfig {
378 entity_id: "https://idp.example.com/metadata".into(),
379 signing_certs: vec!["MIIBidp".into()],
380 want_authn_requests_signed: true,
381 single_sign_on_service: vec![Endpoint::new(
382 Binding::Redirect,
383 "https://idp.example.com/sso",
384 )],
385 ..Default::default()
386 };
387 let xml = generate_idp_metadata(&cfg);
388 let parsed = IdpMetadata::from_xml(&xml)?;
389 assert!(parsed.is_want_authn_requests_signed());
390 assert_eq!(
391 parsed
392 .get_single_sign_on_service(Binding::Redirect)
393 .as_deref(),
394 Some("https://idp.example.com/sso")
395 );
396 assert_eq!(
397 parsed.get_x509_certificate(CertUse::Signing).as_deref(),
398 Some("MIIBidp")
399 );
400 Ok(())
401 }
402
403 #[test]
404 fn try_generate_idp_metadata_rejects_missing_sso() {
405 let cfg = IdpMetadataConfig {
406 entity_id: "https://idp.example.com/metadata".into(),
407 ..Default::default()
408 };
409
410 let result = try_generate_idp_metadata(&cfg);
411
412 assert!(matches!(
413 result,
414 Err(SamlError::MissingMetadata(name)) if name == "SingleSignOnService"
415 ));
416 }
417
418 #[test]
419 fn sp_from_config_escapes_name_id_format_xml_markup() -> TestResult {
420 let injected_format = format!(
421 "{}</NameIDFormat><SingleLogoutService Binding=\"{}\" Location=\"https://evil.example/slo\"/><NameIDFormat>",
422 name_id_format::EMAIL_ADDRESS,
423 Binding::Redirect.urn()
424 );
425 let cfg = SpMetadataConfig {
426 entity_id: "https://sp.example.com/metadata".into(),
427 name_id_format: vec![injected_format.clone()],
428 assertion_consumer_service: vec![Endpoint::new(Binding::Post, "https://sp/acs")],
429 ..Default::default()
430 };
431
432 let sp = ServiceProvider::from_config(&cfg, EntitySetting::default())?;
433
434 assert!(sp
435 .metadata
436 .get_single_logout_service(Binding::Redirect)
437 .is_none());
438 assert_eq!(sp.setting.name_id_format, vec![injected_format]);
439 assert!(sp.metadata_xml().contains("<SingleLogoutService"));
440 Ok(())
441 }
442
443 #[test]
444 fn sp_metadata_escapes_entity_id_attribute_markup() -> TestResult {
445 let entity_id = "https://sp.example.com/metadata\" ID=\"_evil";
446 let cfg = SpMetadataConfig {
447 entity_id: entity_id.into(),
448 assertion_consumer_service: vec![Endpoint::new(Binding::Post, "https://sp/acs")],
449 ..Default::default()
450 };
451
452 let xml = generate_sp_metadata(&cfg);
453 let parsed = SpMetadata::from_xml(&xml)?;
454
455 assert!(!xml.contains(" ID=\"_evil\""));
456 assert!(xml.contains("" ID="_evil"));
457 assert_eq!(parsed.get_entity_id(), Some(entity_id));
458 Ok(())
459 }
460
461 #[test]
462 fn sp_metadata_escapes_certificate_text_markup() -> TestResult {
463 let injected_cert = concat!(
464 "MIIB</ds:X509Certificate></ds:X509Data></ds:KeyInfo></KeyDescriptor>",
465 "<NameIDFormat>evil</NameIDFormat>",
466 "<KeyDescriptor><ds:KeyInfo><ds:X509Data><ds:X509Certificate>tail"
467 );
468 let cfg = SpMetadataConfig {
469 entity_id: "https://sp.example.com/metadata".into(),
470 signing_certs: vec![injected_cert.into()],
471 assertion_consumer_service: vec![Endpoint::new(Binding::Post, "https://sp/acs")],
472 elements_order: Some(vec![
473 "KeyDescriptor".into(),
474 "AssertionConsumerService".into(),
475 ]),
476 ..Default::default()
477 };
478
479 let xml = generate_sp_metadata(&cfg);
480 let parsed = SpMetadata::from_xml(&xml)?;
481
482 assert!(xml.contains("<NameIDFormat>evil</NameIDFormat>"));
483 assert!(parsed.get_name_id_format().is_empty());
484 assert_eq!(parsed.x509_certificates(CertUse::Signing).len(), 1);
485 Ok(())
486 }
487
488 #[test]
489 fn sp_custom_elements_order_preserves_omitted_populated_acs() {
490 let cfg = SpMetadataConfig {
491 entity_id: "https://sp.example.com/metadata".into(),
492 signing_certs: vec!["MIIBsigning".into()],
493 name_id_format: vec![name_id_format::EMAIL_ADDRESS.to_string()],
494 single_logout_service: vec![Endpoint::new(Binding::Redirect, "https://sp/slo")],
495 assertion_consumer_service: vec![Endpoint::new(Binding::Post, "https://sp/acs")],
496 elements_order: Some(vec!["KeyDescriptor".into()]),
497 ..Default::default()
498 };
499
500 let xml = generate_sp_metadata(&cfg);
501 let key = xml.find("<KeyDescriptor").unwrap_or(usize::MAX);
502 let acs = xml.find("<AssertionConsumerService").unwrap_or(usize::MAX);
503
504 assert_eq!(xml.matches("<AssertionConsumerService").count(), 1);
505 assert!(key < acs);
506 assert!(!xml.contains("<NameIDFormat"));
507 assert!(!xml.contains("<SingleLogoutService"));
508 }
509
510 #[test]
511 fn idp_metadata_escapes_endpoint_location_attribute_markup() -> TestResult {
512 let injected_location = format!(
513 "https://idp/sso\"/><SingleLogoutService Binding=\"{}\" Location=\"https://evil.example/slo",
514 Binding::Redirect.urn()
515 );
516 let cfg = IdpMetadataConfig {
517 entity_id: "https://idp.example.com/metadata".into(),
518 single_sign_on_service: vec![Endpoint::new(
519 Binding::Redirect,
520 injected_location.clone(),
521 )],
522 ..Default::default()
523 };
524
525 let xml = try_generate_idp_metadata(&cfg)?;
526 let parsed = IdpMetadata::from_xml(&xml)?;
527
528 assert!(!xml.contains("<SingleLogoutService"));
529 assert_eq!(
530 parsed
531 .get_single_sign_on_service(Binding::Redirect)
532 .as_deref(),
533 Some(injected_location.as_str())
534 );
535 assert!(parsed
536 .get_single_logout_service(Binding::Redirect)
537 .is_none());
538 Ok(())
539 }
540
541 #[test]
542 fn idp_default_elements_order_matches_historical_output() {
543 let cfg = IdpMetadataConfig {
544 entity_id: "https://idp.example.com/metadata".into(),
545 signing_certs: vec!["MIIBsigning".into()],
546 name_id_format: vec![name_id_format::EMAIL_ADDRESS.to_string()],
547 single_sign_on_service: vec![Endpoint::new(Binding::Redirect, "https://idp/sso")],
548 single_logout_service: vec![Endpoint::new(Binding::Redirect, "https://idp/slo")],
549 ..Default::default()
550 };
551
552 let xml = generate_idp_metadata(&cfg);
553 let key = xml.find("<KeyDescriptor").unwrap_or(usize::MAX);
554 let name_id = xml.find("<NameIDFormat").unwrap_or(usize::MAX);
555 let sso = xml.find("<SingleSignOnService").unwrap_or(usize::MAX);
556 let slo = xml.find("<SingleLogoutService").unwrap_or(usize::MAX);
557
558 assert!(key < name_id);
559 assert!(name_id < sso);
560 assert!(sso < slo);
561 }
562
563 #[test]
564 fn idp_custom_elements_order_can_place_sso_before_key_descriptor() {
565 let cfg = IdpMetadataConfig {
566 entity_id: "https://idp.example.com/metadata".into(),
567 signing_certs: vec!["MIIBsigning".into()],
568 name_id_format: vec![name_id_format::EMAIL_ADDRESS.to_string()],
569 single_sign_on_service: vec![Endpoint::new(Binding::Redirect, "https://idp/sso")],
570 single_logout_service: vec![Endpoint::new(Binding::Redirect, "https://idp/slo")],
571 elements_order: Some(vec![
572 "SingleSignOnService".into(),
573 "KeyDescriptor".into(),
574 "NameIDFormat".into(),
575 "SingleLogoutService".into(),
576 ]),
577 ..Default::default()
578 };
579
580 let xml = generate_idp_metadata(&cfg);
581 let sso = xml.find("<SingleSignOnService").unwrap_or(usize::MAX);
582 let key = xml.find("<KeyDescriptor").unwrap_or(usize::MAX);
583 let name_id = xml.find("<NameIDFormat").unwrap_or(usize::MAX);
584 let slo = xml.find("<SingleLogoutService").unwrap_or(usize::MAX);
585
586 assert!(sso < key);
587 assert!(key < name_id);
588 assert!(name_id < slo);
589 }
590
591 #[test]
592 fn idp_elements_order_filters_empty_groups() {
593 let cfg = IdpMetadataConfig {
594 entity_id: "https://idp.example.com/metadata".into(),
595 single_sign_on_service: vec![Endpoint::new(Binding::Redirect, "https://idp/sso")],
596 single_logout_service: vec![Endpoint::new(Binding::Redirect, "https://idp/slo")],
597 elements_order: Some(vec![
598 "KeyDescriptor".into(),
599 "NameIDFormat".into(),
600 "SingleSignOnService".into(),
601 "SingleLogoutService".into(),
602 ]),
603 ..Default::default()
604 };
605
606 let xml = generate_idp_metadata(&cfg);
607
608 assert!(!xml.contains("<KeyDescriptor"));
609 assert!(!xml.contains("<NameIDFormat"));
610 assert!(xml.contains("<SingleSignOnService"));
611 assert!(xml.contains("<SingleLogoutService"));
612 }
613
614 #[test]
615 fn idp_custom_elements_order_omits_unlisted_optional_groups() {
616 let cfg = IdpMetadataConfig {
617 entity_id: "https://idp.example.com/metadata".into(),
618 signing_certs: vec!["MIIBsigning".into()],
619 name_id_format: vec![name_id_format::EMAIL_ADDRESS.to_string()],
620 single_sign_on_service: vec![Endpoint::new(Binding::Redirect, "https://idp/sso")],
621 single_logout_service: vec![Endpoint::new(Binding::Redirect, "https://idp/slo")],
622 elements_order: Some(vec!["SingleSignOnService".into()]),
623 ..Default::default()
624 };
625
626 let xml = generate_idp_metadata(&cfg);
627
628 assert_eq!(xml.matches("<SingleSignOnService").count(), 1);
629 assert!(!xml.contains("<KeyDescriptor"));
630 assert!(!xml.contains("<NameIDFormat"));
631 assert!(!xml.contains("<SingleLogoutService"));
632 }
633
634 #[test]
635 fn idp_custom_elements_order_preserves_omitted_populated_sso() {
636 let cfg = IdpMetadataConfig {
637 entity_id: "https://idp.example.com/metadata".into(),
638 signing_certs: vec!["MIIBsigning".into()],
639 single_sign_on_service: vec![Endpoint::new(Binding::Redirect, "https://idp/sso")],
640 elements_order: Some(vec!["KeyDescriptor".into()]),
641 ..Default::default()
642 };
643
644 let xml = generate_idp_metadata(&cfg);
645 let key = xml.find("<KeyDescriptor").unwrap_or(usize::MAX);
646 let sso = xml.find("<SingleSignOnService").unwrap_or(usize::MAX);
647
648 assert_eq!(xml.matches("<SingleSignOnService").count(), 1);
649 assert!(key < sso);
650 }
651
652 #[test]
653 fn idp_elements_order_profiles_match_upstream() {
654 assert_eq!(
655 elements_order::idp::DEFAULT,
656 [
657 "KeyDescriptor",
658 "NameIDFormat",
659 "SingleSignOnService",
660 "SingleLogoutService",
661 ]
662 );
663 assert_eq!(
664 elements_order::idp::ONELOGIN,
665 [
666 "KeyDescriptor",
667 "NameIDFormat",
668 "SingleLogoutService",
669 "SingleSignOnService",
670 ]
671 );
672 assert_eq!(
673 elements_order::idp::SHIBBOLETH,
674 [
675 "KeyDescriptor",
676 "SingleLogoutService",
677 "NameIDFormat",
678 "SingleSignOnService",
679 ]
680 );
681 }
682}