elif-orm 0.7.1

Production-ready ORM with migrations, database services, connection pooling, and query builder
Documentation
//! Comprehensive SQL Injection Prevention Test Suite
//!
//! This module tests all security measures to prevent SQL injection attacks
//! in the eager loading system and query builder.

#[cfg(test)]
mod tests {
    use super::super::security::*;

    #[test]
    fn test_identifier_escaping_prevents_injection() {
        // Test basic escaping
        assert_eq!(escape_identifier("users"), "\"users\"");

        // Test escaping of double quotes
        assert_eq!(escape_identifier("user\"table"), "\"user\"\"table\"");

        // Test escaping of malicious identifiers
        let malicious_table = "users\"; DROP TABLE secrets; --";
        let escaped = escape_identifier(malicious_table);
        assert_eq!(escaped, "\"users\"\"; DROP TABLE secrets; --\"");

        // Verify the escaped version is safe to use in SQL
        let sql = format!("SELECT * FROM {}", escaped);
        assert!(sql.contains("\"users\"\"; DROP TABLE secrets; --\""));
    }

    #[test]
    fn test_identifier_validation_blocks_dangerous_names() {
        // Valid identifiers should pass
        assert!(validate_identifier("users").is_ok());
        assert!(validate_identifier("user_profile").is_ok());
        assert!(validate_identifier("table1").is_ok());

        // Invalid characters should be rejected
        assert!(validate_identifier("user-profile").is_err());
        assert!(validate_identifier("user profile").is_err());
        assert!(validate_identifier("user;table").is_err());
        assert!(validate_identifier("user'table").is_err());
        assert!(validate_identifier("user\"table").is_err());
        assert!(validate_identifier("user\ntable").is_err());

        // SQL keywords should be rejected
        assert!(validate_identifier("SELECT").is_err());
        assert!(validate_identifier("DROP").is_err());
        assert!(validate_identifier("UNION").is_err());
        assert!(validate_identifier("EXEC").is_err());

        // Empty and too long identifiers
        assert!(validate_identifier("").is_err());
        let long_identifier = "a".repeat(64);
        assert!(validate_identifier(&long_identifier).is_err());

        // Starting with number
        assert!(validate_identifier("1table").is_err());
    }

    #[test]
    fn test_query_pattern_validation_detects_attacks() {
        // Safe queries should pass
        assert!(validate_query_pattern("SELECT * FROM users WHERE id = $1").is_ok());
        assert!(validate_query_pattern("INSERT INTO users (name) VALUES ($1)").is_ok());

        // Multiple statements should be rejected
        assert!(validate_query_pattern("SELECT * FROM users; DROP TABLE users").is_err());
        assert!(validate_query_pattern("SELECT * FROM users;DELETE FROM users").is_err());

        // Union-based injection should be rejected
        assert!(validate_query_pattern("SELECT * FROM users UNION SELECT * FROM secrets").is_err());
        assert!(
            validate_query_pattern("SELECT * FROM users UNION ALL SELECT password FROM admin")
                .is_err()
        );

        // Comment-based injection should be rejected
        assert!(validate_query_pattern("SELECT * FROM users WHERE id = 1'; --").is_err());
        assert!(validate_query_pattern("SELECT * FROM users WHERE id = 1'/*").is_err());

        // Stored procedure execution should be rejected
        assert!(validate_query_pattern("EXEC sp_executesql 'DROP TABLE users'").is_err());
        assert!(validate_query_pattern("EXECUTE xp_cmdshell 'dir'").is_err());

        // Information schema probing should be rejected
        assert!(validate_query_pattern("SELECT * FROM INFORMATION_SCHEMA.TABLES").is_err());
        assert!(validate_query_pattern("SELECT * FROM sys.tables").is_err());
    }

    #[test]
    fn test_parameter_validation_allows_safe_content() {
        // Normal parameters should be allowed
        assert!(validate_parameter("john_doe").is_ok());
        assert!(validate_parameter("user@example.com").is_ok());
        assert!(validate_parameter("123").is_ok());

        // Even SQL-like content should be OK since it's parameterized
        assert!(validate_parameter("'; DROP TABLE users; --").is_ok());
        assert!(validate_parameter("UNION SELECT password FROM admin").is_ok());
        assert!(validate_parameter("1' OR '1'='1").is_ok());

        // Very large parameters should be rejected (DoS protection)
        let large_param = "x".repeat(70000);
        assert!(validate_parameter(&large_param).is_err());
    }

    #[test]
    fn test_identifier_whitelist_strict_validation() {
        let whitelist = IdentifierWhitelist::new(vec!["users", "posts", "comments"]);

        // Allowed identifiers should pass
        assert!(whitelist.validate("users").is_ok());
        assert!(whitelist.validate("posts").is_ok());
        assert!(whitelist.validate("comments").is_ok());

        // Non-whitelisted identifiers should fail
        assert!(whitelist.validate("secrets").is_err());
        assert!(whitelist.validate("admin").is_err());
        assert!(whitelist.validate("'; DROP TABLE users; --").is_err());

        // Escape if allowed should work
        assert_eq!(whitelist.escape_if_allowed("users").unwrap(), "\"users\"");
        assert!(whitelist.escape_if_allowed("secrets").is_err());
    }

    #[test]
    fn test_count_query_injection_prevention() {
        // This test simulates what the build_secure_count_query method should do
        let malicious_relation = "users'; DROP TABLE secrets; --";

        // The relation name should be validated and rejected
        assert!(validate_identifier(malicious_relation).is_err());

        let safe_relation = "users";
        assert!(validate_identifier(safe_relation).is_ok());

        // Test that parameter values are handled safely
        let malicious_ids = vec![
            "1'; DROP TABLE users; --".to_string(),
            "1 UNION SELECT password FROM admin".to_string(),
        ];

        // Even with malicious content, parameters should be validated as OK
        // because they will be properly parameterized
        for id in &malicious_ids {
            assert!(validate_parameter(id).is_ok());
        }
    }

    #[test]
    fn test_complete_injection_attack_scenarios() {
        // Scenario 1: Table name injection
        let attack_table = "users; DROP TABLE secrets; --";
        assert!(validate_identifier(attack_table).is_err());

        // Scenario 2: Column name injection
        let attack_column = "id; DELETE FROM users; --";
        assert!(validate_identifier(attack_column).is_err());

        // Scenario 3: Union-based data exfiltration
        let union_attack = "SELECT id FROM users UNION SELECT password FROM admin";
        assert!(validate_query_pattern(union_attack).is_err());

        // Scenario 4: Boolean-based blind injection (in parameters - should be OK)
        let blind_attack = "1' AND (SELECT COUNT(*) FROM admin) > 0 --";
        assert!(validate_parameter(blind_attack).is_ok()); // Parameterized, so safe

        // Scenario 5: Time-based blind injection
        let time_attack = "1'; WAITFOR DELAY '00:00:10'; --";
        assert!(validate_query_pattern(time_attack).is_err());

        // Scenario 6: Stored procedure execution
        let proc_attack = "1'; EXEC xp_cmdshell 'net user hacker password /add'; --";
        assert!(validate_query_pattern(proc_attack).is_err());

        // Scenario 7: Information schema enumeration
        let schema_attack = "SELECT table_name FROM information_schema.tables";
        assert!(validate_query_pattern(schema_attack).is_err());
    }

    #[test]
    fn test_parameter_binding_security() {
        // Test that our parameter binding approach is secure
        let test_cases = vec![
            "normal_value",
            "'; DROP TABLE users; --",
            "1' OR '1'='1",
            "1 UNION SELECT password FROM admin",
            "1'; EXEC xp_cmdshell 'dir'; --",
            "1' AND (SELECT COUNT(*) FROM admin) > 0 --",
        ];

        for test_case in test_cases {
            // All parameter values should be acceptable for parameterized queries
            assert!(
                validate_parameter(test_case).is_ok(),
                "Parameter validation failed for: {}",
                test_case
            );
        }
    }

    #[test]
    fn test_edge_case_escaping() {
        // Test various edge cases in identifier escaping

        // Empty string
        let empty = "";
        assert!(validate_identifier(empty).is_err());

        // String with only quotes
        let quotes_only = "\"\"\"";
        let escaped = escape_identifier(quotes_only);
        assert_eq!(escaped, "\"\"\"\"\"\"\"\"");

        // Unicode characters (should be rejected by validation)
        let unicode = "table_名前";
        assert!(validate_identifier(unicode).is_err());

        // Control characters (should be rejected)
        let control = "table\x00name";
        assert!(validate_identifier(control).is_err());

        // Maximum length identifier (63 chars - PostgreSQL limit)
        let max_length = "a".repeat(63);
        assert!(validate_identifier(&max_length).is_ok());

        let too_long = "a".repeat(64);
        assert!(validate_identifier(&too_long).is_err());
    }
}