perimeterx-fastly-enforcer 2.2.2

PerimeterX Fastly Compute@Edge Rust Enforcer
Documentation
use crate::handlers::pxcredentials_intelligence::*;
use crate::pxconfig::{PXConfig, PXCredentialEndpointConfig, PXPreparedCredentialEndpoint, PXRawCredentials};
use crate::pxcontext::PXContext;

#[test]
fn raw_credentials_empty_strings_become_none() {
    let raw = PXRawCredentials {
        user: Some(String::new()),
        pass: Some(String::new()),
    }
    .without_empty_fields();
    assert!(raw.user.is_none());
    assert!(raw.pass.is_none());
}

#[test]
fn normalize_email_gmail_plus_and_dots() {
    assert_eq!(
        normalize_username("Perimeter.X+001@gmail.com"),
        "perimeterx@gmail.com"
    );
}

#[test]
fn hash_v2_gmail_user_credentials() {
    let raw = PXRawCredentials {
        user: Some("Perimeter.X+001@gmail.com".to_owned()),
        pass: Some("1234".to_owned()),
    };
    let Some(hashed) = hash_v2(&raw) else {
        return;
    };
    assert_eq!(
        hashed.user.as_deref(),
        Some("2bd9bd06f3440c682044a3f1b1fa7a97bd8b568a6e9e7d2cb0c6e858d9c78069")
    );
    assert_eq!(
        hashed.pass.as_deref(),
        Some("5246d99e5d2506d70db44e8216aecb7be42bf5bf7bc1766a680cbdad2ce046ab")
    );
}

#[test]
fn normalize_non_email_unchanged() {
    assert_eq!(normalize_username("pxUser"), "pxUser");
}

#[test]
fn hash_v2_regular_credentials() {
    let raw = PXRawCredentials {
        user: Some("pxUser".to_owned()),
        pass: Some("1234".to_owned()),
    };
    let hashed = match hash_v2(&raw) {
        Some(h) => h,
        None => return,
    };
    assert_eq!(hashed.ci_version, CI_VERSION_V2);
    assert_eq!(
        hashed.user.as_deref(),
        Some("9620f4cab3b3a50b9cbcb9a8d01328874ec33eb6882ae31c022f6986fc516851")
    );
    assert_eq!(
        hashed.pass.as_deref(),
        Some("c958c33151f273c620ec658ac4de9abd33ad7627df5d8c468224b0bae7173eb4")
    );
}

#[test]
fn parse_urlencoded_decodes_percent_encoding() {
    let map = parse_urlencoded("username=User%2eName&password=p%40ss");
    assert_eq!(map.get("username").map(String::as_str), Some("User.Name"));
    assert_eq!(map.get("password").map(String::as_str), Some("p@ss"));
}

#[test]
fn parse_multipart_boundary_handles_quoted_boundary() {
    let content_type = r#"multipart/form-data; boundary="----WebKitFormBoundaryPxTest""#;
    assert_eq!(
        parse_multipart_boundary(content_type).as_deref(),
        Some("----WebKitFormBoundaryPxTest")
    );
}

#[test]
fn parse_multipart_boundary_handles_unquoted_boundary() {
    let content_type = "multipart/form-data; boundary=----WebKitFormBoundaryPxTest";
    assert_eq!(
        parse_multipart_boundary(content_type).as_deref(),
        Some("----WebKitFormBoundaryPxTest")
    );
}

#[test]
fn multipart_extraction_works_with_quoted_boundary_header_value() {
    let content_type = r#"multipart/form-data; boundary="----WebKitFormBoundaryPxTest""#;
    let Some(boundary) = parse_multipart_boundary(content_type) else {
        return;
    };
    let body = concat!(
        "------WebKitFormBoundaryPxTest\r\n",
        "Content-Disposition: form-data; name=\"username\"\r\n",
        "\r\n",
        "pxUser\r\n",
        "------WebKitFormBoundaryPxTest\r\n",
        "Content-Disposition: form-data; name=\"password\"\r\n",
        "\r\n",
        "1234\r\n",
        "------WebKitFormBoundaryPxTest--\r\n"
    );
    let config = PXCredentialEndpointConfig {
        user_field: "username".to_owned(),
        pass_field: "password".to_owned(),
        ..Default::default()
    };

    let Some(raw) = extract_from_multipart(body, boundary, &config) else {
        return;
    };
    assert_eq!(raw.user.as_deref(), Some("pxUser"));
    assert_eq!(raw.pass.as_deref(), Some("1234"));
}

#[test]
fn hash_credentials_v1_is_unsupported() {
    let raw = PXRawCredentials {
        user: Some("u".to_owned()),
        pass: Some("p".to_owned()),
    };
    assert!(hash_credentials(CI_VERSION_V1, &raw).is_none());
}

#[test]
fn hash_credentials_both_uses_multistep_for_partial() {
    let raw = PXRawCredentials {
        user: Some("pxUser".to_owned()),
        pass: None,
    };
    let Some(hashed) = hash_credentials(CI_VERSION_BOTH, &raw) else {
        return;
    };
    assert_eq!(hashed.ci_version, CI_VERSION_MULTISTEP);
    assert_eq!(hashed.sso_step.as_deref(), Some(SSO_STEP_USER));
}

#[test]
fn endpoint_exact_match_is_case_insensitive_method() {
    let endpoint = PXPreparedCredentialEndpoint {
        config: PXCredentialEndpointConfig {
            path: "/login".to_owned(),
            method: "post".to_owned(),
            ..Default::default()
        },
        path_regex: None,
    };
    assert!(endpoint_matches(&endpoint, "/login", "POST"));
    assert!(!endpoint_matches(&endpoint, "/login", "GET"));
}

#[test]
fn additional_s2s_base_uses_expected_schema_shape() {
    let conf = PXConfig {
        app_id: "PX123".to_owned(),
        ..Default::default()
    };
    let ctx = PXContext {
        credential_intelligence: Some(PXCredentialIntelligenceData {
            ci_version: CI_VERSION_V2.to_owned(),
            raw_username: Some("pxUser".to_owned()),
            ..Default::default()
        }),
        uuid: Some("client-uuid".to_owned()),
        full_url: "https://example.com/login".to_owned(),
        ip: "1.1.1.1".to_owned(),
        ..Default::default()
    };

    let activity = build_additional_s2s_activity_base(&conf, &ctx);
    assert_eq!(
        activity.get("type").and_then(serde_json::Value::as_str),
        Some("additional_s2s")
    );
    assert!(activity.get("headers").is_none());
    let details = activity
        .get("details")
        .and_then(serde_json::Value::as_object);
    assert_eq!(
        details
            .and_then(|d| d.get("client_uuid"))
            .and_then(serde_json::Value::as_str),
        Some("client-uuid")
    );
    assert_eq!(
        details
            .and_then(|d| d.get("ci_version"))
            .and_then(serde_json::Value::as_str),
        Some(CI_VERSION_V2)
    );
}