cedarling 0.0.49

The Cedarling: a high-performance local authorization service powered by the Rust Cedar Engine.
Documentation
// This software is available under the Apache-2.0 license.
// See https://www.apache.org/licenses/LICENSE-2.0.txt for full text.
//
// Copyright (c) 2024, Gluu, Inc.

//! Integration tests for SSA JWT validation functionality

use crate::log::StdOutLoggerMode;
use crate::{
    AuthorizationConfig, BootstrapConfig, Cedarling, DataStoreConfig, JwtConfig, LockServiceConfig,
    LockTransport, LogConfig, LogLevel, LogTypeConfig, PolicyStoreConfig, PolicyStoreSource,
};
use serde_json::json;
use std::collections::HashSet;
use std::time::Duration;

static POLICY_STORE_RAW: &str = include_str!("../../../test_files/policy-store_ok.yaml");

// Valid SSA JWT for testing
const VALID_SSA_JWT: &str = "eyJraWQiOiJzc2FfMmRmMGNkZDUtNTU2Yi00ZDRlLTkzNjItNjc2Mjk1NjEzMzMxX3NpZ19yczI1NiIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzb2Z0d2FyZV9pZCI6ImNlZGFybGluZyIsImdyYW50X3R5cGVzIjpbImNsaWVudF9jcmVkZW50aWFscyJdLCJvcmdfaWQiOiJteV9vcmciLCJpc3MiOiJodHRwczovL2RlbW9leGFtcGxlLmphbnMuaW8iLCJsaWZldGltZSI6MTU3Njc5OTk5OSwic29mdHdhcmVfcm9sZXMiOlsiaHR0cHM6Ly9qYW5zLmlvL29hdXRoL2xvY2svbG9nLndyaXRlIiwiaHR0cHM6Ly9qYW5zLmlvL29hdXRoL2xvY2svaGVhbHRoLndyaXRlIiwiaHR0cHM6Ly9qYW5zLmlvL29hdXRoL2xvY2svdGVsZW1ldHJ5LndyaXRlIl0sImV4cCI6MzMyMzA4NjI3NSwiaWF0IjoxNzQ2Mjg2Mjc2LCJqdGkiOiJkM2EwODI1Yi1kZjFhLTQ3ZTYtYmQ5MC0yMTk2NTkyYTVlNGQifQ.KxMQyxDZ3zsZDHj5OjZdAWi3J8fuYdxMbl2NC3fzS0e308Zd_t8CvtFX0F3edYAwvy3mdnva_MxKkxgSsXGniv2UfiFj7p8gKaqybYB4ngb1mX1BZCJZ27M0K5g9H3pa4g3csKp_UHjmV2LBHePkr3RA343E9ezDtR-4WwxQwJ6Lq_dmdXvtMW5iQLE9SsDT0f6rPNs2jt1mx-_PjT3mpOo2NG7mMB1TPX_runu53PqnJ844QoZNa1yjjIhJUGxLzE-DH8t6pjwiatnd1kDS7jDhfAn41l-t29IraIpYKTmPEGNxKd6EkIr-j7Si54HPIJZoZXY8E-UEnLHwDNo7hQ";

#[tokio::test]
async fn test_cedarling_with_valid_ssa() {
    // This test verifies that Cedarling can start successfully with a valid SSA JWT
    let lock_config = LockServiceConfig {
        log_level: LogLevel::TRACE,
        config_uri: "https://demoexample.jans.io/.well-known/lock-server-configuration"
            .parse()
            .unwrap(),
        dynamic_config: false,
        ssa_jwt: Some(VALID_SSA_JWT.to_string()),
        log_interval: Some(Duration::from_secs(3)),
        health_interval: None,
        telemetry_interval: None,
        listen_sse: false,
        accept_invalid_certs: true,
        transport: LockTransport::Rest,
        ..Default::default()
    };

    let result = Cedarling::new(&BootstrapConfig {
        application_name: "test_app".to_string(),
        log_config: LogConfig {
            log_type: LogTypeConfig::StdOut(StdOutLoggerMode::Immediate),
            log_level: LogLevel::INFO,
        },
        policy_store_config: PolicyStoreConfig {
            source: PolicyStoreSource::Yaml(POLICY_STORE_RAW.to_string()),
            ..Default::default()
        },
        jwt_config: JwtConfig {
            jwks: None,
            jwt_sig_validation: false,
            jwt_status_validation: false,
            signature_algorithms_supported: HashSet::new(),
            ..Default::default()
        }
        .allow_all_algorithms(),
        authorization_config: AuthorizationConfig {
            decision_log_default_jwt_id: "jti".to_string(),
            strict_schema_validation: true,
        },
        lock_config: Some(lock_config),
        max_default_entities: None,
        max_base64_size: None,
        data_store_config: DataStoreConfig::default(),
        http_client_config: crate::HttpClientConfig::default(),
    })
    .await;

    // Note: This test will fail in a real environment because it can't connect to the actual lock server
    // In a real integration test, you would need to mock the lock server endpoints
    // For now, we just verify that the configuration is accepted
    assert!(result.is_ok() || result.is_err()); // Either success or network error is acceptable
}

#[tokio::test]
async fn test_cedarling_without_ssa() {
    // This test verifies that Cedarling can start successfully without an SSA JWT
    let lock_config = LockServiceConfig {
        log_level: LogLevel::TRACE,
        config_uri: "https://demoexample.jans.io/.well-known/lock-server-configuration"
            .parse()
            .unwrap(),
        dynamic_config: false,
        ssa_jwt: None,
        log_interval: Some(Duration::from_secs(3)),
        health_interval: None,
        telemetry_interval: None,
        listen_sse: false,
        accept_invalid_certs: true,
        transport: LockTransport::Rest,
        ..Default::default()
    };

    let result = Cedarling::new(&BootstrapConfig {
        application_name: "test_app".to_string(),
        log_config: LogConfig {
            log_type: LogTypeConfig::StdOut(StdOutLoggerMode::Immediate),
            log_level: LogLevel::INFO,
        },
        policy_store_config: PolicyStoreConfig {
            source: PolicyStoreSource::Yaml(POLICY_STORE_RAW.to_string()),
            ..Default::default()
        },
        jwt_config: JwtConfig {
            jwks: None,
            jwt_sig_validation: false,
            jwt_status_validation: false,
            signature_algorithms_supported: HashSet::new(),
            ..Default::default()
        }
        .allow_all_algorithms(),
        authorization_config: AuthorizationConfig {
            decision_log_default_jwt_id: "jti".to_string(),
            strict_schema_validation: true,
        },
        lock_config: Some(lock_config),
        max_default_entities: None,
        max_base64_size: None,
        data_store_config: DataStoreConfig::default(),
        http_client_config: crate::HttpClientConfig::default(),
    })
    .await;

    // Note: This test will fail in a real environment because it can't connect to the actual lock server
    // In a real integration test, you would need to mock the lock server endpoints
    // For now, we just verify that the configuration is accepted
    assert!(result.is_ok() || result.is_err()); // Either success or network error is acceptable
}

#[tokio::test]
async fn test_ssa_validation_structure() {
    // Test SSA JWT structure validation
    use crate::jwt::{DecodedJwt, DecodedJwtClaims, DecodedJwtHeader};
    use crate::lock::ssa_validation::{
        SsaValidationConfig, SsaValidationError, validate_ssa_structure_with_config,
    };
    use jsonwebtoken::Algorithm;

    // Test valid SSA structure
    let valid_claims = json!({
        "software_id": "test_software",
        "grant_types": ["client_credentials"],
        "org_id": "test_org",
        "iss": "https://test.issuer.com",
        "software_roles": ["cedarling"],
        "exp": 1_735_689_600,
        "iat": 1_735_603_200,
        "jti": "test-jti-123"
    });

    let valid_decoded_jwt = DecodedJwt {
        header: DecodedJwtHeader {
            typ: Some("JWT".to_string()),
            alg: Algorithm::RS256,
            cty: None,
            kid: Some("test-kid".to_string()),
        },
        claims: DecodedJwtClaims {
            inner: valid_claims,
        },
    };

    let config = SsaValidationConfig::default();
    let result = validate_ssa_structure_with_config(&valid_decoded_jwt, &config);
    assert!(result.is_ok());

    // Test missing required claims
    let invalid_claims = json!({
        "software_id": "test_software",
        "grant_types": ["client_credentials"],
        // Missing org_id, iss, software_roles, exp, iat, jti
    });

    let invalid_decoded_jwt = DecodedJwt {
        header: DecodedJwtHeader {
            typ: Some("JWT".to_string()),
            alg: Algorithm::RS256,
            cty: None,
            kid: Some("test-kid".to_string()),
        },
        claims: DecodedJwtClaims {
            inner: invalid_claims,
        },
    };

    let result = validate_ssa_structure_with_config(&invalid_decoded_jwt, &config);
    assert!(matches!(
        result,
        Err(SsaValidationError::MissingRequiredClaims(_))
    ));

    // Test invalid grant_types (not an array)
    let invalid_grant_types_claims = json!({
        "software_id": "test_software",
        "grant_types": "client_credentials", // Should be array
        "org_id": "test_org",
        "iss": "https://test.issuer.com",
        "software_roles": ["cedarling"],
        "exp": 1_735_689_600,
        "iat": 1_735_603_200,
        "jti": "test-jti-123"
    });

    let invalid_grant_types_jwt = DecodedJwt {
        header: DecodedJwtHeader {
            typ: Some("JWT".to_string()),
            alg: Algorithm::RS256,
            cty: None,
            kid: Some("test-kid".to_string()),
        },
        claims: DecodedJwtClaims {
            inner: invalid_grant_types_claims,
        },
    };

    let result = validate_ssa_structure_with_config(&invalid_grant_types_jwt, &config);
    assert!(matches!(result, Err(SsaValidationError::InvalidGrantTypes)));
}

#[tokio::test]
async fn test_ssa_jwt_decode() {
    // Test SSA JWT decoding
    use crate::jwt::decode_jwt;

    // Test decoding a valid JWT structure
    let valid_jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";

    let result = decode_jwt(valid_jwt);
    assert!(result.is_ok());

    // Test decoding an invalid JWT structure
    let invalid_jwt = "invalid.jwt.token";

    let result = decode_jwt(invalid_jwt);
    assert!(result.is_err());
}

#[tokio::test]
async fn test_ssa_configuration_validation() {
    // Test that the SSA configuration is properly validated
    let config = LockServiceConfig {
        log_level: LogLevel::TRACE,
        config_uri: "https://demoexample.jans.io/.well-known/lock-server-configuration"
            .parse()
            .unwrap(),
        dynamic_config: false,
        ssa_jwt: Some(VALID_SSA_JWT.to_string()),
        log_interval: Some(Duration::from_secs(3)),
        health_interval: None,
        telemetry_interval: None,
        listen_sse: false,
        accept_invalid_certs: true,
        transport: LockTransport::Rest,
        ..Default::default()
    };

    // Verify that the SSA JWT is properly set
    assert!(config.ssa_jwt.is_some());
    assert_eq!(config.ssa_jwt.as_ref().unwrap(), VALID_SSA_JWT);

    // Test configuration without SSA
    let config_without_ssa = LockServiceConfig {
        log_level: LogLevel::TRACE,
        config_uri: "https://demoexample.jans.io/.well-known/lock-server-configuration"
            .parse()
            .unwrap(),
        dynamic_config: false,
        ssa_jwt: None,
        log_interval: Some(Duration::from_secs(3)),
        health_interval: None,
        telemetry_interval: None,
        listen_sse: false,
        accept_invalid_certs: true,
        transport: LockTransport::Rest,
        ..Default::default()
    };

    // Verify that the SSA JWT is not set
    assert!(config_without_ssa.ssa_jwt.is_none());
}