rustauth-saml 0.3.0

SAML 2.0 service-provider support for RustAuth.
Documentation
use opensaml::constants::Binding;
use opensaml::metadata::{generate_sp_metadata, Endpoint, IdpMetadata, SpMetadataConfig};

use crate::options::SamlConfig;
use crate::saml_impl::authn_request::assertion_consumer_service_url;
use crate::saml_impl::xml::validate_saml_xml;
use rustauth_core::error::RustAuthError;

pub fn service_provider_metadata(
    provider_id: &str,
    base_url: &str,
    config: &SamlConfig,
    single_logout_enabled: bool,
) -> String {
    if let Some(metadata) = config
        .sp_metadata
        .metadata
        .as_deref()
        .filter(|metadata| !metadata.trim().is_empty())
    {
        return metadata.to_owned();
    }

    let entity_id = config
        .sp_metadata
        .entity_id
        .as_deref()
        .unwrap_or(config.issuer.as_str())
        .to_owned();
    let acs = assertion_consumer_service_url(provider_id, base_url, config);
    let mut name_id_format = Vec::new();
    if let Some(format) = &config.identifier_format {
        name_id_format.push(format.clone());
    }
    let mut single_logout_service = Vec::new();
    if single_logout_enabled {
        let slo_url = format!(
            "{}/sso/saml2/sp/slo/{}",
            base_url.trim_end_matches('/'),
            provider_id
        );
        single_logout_service.push(Endpoint::new(Binding::Post, slo_url.clone()));
        single_logout_service.push(Endpoint::new(Binding::Redirect, slo_url));
    }

    generate_sp_metadata(&SpMetadataConfig {
        entity_id,
        signing_certs: if config.cert.is_empty() {
            Vec::new()
        } else {
            vec![config.cert.clone()]
        },
        encrypt_certs: Vec::new(),
        authn_requests_signed: config.authn_requests_signed,
        want_assertions_signed: config.want_assertions_signed,
        name_id_format,
        single_logout_service,
        assertion_consumer_service: vec![Endpoint::new(Binding::Post, acs)],
        elements_order: None,
    })
}

pub fn first_single_sign_on_service_location(xml: &str) -> Result<Option<String>, RustAuthError> {
    validate_saml_xml(xml)?;
    Ok(IdpMetadata::from_xml(xml)
        .ok()
        .and_then(|metadata| metadata.get_single_sign_on_service(Binding::Redirect))
        .or_else(|| {
            IdpMetadata::from_xml(xml)
                .ok()
                .and_then(|metadata| metadata.get_single_sign_on_service(Binding::Post))
        }))
}

pub fn first_single_logout_service_location(xml: &str) -> Result<Option<String>, RustAuthError> {
    validate_saml_xml(xml)?;
    Ok(IdpMetadata::from_xml(xml)
        .ok()
        .and_then(|metadata| metadata.get_single_logout_service(Binding::Redirect))
        .or_else(|| {
            IdpMetadata::from_xml(xml)
                .ok()
                .and_then(|metadata| metadata.get_single_logout_service(Binding::Post))
        }))
}