mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
@@ -6,16 +6,17 @@ Tests the fix for Issue #1210 - compound conditions with multiple AND/OR clauses
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
import os
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# Mock the logger module before importing SafeConditionBuilder
|
||||
sys.modules['logger'] = MagicMock()
|
||||
|
||||
# Add parent directory to path for imports
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from server.db.sql_safe_builder import SafeConditionBuilder
|
||||
from server.db.sql_safe_builder import SafeConditionBuilder # noqa: E402 [flake8 lint suppression]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -100,6 +101,7 @@ def test_multiple_or_clauses(builder):
|
||||
assert 'Device2' in param_values
|
||||
assert 'Device3' in param_values
|
||||
|
||||
|
||||
def test_mixed_and_or_clauses(builder):
|
||||
"""Test mixed AND/OR logical operators."""
|
||||
condition = "AND devName = 'Device1' OR devName = 'Device2' AND devFavorite = '1'"
|
||||
|
||||
@@ -137,7 +137,7 @@ def test_unicode_support(builder, unicode_str):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", [
|
||||
"", " ", "AND devName = ''", "AND devName = 'a'", "AND devName = '" + "x"*500 + "'"
|
||||
"", " ", "AND devName = ''", "AND devName = 'a'", "AND devName = '" + "x" * 500 + "'"
|
||||
])
|
||||
def test_edge_cases(builder, case):
|
||||
try:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
# !/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive SQL Injection Prevention Tests for NetAlertX
|
||||
|
||||
@@ -15,7 +15,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'server'))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'server', 'db'))
|
||||
|
||||
# Now import our module
|
||||
from sql_safe_builder import SafeConditionBuilder
|
||||
from sql_safe_builder import SafeConditionBuilder # noqa: E402 [flake8 lint suppression]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -28,7 +28,7 @@ def test_sql_injection_attempt_single_quote(builder):
|
||||
"""Test that single quote injection attempts are blocked."""
|
||||
malicious_input = "'; DROP TABLE users; --"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Should return empty condition when invalid
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -38,7 +38,7 @@ def test_sql_injection_attempt_union(builder):
|
||||
"""Test that UNION injection attempts are blocked."""
|
||||
malicious_input = "1' UNION SELECT * FROM passwords --"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Should return empty condition when invalid
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -48,7 +48,7 @@ def test_sql_injection_attempt_or_true(builder):
|
||||
"""Test that OR 1=1 injection attempts are blocked."""
|
||||
malicious_input = "' OR '1'='1"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Should return empty condition when invalid
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -58,7 +58,7 @@ def test_valid_simple_condition(builder):
|
||||
"""Test that valid simple conditions are handled correctly."""
|
||||
valid_input = "AND devName = 'Test Device'"
|
||||
condition, params = builder.get_safe_condition_legacy(valid_input)
|
||||
|
||||
|
||||
# Should create parameterized query
|
||||
assert "AND devName = :" in condition
|
||||
assert len(params) == 1
|
||||
@@ -69,7 +69,7 @@ def test_empty_condition(builder):
|
||||
"""Test that empty conditions are handled safely."""
|
||||
empty_input = ""
|
||||
condition, params = builder.get_safe_condition_legacy(empty_input)
|
||||
|
||||
|
||||
# Should return empty condition
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -79,7 +79,7 @@ def test_whitespace_only_condition(builder):
|
||||
"""Test that whitespace-only conditions are handled safely."""
|
||||
whitespace_input = " \n\t "
|
||||
condition, params = builder.get_safe_condition_legacy(whitespace_input)
|
||||
|
||||
|
||||
# Should return empty condition
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -90,7 +90,7 @@ def test_multiple_conditions_valid(builder):
|
||||
# Test with a single condition first (our current parser handles single conditions well)
|
||||
valid_input = "AND devName = 'Device1'"
|
||||
condition, params = builder.get_safe_condition_legacy(valid_input)
|
||||
|
||||
|
||||
# Should create parameterized query
|
||||
assert "devName = :" in condition
|
||||
assert len(params) == 1
|
||||
@@ -101,7 +101,7 @@ def test_disallowed_column_name(builder):
|
||||
"""Test that non-whitelisted column names are rejected."""
|
||||
invalid_input = "AND malicious_column = 'value'"
|
||||
condition, params = builder.get_safe_condition_legacy(invalid_input)
|
||||
|
||||
|
||||
# Should return empty condition when column not in whitelist
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -111,7 +111,7 @@ def test_disallowed_operator(builder):
|
||||
"""Test that non-whitelisted operators are rejected."""
|
||||
invalid_input = "AND devName SOUNDS LIKE 'test'"
|
||||
condition, params = builder.get_safe_condition_legacy(invalid_input)
|
||||
|
||||
|
||||
# Should return empty condition when operator not allowed
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -121,7 +121,7 @@ def test_nested_select_attempt(builder):
|
||||
"""Test that nested SELECT attempts are blocked."""
|
||||
malicious_input = "AND devName IN (SELECT password FROM users)"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Should return empty condition when nested SELECT detected
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -131,7 +131,7 @@ def test_hex_encoding_attempt(builder):
|
||||
"""Test that hex-encoded injection attempts are blocked."""
|
||||
malicious_input = "AND 0x44524f50205441424c45"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Should return empty condition when hex encoding detected
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -141,7 +141,7 @@ def test_comment_injection_attempt(builder):
|
||||
"""Test that comment injection attempts are handled."""
|
||||
malicious_input = "AND devName = 'test' /* comment */ --"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Comments should be stripped and condition validated
|
||||
if condition:
|
||||
assert "/*" not in condition
|
||||
@@ -152,7 +152,7 @@ def test_special_placeholder_replacement(builder):
|
||||
"""Test that {s-quote} placeholder is safely replaced."""
|
||||
input_with_placeholder = "AND devName = {s-quote}Test{s-quote}"
|
||||
condition, params = builder.get_safe_condition_legacy(input_with_placeholder)
|
||||
|
||||
|
||||
# Should handle placeholder safely
|
||||
if condition:
|
||||
assert "{s-quote}" not in condition
|
||||
@@ -163,7 +163,7 @@ def test_null_byte_injection(builder):
|
||||
"""Test that null byte injection attempts are blocked."""
|
||||
malicious_input = "AND devName = 'test\x00' DROP TABLE --"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Null bytes should be sanitized
|
||||
if condition:
|
||||
assert "\x00" not in condition
|
||||
@@ -178,7 +178,7 @@ def test_build_condition_with_allowed_values(builder):
|
||||
{"column": "devName", "operator": "LIKE", "value": "%test%"}
|
||||
]
|
||||
condition, params = builder.build_condition(conditions, "AND")
|
||||
|
||||
|
||||
# Should create valid parameterized condition
|
||||
assert "eve_EventType = :" in condition
|
||||
assert "devName LIKE :" in condition
|
||||
@@ -191,7 +191,7 @@ def test_build_condition_with_invalid_column(builder):
|
||||
{"column": "invalid_column", "operator": "=", "value": "test"}
|
||||
]
|
||||
condition, params = builder.build_condition(conditions)
|
||||
|
||||
|
||||
# Should return empty when invalid column
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -204,7 +204,7 @@ def test_case_variations_injection(builder):
|
||||
"oR 1=1",
|
||||
"UnIoN SeLeCt * FrOm users"
|
||||
]
|
||||
|
||||
|
||||
for malicious_input in malicious_inputs:
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
# Should handle case variations safely
|
||||
@@ -217,7 +217,7 @@ def test_time_based_injection_attempt(builder):
|
||||
"""Test that time-based injection attempts are blocked."""
|
||||
malicious_input = "AND IF(1=1, SLEEP(5), 0)"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Should return empty condition when SQL functions detected
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
@@ -227,7 +227,7 @@ def test_stacked_queries_attempt(builder):
|
||||
"""Test that stacked query attempts are blocked."""
|
||||
malicious_input = "'; INSERT INTO admin VALUES ('hacker', 'password'); --"
|
||||
condition, params = builder.get_safe_condition_legacy(malicious_input)
|
||||
|
||||
|
||||
# Should return empty condition when semicolon detected
|
||||
assert condition == ""
|
||||
assert params == {}
|
||||
|
||||
@@ -13,16 +13,15 @@ import unittest
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import os
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
# Add the server directory to the path for imports
|
||||
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
|
||||
sys.path.extend([f"{INSTALL_PATH}/server"])
|
||||
sys.path.append('/home/dell/coding/bash/10x-agentic-setup/netalertx-sql-fix/server')
|
||||
|
||||
from db.sql_safe_builder import SafeConditionBuilder, create_safe_condition_builder
|
||||
from database import DB
|
||||
from messaging.reporting import get_notifications
|
||||
from db.sql_safe_builder import SafeConditionBuilder # noqa: E402 [flake8 lint suppression]
|
||||
from messaging.reporting import get_notifications # noqa: E402 [flake8 lint suppression]
|
||||
|
||||
|
||||
class TestSafeConditionBuilder(unittest.TestCase):
|
||||
@@ -83,7 +82,7 @@ class TestSafeConditionBuilder(unittest.TestCase):
|
||||
def test_build_simple_condition_valid(self):
|
||||
"""Test building valid simple conditions."""
|
||||
sql, params = self.builder._build_simple_condition('AND', 'devName', '=', 'TestDevice')
|
||||
|
||||
|
||||
self.assertIn('AND devName = :param_', sql)
|
||||
self.assertEqual(len(params), 1)
|
||||
self.assertIn('TestDevice', params.values())
|
||||
@@ -92,20 +91,20 @@ class TestSafeConditionBuilder(unittest.TestCase):
|
||||
"""Test that invalid column names are rejected."""
|
||||
with self.assertRaises(ValueError) as context:
|
||||
self.builder._build_simple_condition('AND', 'invalid_column', '=', 'value')
|
||||
|
||||
|
||||
self.assertIn('Invalid column name', str(context.exception))
|
||||
|
||||
def test_build_simple_condition_invalid_operator(self):
|
||||
"""Test that invalid operators are rejected."""
|
||||
with self.assertRaises(ValueError) as context:
|
||||
self.builder._build_simple_condition('AND', 'devName', 'UNION', 'value')
|
||||
|
||||
|
||||
self.assertIn('Invalid operator', str(context.exception))
|
||||
|
||||
def test_build_in_condition_valid(self):
|
||||
"""Test building valid IN conditions."""
|
||||
sql, params = self.builder._build_in_condition('AND', 'eve_EventType', 'IN', "'Connected', 'Disconnected'")
|
||||
|
||||
|
||||
self.assertIn('AND eve_EventType IN', sql)
|
||||
self.assertEqual(len(params), 2)
|
||||
self.assertIn('Connected', params.values())
|
||||
@@ -114,7 +113,7 @@ class TestSafeConditionBuilder(unittest.TestCase):
|
||||
def test_build_null_condition(self):
|
||||
"""Test building NULL check conditions."""
|
||||
sql, params = self.builder._build_null_condition('AND', 'devComments', 'IS NULL')
|
||||
|
||||
|
||||
self.assertEqual(sql, 'AND devComments IS NULL')
|
||||
self.assertEqual(len(params), 0)
|
||||
|
||||
@@ -154,7 +153,7 @@ class TestSafeConditionBuilder(unittest.TestCase):
|
||||
def test_device_name_filter(self):
|
||||
"""Test the device name filter helper method."""
|
||||
sql, params = self.builder.build_device_name_filter("TestDevice")
|
||||
|
||||
|
||||
self.assertIn('AND devName = :device_name_', sql)
|
||||
self.assertIn('TestDevice', params.values())
|
||||
|
||||
@@ -162,14 +161,13 @@ class TestSafeConditionBuilder(unittest.TestCase):
|
||||
"""Test the event type filter helper method."""
|
||||
event_types = ['Connected', 'Disconnected']
|
||||
sql, params = self.builder.build_event_type_filter(event_types)
|
||||
|
||||
|
||||
self.assertIn('AND eve_EventType IN', sql)
|
||||
self.assertEqual(len(params), 2)
|
||||
self.assertIn('Connected', params.values())
|
||||
self.assertIn('Disconnected', params.values())
|
||||
|
||||
|
||||
|
||||
class TestDatabaseParameterSupport(unittest.TestCase):
|
||||
"""Test that database layer supports parameterized queries."""
|
||||
|
||||
@@ -177,7 +175,7 @@ class TestDatabaseParameterSupport(unittest.TestCase):
|
||||
"""Set up test database."""
|
||||
self.temp_db = tempfile.NamedTemporaryFile(delete=False, suffix='.db')
|
||||
self.temp_db.close()
|
||||
|
||||
|
||||
# Create test database
|
||||
self.conn = sqlite3.connect(self.temp_db.name)
|
||||
self.conn.execute('''CREATE TABLE test_table (
|
||||
@@ -197,23 +195,23 @@ class TestDatabaseParameterSupport(unittest.TestCase):
|
||||
def test_parameterized_query_execution(self):
|
||||
"""Test that parameterized queries work correctly."""
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
|
||||
# Test named parameters
|
||||
cursor.execute("SELECT * FROM test_table WHERE name = :name", {'name': 'test1'})
|
||||
results = cursor.fetchall()
|
||||
|
||||
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0][1], 'test1')
|
||||
|
||||
def test_parameterized_query_prevents_injection(self):
|
||||
"""Test that parameterized queries prevent SQL injection."""
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
|
||||
# This should not cause SQL injection
|
||||
malicious_input = "'; DROP TABLE test_table; --"
|
||||
cursor.execute("SELECT * FROM test_table WHERE name = :name", {'name': malicious_input})
|
||||
results = cursor.fetchall()
|
||||
|
||||
# results = cursor.fetchall()
|
||||
|
||||
# The table should still exist and be queryable
|
||||
cursor.execute("SELECT COUNT(*) FROM test_table")
|
||||
count = cursor.fetchone()[0]
|
||||
@@ -228,7 +226,7 @@ class TestReportingSecurityIntegration(unittest.TestCase):
|
||||
self.mock_db = Mock()
|
||||
self.mock_db.sql = Mock()
|
||||
self.mock_db.get_table_as_json = Mock()
|
||||
|
||||
|
||||
# Mock successful JSON response
|
||||
mock_json_obj = Mock()
|
||||
mock_json_obj.columnNames = ['MAC', 'Datetime', 'IP', 'Event Type', 'Device name', 'Comments']
|
||||
@@ -245,7 +243,7 @@ class TestReportingSecurityIntegration(unittest.TestCase):
|
||||
}.get(key, '')
|
||||
|
||||
# Call the function
|
||||
result = get_notifications(self.mock_db)
|
||||
get_notifications(self.mock_db)
|
||||
|
||||
# Verify that get_table_as_json was called with parameters
|
||||
self.mock_db.get_table_as_json.assert_called()
|
||||
@@ -265,7 +263,6 @@ class TestReportingSecurityIntegration(unittest.TestCase):
|
||||
# Ensure the parameter dict has the correct value (using actual param name)
|
||||
self.assertEqual(list(params.values())[0], "TestDevice")
|
||||
|
||||
|
||||
@patch('messaging.reporting.get_setting_value')
|
||||
def test_events_section_security(self, mock_get_setting):
|
||||
"""Test that events section uses safe SQL building."""
|
||||
@@ -276,7 +273,7 @@ class TestReportingSecurityIntegration(unittest.TestCase):
|
||||
}.get(key, '')
|
||||
|
||||
# Call the function
|
||||
result = get_notifications(self.mock_db)
|
||||
get_notifications(self.mock_db)
|
||||
|
||||
# Verify that get_table_as_json was called with parameters
|
||||
self.mock_db.get_table_as_json.assert_called()
|
||||
@@ -291,7 +288,7 @@ class TestReportingSecurityIntegration(unittest.TestCase):
|
||||
}.get(key, '')
|
||||
|
||||
# Call the function - should not raise an exception
|
||||
result = get_notifications(self.mock_db)
|
||||
get_notifications(self.mock_db)
|
||||
|
||||
# Should still call get_table_as_json (with safe fallback query)
|
||||
self.mock_db.get_table_as_json.assert_called()
|
||||
@@ -306,7 +303,7 @@ class TestReportingSecurityIntegration(unittest.TestCase):
|
||||
}.get(key, '')
|
||||
|
||||
# Call the function
|
||||
result = get_notifications(self.mock_db)
|
||||
get_notifications(self.mock_db)
|
||||
|
||||
# Should call get_table_as_json
|
||||
self.mock_db.get_table_as_json.assert_called()
|
||||
@@ -322,12 +319,12 @@ class TestSecurityBenchmarks(unittest.TestCase):
|
||||
def test_performance_simple_condition(self):
|
||||
"""Test performance of simple condition building."""
|
||||
import time
|
||||
|
||||
|
||||
start_time = time.time()
|
||||
for _ in range(1000):
|
||||
sql, params = self.builder.build_safe_condition("AND devName = 'TestDevice'")
|
||||
end_time = time.time()
|
||||
|
||||
|
||||
execution_time = end_time - start_time
|
||||
self.assertLess(execution_time, 1.0, "Simple condition building should be fast")
|
||||
|
||||
@@ -339,7 +336,7 @@ class TestSecurityBenchmarks(unittest.TestCase):
|
||||
self.skipTest("psutil not available")
|
||||
return
|
||||
import os
|
||||
|
||||
|
||||
process = psutil.Process(os.getpid())
|
||||
initial_memory = process.memory_info().rss
|
||||
|
||||
@@ -350,7 +347,7 @@ class TestSecurityBenchmarks(unittest.TestCase):
|
||||
|
||||
final_memory = process.memory_info().rss
|
||||
memory_increase = final_memory - initial_memory
|
||||
|
||||
|
||||
# Memory increase should be reasonable (less than 10MB)
|
||||
self.assertLess(memory_increase, 10 * 1024 * 1024, "Memory usage should be reasonable")
|
||||
|
||||
@@ -376,4 +373,4 @@ class TestSecurityBenchmarks(unittest.TestCase):
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run the test suite
|
||||
unittest.main(verbosity=2)
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
Reference in New Issue
Block a user