feat: 添加安全模块 + Dockerfile添加curl支持健康检查
主要更新: - 新增 security/ 安全模块 (风险评估、威胁检测、蜜罐等) - Dockerfile 添加 curl 以支持 Docker 健康检查 - 前端页面更新 (管理后台、用户端) - 数据库迁移和 schema 更新 - 新增 kdocs 上传服务 - 添加安全相关测试用例 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
230
db/migrations.py
230
db/migrations.py
@@ -72,6 +72,24 @@ def migrate_database(conn, target_version: int) -> None:
|
||||
if current_version < 12:
|
||||
_migrate_to_v12(conn)
|
||||
current_version = 12
|
||||
if current_version < 13:
|
||||
_migrate_to_v13(conn)
|
||||
current_version = 13
|
||||
if current_version < 14:
|
||||
_migrate_to_v14(conn)
|
||||
current_version = 14
|
||||
if current_version < 15:
|
||||
_migrate_to_v15(conn)
|
||||
current_version = 15
|
||||
if current_version < 16:
|
||||
_migrate_to_v16(conn)
|
||||
current_version = 16
|
||||
if current_version < 17:
|
||||
_migrate_to_v17(conn)
|
||||
current_version = 17
|
||||
if current_version < 18:
|
||||
_migrate_to_v18(conn)
|
||||
current_version = 18
|
||||
|
||||
if current_version != int(target_version):
|
||||
set_current_version(conn, int(target_version))
|
||||
@@ -519,3 +537,215 @@ def _migrate_to_v12(conn):
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_login_ips_user ON login_ips(user_id)")
|
||||
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _migrate_to_v13(conn):
|
||||
"""迁移到版本13 - 安全防护:威胁检测相关表"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS threat_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
threat_type TEXT NOT NULL,
|
||||
score INTEGER NOT NULL DEFAULT 0,
|
||||
rule TEXT,
|
||||
field_name TEXT,
|
||||
matched TEXT,
|
||||
value_preview TEXT,
|
||||
ip TEXT,
|
||||
user_id INTEGER,
|
||||
request_method TEXT,
|
||||
request_path TEXT,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
)
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_events_created_at ON threat_events(created_at)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_events_ip ON threat_events(ip)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_events_user_id ON threat_events(user_id)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_events_type ON threat_events(threat_type)")
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS ip_risk_scores (
|
||||
ip TEXT PRIMARY KEY,
|
||||
risk_score INTEGER NOT NULL DEFAULT 0,
|
||||
last_seen TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_ip_risk_scores_score ON ip_risk_scores(risk_score)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_ip_risk_scores_updated_at ON ip_risk_scores(updated_at)")
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS user_risk_scores (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
risk_score INTEGER NOT NULL DEFAULT 0,
|
||||
last_seen TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
)
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_risk_scores_score ON user_risk_scores(risk_score)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_risk_scores_updated_at ON user_risk_scores(updated_at)")
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS ip_blacklist (
|
||||
ip TEXT PRIMARY KEY,
|
||||
reason TEXT,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_ip_blacklist_active ON ip_blacklist(is_active)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_ip_blacklist_expires ON ip_blacklist(expires_at)")
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS threat_signatures (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
threat_type TEXT NOT NULL,
|
||||
pattern TEXT NOT NULL,
|
||||
pattern_type TEXT DEFAULT 'regex',
|
||||
score INTEGER DEFAULT 0,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_signatures_type ON threat_signatures(threat_type)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_threat_signatures_active ON threat_signatures(is_active)")
|
||||
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _migrate_to_v14(conn):
|
||||
"""迁移到版本14 - 安全防护:用户黑名单表"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS user_blacklist (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
reason TEXT,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_blacklist_active ON user_blacklist(is_active)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_blacklist_expires ON user_blacklist(expires_at)")
|
||||
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _migrate_to_v15(conn):
|
||||
"""迁移到版本15 - 邮件设置:新设备登录提醒全局开关"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='email_settings'")
|
||||
if not cursor.fetchone():
|
||||
# 邮件表由 email_service.init_email_tables 创建;此处仅做增量字段迁移
|
||||
return
|
||||
|
||||
cursor.execute("PRAGMA table_info(email_settings)")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
|
||||
changed = False
|
||||
if "login_alert_enabled" not in columns:
|
||||
cursor.execute("ALTER TABLE email_settings ADD COLUMN login_alert_enabled INTEGER DEFAULT 1")
|
||||
print(" ✓ 添加 email_settings.login_alert_enabled 字段")
|
||||
changed = True
|
||||
|
||||
try:
|
||||
cursor.execute("UPDATE email_settings SET login_alert_enabled = 1 WHERE login_alert_enabled IS NULL")
|
||||
if cursor.rowcount:
|
||||
changed = True
|
||||
except sqlite3.OperationalError:
|
||||
# 列不存在等情况由上方迁移兜底;不阻断主流程
|
||||
pass
|
||||
|
||||
if changed:
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _migrate_to_v16(conn):
|
||||
"""迁移到版本16 - 公告支持图片字段"""
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("PRAGMA table_info(announcements)")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
|
||||
if "image_url" not in columns:
|
||||
cursor.execute("ALTER TABLE announcements ADD COLUMN image_url TEXT")
|
||||
conn.commit()
|
||||
print(" ✓ 添加 announcements.image_url 字段")
|
||||
|
||||
|
||||
def _migrate_to_v17(conn):
|
||||
"""迁移到版本17 - 金山文档上传配置与用户开关"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("PRAGMA table_info(system_config)")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
|
||||
system_fields = [
|
||||
("kdocs_enabled", "INTEGER DEFAULT 0"),
|
||||
("kdocs_doc_url", "TEXT DEFAULT ''"),
|
||||
("kdocs_default_unit", "TEXT DEFAULT ''"),
|
||||
("kdocs_sheet_name", "TEXT DEFAULT ''"),
|
||||
("kdocs_sheet_index", "INTEGER DEFAULT 0"),
|
||||
("kdocs_unit_column", "TEXT DEFAULT 'A'"),
|
||||
("kdocs_image_column", "TEXT DEFAULT 'D'"),
|
||||
("kdocs_admin_notify_enabled", "INTEGER DEFAULT 0"),
|
||||
("kdocs_admin_notify_email", "TEXT DEFAULT ''"),
|
||||
]
|
||||
for field, ddl in system_fields:
|
||||
if field not in columns:
|
||||
cursor.execute(f"ALTER TABLE system_config ADD COLUMN {field} {ddl}")
|
||||
print(f" ✓ 添加 system_config.{field} 字段")
|
||||
|
||||
cursor.execute("PRAGMA table_info(users)")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
|
||||
user_fields = [
|
||||
("kdocs_unit", "TEXT DEFAULT ''"),
|
||||
("kdocs_auto_upload", "INTEGER DEFAULT 0"),
|
||||
]
|
||||
for field, ddl in user_fields:
|
||||
if field not in columns:
|
||||
cursor.execute(f"ALTER TABLE users ADD COLUMN {field} {ddl}")
|
||||
print(f" ✓ 添加 users.{field} 字段")
|
||||
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _migrate_to_v18(conn):
|
||||
"""迁移到版本18 - 金山文档上传:有效行范围配置"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("PRAGMA table_info(system_config)")
|
||||
columns = [col[1] for col in cursor.fetchall()]
|
||||
|
||||
if "kdocs_row_start" not in columns:
|
||||
cursor.execute("ALTER TABLE system_config ADD COLUMN kdocs_row_start INTEGER DEFAULT 0")
|
||||
print(" ✓ 添加 system_config.kdocs_row_start 字段")
|
||||
|
||||
if "kdocs_row_end" not in columns:
|
||||
cursor.execute("ALTER TABLE system_config ADD COLUMN kdocs_row_end INTEGER DEFAULT 0")
|
||||
print(" ✓ 添加 system_config.kdocs_row_end 字段")
|
||||
|
||||
conn.commit()
|
||||
|
||||
Reference in New Issue
Block a user