- 新增 crypto.go: AES-256-GCM 加解密,与 Python backend 兼容 - auth.go: Login 添加 LDAP fallback(本地优先→LDAP绑定→自动创建本地用户) - ldap.go: 实现真实 LDAP 连接/用户搜索/密码验证/OU列表/用户列表 - 所有 LDAP 操作解密 admin_password 后使用 - 新增/更新密码存储时加密 - router.go: 补充 delete-user 路由 - 添加 go-ldap/v3 依赖
281 lines
11 KiB
Python
281 lines
11 KiB
Python
"""风险等级评估 + 维护窗口 + 通知脱敏 + SQL items 测试。"""
|
|
|
|
import json
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
# ===== 风险等级评估测试 =====
|
|
|
|
class TestRiskEvaluator:
|
|
"""风险等级自动计算测试。"""
|
|
|
|
def test_select_is_low(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("SELECT * FROM users WHERE id = 1") == "low"
|
|
|
|
def test_show_is_low(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("SHOW TABLES") == "low"
|
|
|
|
def test_explain_is_low(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("EXPLAIN SELECT * FROM users") == "low"
|
|
|
|
def test_create_table_is_medium(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("CREATE TABLE t1 (id INT)") == "medium"
|
|
|
|
def test_alter_table_is_medium(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("ALTER TABLE users ADD COLUMN age INT") == "medium"
|
|
|
|
def test_insert_with_where_is_medium(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level(
|
|
"INSERT INTO users (name) VALUES ('test')",
|
|
sql_type="DML",
|
|
) == "medium"
|
|
|
|
def test_update_without_where_is_high(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("UPDATE users SET status = 1") == "high"
|
|
|
|
def test_delete_without_where_is_high(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("DELETE FROM users") == "high"
|
|
|
|
def test_alter_drop_column_is_high(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("ALTER TABLE users DROP COLUMN age") == "high"
|
|
|
|
def test_alter_modify_column_is_high(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("ALTER TABLE users MODIFY COLUMN age BIGINT") == "high"
|
|
|
|
def test_drop_table_is_critical(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("DROP TABLE users") == "critical"
|
|
|
|
def test_truncate_is_critical(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("TRUNCATE TABLE users") == "critical"
|
|
|
|
def test_drop_database_is_critical(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("DROP DATABASE mydb") == "critical"
|
|
|
|
def test_many_statements_is_high(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level(
|
|
"SELECT 1", statement_count=10
|
|
) == "high"
|
|
|
|
def test_multiple_audit_errors_is_high(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
findings = [
|
|
{"level": "error", "rule_id": "R1"},
|
|
{"level": "error", "rule_id": "R2"},
|
|
]
|
|
assert evaluate_risk_level(
|
|
"UPDATE users SET x=1 WHERE id=1",
|
|
sql_type="DML",
|
|
audit_findings=findings,
|
|
) == "high"
|
|
|
|
def test_delete_with_where_no_limit_is_medium(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level(
|
|
"DELETE FROM users WHERE id > 100"
|
|
) == "medium"
|
|
|
|
def test_delete_with_where_and_limit_is_low(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level(
|
|
"DELETE FROM users WHERE id > 100 LIMIT 10"
|
|
) == "low"
|
|
|
|
# Redis 风险
|
|
def test_redis_flushall_is_critical(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("FLUSHALL", db_type="redis") == "critical"
|
|
|
|
def test_redis_flushdb_is_critical(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("FLUSHDB", db_type="redis") == "critical"
|
|
|
|
def test_redis_set_is_medium(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("SET key value", db_type="redis") == "medium"
|
|
|
|
def test_redis_get_is_low(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("GET key", db_type="redis") == "low"
|
|
|
|
# MongoDB 风险
|
|
def test_mongo_drop_database_is_critical(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("db.dropDatabase()", db_type="mongodb") == "critical"
|
|
|
|
def test_mongo_drop_database_upper_is_critical(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("DROP DATABASE mydb", db_type="mongodb") == "critical"
|
|
|
|
def test_mongo_insert_is_medium(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("db.users.insertOne({name: 'test'})", db_type="mongodb") == "medium"
|
|
|
|
def test_mongo_find_is_low(self):
|
|
from app.services.risk_evaluator import evaluate_risk_level
|
|
assert evaluate_risk_level("db.users.find({})", db_type="mongodb") == "low"
|
|
|
|
|
|
# ===== 风险等级配置测试 =====
|
|
|
|
class TestRiskLevelConfig:
|
|
"""风险等级配置验证。"""
|
|
|
|
def test_all_levels_have_config(self):
|
|
from app.services.risk_evaluator import RISK_LEVEL_CONFIG
|
|
for level in ["low", "medium", "high", "critical"]:
|
|
assert level in RISK_LEVEL_CONFIG
|
|
cfg = RISK_LEVEL_CONFIG[level]
|
|
assert "label" in cfg
|
|
assert "color" in cfg
|
|
assert "min_approvers" in cfg
|
|
|
|
def test_high_and_critical_require_2_approvers(self):
|
|
from app.services.risk_evaluator import RISK_LEVEL_CONFIG
|
|
assert RISK_LEVEL_CONFIG["high"]["min_approvers"] >= 2
|
|
assert RISK_LEVEL_CONFIG["critical"]["min_approvers"] >= 2
|
|
|
|
|
|
# ===== 通知脱敏测试 =====
|
|
|
|
class TestNotificationMasking:
|
|
"""通知内容脱敏测试。"""
|
|
|
|
def test_mask_password_in_sql_preview(self):
|
|
from app.services.notification_sender import NotificationSender
|
|
data = {"sql_preview": "SET password='secret123' WHERE id=1"}
|
|
masked = NotificationSender._mask_sensitive_data(data)
|
|
assert "secret123" not in masked["sql_preview"]
|
|
assert "***" in masked["sql_preview"]
|
|
|
|
def test_mask_drop_table_in_sql_preview(self):
|
|
from app.services.notification_sender import NotificationSender
|
|
data = {"sql_preview": "DROP TABLE users"}
|
|
masked = NotificationSender._mask_sensitive_data(data)
|
|
assert "***" in masked["sql_preview"]
|
|
|
|
def test_mask_error_field(self):
|
|
from app.services.notification_sender import NotificationSender
|
|
data = {"error": "Connection failed: password='mysecret'"}
|
|
masked = NotificationSender._mask_sensitive_data(data)
|
|
assert "mysecret" not in masked["error"]
|
|
|
|
def test_truncate_long_sql_preview(self):
|
|
from app.services.notification_sender import NotificationSender
|
|
long_sql = "SELECT * FROM users WHERE " + "x=1 AND " * 50
|
|
data = {"sql_preview": long_sql}
|
|
masked = NotificationSender._mask_sensitive_data(data)
|
|
assert len(masked["sql_preview"]) <= 110 # 100 + "..."
|
|
|
|
def test_non_sensitive_data_unchanged(self):
|
|
from app.services.notification_sender import NotificationSender
|
|
data = {"title": "测试工单", "applicant": "张三"}
|
|
masked = NotificationSender._mask_sensitive_data(data)
|
|
assert masked["title"] == "测试工单"
|
|
assert masked["applicant"] == "张三"
|
|
|
|
|
|
# ===== 维护窗口测试 =====
|
|
|
|
class TestMaintenanceWindow:
|
|
"""维护窗口逻辑测试。"""
|
|
|
|
def test_no_config_allows_execution(self):
|
|
"""无配置时允许执行。"""
|
|
from app.api.v1.endpoints.ticket import _check_maintenance_window
|
|
from unittest.mock import AsyncMock
|
|
import asyncio
|
|
|
|
mock_db = AsyncMock()
|
|
mock_result = MagicMock()
|
|
mock_result.scalar_one_or_none.return_value = None
|
|
mock_db.execute.return_value = mock_result
|
|
|
|
async def run():
|
|
result = await _check_maintenance_window(mock_db)
|
|
assert result is None
|
|
|
|
asyncio.get_event_loop().run_until_complete(run())
|
|
|
|
def test_disabled_window_allows_execution(self):
|
|
"""disabled 的窗口允许执行。"""
|
|
from app.api.v1.endpoints.ticket import _check_maintenance_window
|
|
from app.models.system_setting import SystemSetting
|
|
from unittest.mock import AsyncMock
|
|
import asyncio
|
|
|
|
setting = SystemSetting(key="maintenance_window", value='{"enabled": false}')
|
|
mock_db = AsyncMock()
|
|
mock_result = MagicMock()
|
|
mock_result.scalar_one_or_none.return_value = setting
|
|
mock_db.execute.return_value = mock_result
|
|
|
|
async def run():
|
|
result = await _check_maintenance_window(mock_db)
|
|
assert result is None
|
|
|
|
asyncio.get_event_loop().run_until_complete(run())
|
|
|
|
|
|
# ===== SQL 类型分类测试 =====
|
|
|
|
class TestClassifySqlType:
|
|
"""SQL 类型分类辅助函数测试。"""
|
|
|
|
def test_select_is_dql(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("SELECT * FROM t") == "DQL"
|
|
|
|
def test_insert_is_dml(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("INSERT INTO t VALUES (1)") == "DML"
|
|
|
|
def test_update_is_dml(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("UPDATE t SET x=1") == "DML"
|
|
|
|
def test_delete_is_dml(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("DELETE FROM t") == "DML"
|
|
|
|
def test_create_is_ddl(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("CREATE TABLE t (id INT)") == "DDL"
|
|
|
|
def test_alter_is_ddl(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("ALTER TABLE t ADD COLUMN x INT") == "DDL"
|
|
|
|
def test_drop_is_ddl(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("DROP TABLE t") == "DDL"
|
|
|
|
def test_grant_is_dcl(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("GRANT SELECT ON t TO user") == "DCL"
|
|
|
|
def test_redis_set(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("SET key value") == "SET"
|
|
|
|
def test_redis_get(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("GET key") == "SET"
|
|
|
|
def test_unknown(self):
|
|
from app.api.v1.endpoints.ticket import _classify_sql_type
|
|
assert _classify_sql_type("UNKNOWN COMMAND") == "OTHER"
|