Skip to main content

slo/
slo.rs

1//! End-to-end typed Single Logout after a typed SSO session.
2//!
3//! Run with: `cargo run -p saml-rs --example slo`
4//! (the `crypto-bergshamra` feature is on by default).
5
6#[cfg(feature = "crypto-bergshamra")]
7fn main() -> Result<(), Box<dyn std::error::Error>> {
8    use saml_rs::{
9        AcsEndpoint, AuthnRequest, BrowserInput, CertificatePem, Credentials, EntityId, IdpConfig,
10        IdpDescriptor, IdpValidationPolicy, LogoutRequest, LogoutResponse, MetadataTrustPolicy,
11        NameId, PrivateKeyPem, ReplayPolicy, RespondSlo, RespondSso, Saml, SamlValidationContext,
12        SloEndpoint, SpConfig, SpDescriptor, SpValidationPolicy, SsoEndpoint, SsoResponse,
13        StartSlo, StartSso, Subject,
14    };
15    use std::time::SystemTime;
16
17    let privkey = include_str!("../tests/fixtures/key/sp_privkey.pem");
18    let cert = include_str!("../tests/fixtures/key/sp_signing_cert.cer");
19    let credentials = || Credentials {
20        signing_key: Some(PrivateKeyPem::new(privkey)),
21        signing_certificate: Some(CertificatePem::new(cert)),
22        ..Credentials::default()
23    };
24    let validation =
25        || SamlValidationContext::new(SystemTime::now(), ReplayPolicy::DisabledForCompatibility);
26
27    let sp = Saml::sp(
28        SpConfig::builder(EntityId::try_new("https://sp.example.com/metadata")?)
29            .acs_endpoint(AcsEndpoint::post("https://sp.example.com/acs")?)
30            .slo_endpoint(SloEndpoint::post("https://sp.example.com/slo")?)
31            .credentials(credentials())
32            .validation(SpValidationPolicy::strict())
33            .build()?,
34    )?;
35    let idp = Saml::idp(
36        IdpConfig::builder(EntityId::try_new("https://idp.example.com/metadata")?)
37            .sso_endpoint(SsoEndpoint::post("https://idp.example.com/sso")?)
38            .slo_endpoint(SloEndpoint::post("https://idp.example.com/slo")?)
39            .credentials(credentials())
40            .validation(IdpValidationPolicy::strict())
41            .build()?,
42    )?;
43    let sp_descriptor = SpDescriptor::from_metadata_xml_for(
44        EntityId::try_new("https://sp.example.com/metadata")?,
45        sp.metadata_xml(),
46        MetadataTrustPolicy::UnsignedForCompatibility,
47    )?;
48    let idp_descriptor = IdpDescriptor::from_metadata_xml_for(
49        EntityId::try_new("https://idp.example.com/metadata")?,
50        idp.metadata_xml(),
51        MetadataTrustPolicy::UnsignedForCompatibility,
52    )?;
53
54    let sso = sp.start_sso(&idp_descriptor, StartSso::post())?;
55    let request = idp.receive_sso(
56        &sp_descriptor,
57        BrowserInput::<AuthnRequest>::post(sso.outbound.post_form()?.fields().to_vec()),
58        validation(),
59    )?;
60    let response = idp.respond_sso(
61        &sp_descriptor,
62        &request,
63        Subject::new(NameId::new("alice@example.com", None), Vec::new()),
64        RespondSso::post(),
65    )?;
66    let session = sp.finish_sso(
67        &idp_descriptor,
68        &sso.pending,
69        BrowserInput::<SsoResponse>::post(response.post_form()?.fields().to_vec()),
70        validation(),
71    )?;
72
73    let subject = session
74        .logout_subject()
75        .ok_or("session has no logout subject")?;
76    let logout = sp.start_slo(&idp_descriptor, subject, StartSlo::post())?;
77    println!("SP  -> LogoutRequest id = {}", logout.pending.id().as_str());
78
79    let logout_request = idp.receive_slo(
80        &sp_descriptor,
81        BrowserInput::<LogoutRequest>::post(logout.outbound.post_form()?.fields().to_vec()),
82        validation(),
83    )?;
84    let logout_response = idp.respond_slo(
85        &sp_descriptor,
86        &logout_request,
87        RespondSlo::post().relay_state(logout.pending.relay_state().clone()),
88    )?;
89    let completed = sp.finish_slo(
90        &idp_descriptor,
91        &logout.pending,
92        BrowserInput::<LogoutResponse>::post(logout_response.post_form()?.fields().to_vec()),
93        validation(),
94    )?;
95    println!(
96        "SP  <- LogoutResponse status = {}",
97        completed.status().unwrap_or("missing")
98    );
99    Ok(())
100}
101
102#[cfg(not(feature = "crypto-bergshamra"))]
103fn main() {
104    eprintln!("Enable the `crypto-bergshamra` feature (on by default) to sign and verify.");
105}