opensaml 0.1.3

SAML 2.0 Service Provider library (bergshamra for XML crypto).
Documentation
//! Port of samlify `test/extractor.ts` (9 cases) onto `opensaml::xml::extract`.

use opensaml::util::Value;
use opensaml::xml::{extract, ExtractorField};

const RESPONSE: &str = include_str!("fixtures/misc/response_signed.xml");
const SPMETA: &str = include_str!("fixtures/misc/spmeta.xml");

#[test]
fn fetch_multiple_attributes() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        RESPONSE,
        &[ExtractorField::new("response", &["Response"]).attrs(&["ID", "Destination"])],
    )?;
    assert_eq!(
        r.get_str("response.id"),
        Some("_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6")
    );
    assert_eq!(
        r.get_str("response.destination"),
        Some("http://sp.example.com/demo1/index.php?acs")
    );
    Ok(())
}

#[test]
fn fetch_single_attributes() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        RESPONSE,
        &[
            ExtractorField::new("statusCode", &["Response", "Status", "StatusCode"])
                .attrs(&["Value"]),
        ],
    )?;
    assert_eq!(
        r.get_str("statusCode"),
        Some("urn:oasis:names:tc:SAML:2.0:status:Success")
    );
    Ok(())
}

#[test]
fn fetch_inner_context_of_leaf_node() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        RESPONSE,
        &[ExtractorField::new(
            "audience",
            &[
                "Response",
                "Assertion",
                "Conditions",
                "AudienceRestriction",
                "Audience",
            ],
        )],
    )?;
    assert_eq!(
        r.get_str("audience"),
        Some("https://sp.example.com/metadata")
    );
    Ok(())
}

#[test]
fn fetch_entire_context_of_non_existing_node() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        RESPONSE,
        &[ExtractorField::new(
            "assertionSignature",
            &["Response", "Assertion", "Signature"],
        )
        .with_context()],
    )?;
    assert!(r
        .get("assertionSignature")
        .map(Value::is_null)
        .unwrap_or(false));
    Ok(())
}

#[test]
fn fetch_entire_context_of_existing_node() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        RESPONSE,
        &[ExtractorField::new("messageSignature", &["Response", "Signature"]).with_context()],
    )?;
    assert!(!r
        .get("messageSignature")
        .map(Value::is_null)
        .unwrap_or(true));
    Ok(())
}

#[test]
fn fetch_unique_inner_context_of_multiple_nodes() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        RESPONSE,
        &[ExtractorField::multi(
            "issuer",
            &[
                &["Response", "Issuer"],
                &["Response", "Assertion", "Issuer"],
            ],
        )],
    )?;
    let issuer = r.get("issuer").and_then(Value::as_array).unwrap_or(&[]);
    assert_eq!(issuer.len(), 1);
    assert!(issuer
        .iter()
        .all(|i| i.as_str() == Some("https://idp.example.com/metadata")));
    Ok(())
}

#[test]
fn fetch_attribute_with_wildcard_local_path() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        SPMETA,
        &[ExtractorField::new(
            "certificate",
            &["EntityDescriptor", "~SSODescriptor", "KeyDescriptor"],
        )
        .aggregate(&["use"], &["KeyInfo", "X509Data", "X509Certificate"])],
    )?;
    assert!(r.get_str("certificate.signing").is_some());
    assert!(r.get_str("certificate.encryption").is_some());
    Ok(())
}

#[test]
fn fetch_attribute_with_non_wildcard_local_path() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        RESPONSE,
        &[ExtractorField::new(
            "attributes",
            &["Response", "Assertion", "AttributeStatement", "Attribute"],
        )
        .aggregate(&["Name"], &["AttributeValue"])],
    )?;
    assert_eq!(r.get_str("attributes.uid"), Some("test"));
    assert_eq!(r.get_str("attributes.mail"), Some("test@example.com"));
    assert_eq!(
        r.get("attributes.eduPersonAffiliation")
            .and_then(Value::as_array)
            .map(<[_]>::len),
        Some(2)
    );
    Ok(())
}

#[test]
fn fetch_one_attribute_as_key_another_as_value() -> Result<(), Box<dyn std::error::Error>> {
    let r = extract(
        SPMETA,
        &[ExtractorField::new(
            "singleSignOnService",
            &[
                "EntityDescriptor",
                "~SSODescriptor",
                "AssertionConsumerService",
            ],
        )
        .aggregate(&["Binding"], &[])
        .attrs(&["Location"])],
    )?;
    let sso = r.get("singleSignOnService").ok_or("missing sso")?;
    assert_eq!(
        sso.get_key("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
            .and_then(Value::as_str),
        Some("https://sp.example.org/sp/sso")
    );
    assert_eq!(
        sso.get_key("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact")
            .and_then(Value::as_str),
        Some("https://sp.example.org/sp/sso")
    );
    Ok(())
}