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#![allow(
6    deprecated,
7    reason = "the example keeps exercising the deprecated strict() preset"
8)]
9
10#[cfg(any(
11    feature = "crypto-rustcrypto",
12    feature = "crypto-aws-lc",
13    feature = "crypto-fips"
14))]
15fn main() -> Result<(), Box<dyn std::error::Error>> {
16    use saml_rs::{
17        AcsEndpoint, AuthnRequest, BrowserInput, CertificatePem, Credentials, EntityId, IdpConfig,
18        IdpDescriptor, IdpValidationPolicy, LogoutRequest, LogoutResponse, MetadataTrustPolicy,
19        NameId, PrivateKeyPem, ReplayPolicy, RespondSlo, RespondSso, Saml, SamlValidationContext,
20        SloEndpoint, SpConfig, SpDescriptor, SpValidationPolicy, SsoEndpoint, SsoResponse,
21        StartSlo, StartSso, Subject,
22    };
23    use std::time::SystemTime;
24
25    let privkey = include_str!("../tests/fixtures/key/sp_privkey.pem");
26    let cert = include_str!("../tests/fixtures/key/sp_signing_cert.cer");
27    let credentials = || Credentials {
28        signing_key: Some(PrivateKeyPem::new(privkey)),
29        signing_certificate: Some(CertificatePem::new(cert)),
30        ..Credentials::default()
31    };
32    // DisabledForCompatibility preserves the raw API, which has no replay cache.
33    let validation =
34        || SamlValidationContext::new(SystemTime::now(), ReplayPolicy::DisabledForCompatibility);
35
36    let sp = Saml::sp(
37        SpConfig::builder(EntityId::try_new("https://sp.example.com/metadata")?)
38            .acs_endpoint(AcsEndpoint::post("https://sp.example.com/acs")?)
39            .slo_endpoint(SloEndpoint::post("https://sp.example.com/slo")?)
40            .credentials(credentials())
41            .validation(SpValidationPolicy::strict())
42            .build()?,
43    )?;
44    let idp = Saml::idp(
45        IdpConfig::builder(EntityId::try_new("https://idp.example.com/metadata")?)
46            .sso_endpoint(SsoEndpoint::post("https://idp.example.com/sso")?)
47            .slo_endpoint(SloEndpoint::post("https://idp.example.com/slo")?)
48            .credentials(credentials())
49            .validation(IdpValidationPolicy::strict())
50            .build()?,
51    )?;
52    let sp_descriptor = SpDescriptor::from_metadata_xml_for(
53        EntityId::try_new("https://sp.example.com/metadata")?,
54        sp.metadata_xml(),
55        // UnsignedForCompatibility preserves an unsigned samlify-port metadata import.
56        MetadataTrustPolicy::UnsignedForCompatibility,
57    )?;
58    let idp_descriptor = IdpDescriptor::from_metadata_xml_for(
59        EntityId::try_new("https://idp.example.com/metadata")?,
60        idp.metadata_xml(),
61        // UnsignedForCompatibility preserves an unsigned samlify-port metadata import.
62        MetadataTrustPolicy::UnsignedForCompatibility,
63    )?;
64
65    let sso = sp.start_sso(&idp_descriptor, StartSso::post())?;
66    let request = idp.receive_sso(
67        &sp_descriptor,
68        BrowserInput::<AuthnRequest>::post(sso.outbound.post_form()?.fields().to_vec()),
69        validation(),
70    )?;
71    let response = idp.respond_sso(
72        &sp_descriptor,
73        &request,
74        Subject::new(NameId::new("alice@example.com", None), Vec::new()),
75        RespondSso::post(),
76    )?;
77    let session = sp.finish_sso(
78        &idp_descriptor,
79        &sso.pending,
80        BrowserInput::<SsoResponse>::post(response.post_form()?.fields().to_vec()),
81        validation(),
82    )?;
83
84    let subject = session
85        .logout_subject()
86        .ok_or("session has no logout subject")?;
87    let logout = sp.start_slo(&idp_descriptor, subject, StartSlo::post())?;
88    println!("SP  -> LogoutRequest id = {}", logout.pending.id().as_str());
89
90    let logout_request = idp.receive_slo(
91        &sp_descriptor,
92        BrowserInput::<LogoutRequest>::post(logout.outbound.post_form()?.fields().to_vec()),
93        validation(),
94    )?;
95    let logout_response = idp.respond_slo(
96        &sp_descriptor,
97        &logout_request,
98        RespondSlo::post().relay_state(logout.pending.relay_state().clone()),
99    )?;
100    let completed = sp.finish_slo(
101        &idp_descriptor,
102        &logout.pending,
103        BrowserInput::<LogoutResponse>::post(logout_response.post_form()?.fields().to_vec()),
104        validation(),
105    )?;
106    println!(
107        "SP  <- LogoutResponse status = {}",
108        completed.status().unwrap_or("missing")
109    );
110    Ok(())
111}
112
113#[cfg(not(any(
114    feature = "crypto-rustcrypto",
115    feature = "crypto-aws-lc",
116    feature = "crypto-fips"
117)))]
118fn main() {
119    eprintln!("Enable a crypto provider feature (`crypto-bergshamra` is on by default).");
120}