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