177 lines
7.3 KiB
Python
177 lines
7.3 KiB
Python
|
|
"""ORM 模型定义。
|
|||
|
|
|
|||
|
|
涉及表:
|
|||
|
|
- ``scan_tasks`` 扫描任务(同步与异步任务共用一张表)
|
|||
|
|
- ``scan_findings`` 单条检测信号
|
|||
|
|
- ``audit_logs`` 审计日志(带哈希链,防篡改)
|
|||
|
|
- ``console_users`` 控制台账号
|
|||
|
|
- ``login_attempts`` 登录失败记录(用于锁定)
|
|||
|
|
- ``policy_versions`` 策略版本快照
|
|||
|
|
- ``review_records`` 人工复核记录
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import uuid
|
|||
|
|
from datetime import datetime, timezone
|
|||
|
|
|
|||
|
|
from sqlalchemy import (
|
|||
|
|
JSON,
|
|||
|
|
Boolean,
|
|||
|
|
DateTime,
|
|||
|
|
Float,
|
|||
|
|
ForeignKey,
|
|||
|
|
Index,
|
|||
|
|
Integer,
|
|||
|
|
String,
|
|||
|
|
Text,
|
|||
|
|
UniqueConstraint,
|
|||
|
|
)
|
|||
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|||
|
|
|
|||
|
|
from app.db import Base
|
|||
|
|
|
|||
|
|
|
|||
|
|
def utcnow() -> datetime:
|
|||
|
|
return datetime.now(timezone.utc)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def new_id() -> str:
|
|||
|
|
return uuid.uuid4().hex
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ScanTask(Base):
|
|||
|
|
__tablename__ = "scan_tasks"
|
|||
|
|
|
|||
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=new_id)
|
|||
|
|
channel: Mapped[str] = mapped_column(String(64), default="default", index=True)
|
|||
|
|
client_id: Mapped[str] = mapped_column(String(64), index=True)
|
|||
|
|
|
|||
|
|
filename: Mapped[str] = mapped_column(String(512))
|
|||
|
|
declared_ext: Mapped[str] = mapped_column(String(32), default="")
|
|||
|
|
size: Mapped[int] = mapped_column(Integer, default=0)
|
|||
|
|
sha256: Mapped[str] = mapped_column(String(64), index=True)
|
|||
|
|
md5: Mapped[str] = mapped_column(String(32), default="")
|
|||
|
|
real_type: Mapped[str] = mapped_column(String(256), default="")
|
|||
|
|
|
|||
|
|
status: Mapped[str] = mapped_column(String(16), default="pending", index=True)
|
|||
|
|
mode: Mapped[str] = mapped_column(String(16), default="sync")
|
|||
|
|
verdict: Mapped[str] = mapped_column(String(24), default="pending")
|
|||
|
|
action: Mapped[str] = mapped_column(String(32), default="pending")
|
|||
|
|
score: Mapped[float] = mapped_column(Float, default=0.0)
|
|||
|
|
reason: Mapped[str] = mapped_column(Text, default="")
|
|||
|
|
|
|||
|
|
policy_version: Mapped[str] = mapped_column(String(64), default="")
|
|||
|
|
detail: Mapped[dict] = mapped_column(JSON, default=dict)
|
|||
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, index=True)
|
|||
|
|
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|||
|
|
duration_ms: Mapped[int] = mapped_column(Integer, default=0)
|
|||
|
|
|
|||
|
|
findings: Mapped[list["ScanFinding"]] = relationship(
|
|||
|
|
back_populates="task", cascade="all, delete-orphan", lazy="selectin"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ScanFinding(Base):
|
|||
|
|
__tablename__ = "scan_findings"
|
|||
|
|
|
|||
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|||
|
|
task_id: Mapped[str] = mapped_column(
|
|||
|
|
String(64), ForeignKey("scan_tasks.id", ondelete="CASCADE"), index=True
|
|||
|
|
)
|
|||
|
|
engine: Mapped[str] = mapped_column(String(64))
|
|||
|
|
layer: Mapped[str] = mapped_column(String(8))
|
|||
|
|
severity: Mapped[str] = mapped_column(String(16))
|
|||
|
|
rule: Mapped[str] = mapped_column(String(256))
|
|||
|
|
detail: Mapped[str] = mapped_column(Text, default="")
|
|||
|
|
extracted_path: Mapped[str] = mapped_column(String(1024), default="")
|
|||
|
|
weight: Mapped[float] = mapped_column(Float, default=0.0)
|
|||
|
|
|
|||
|
|
task: Mapped[ScanTask] = relationship(back_populates="findings")
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AuditLog(Base):
|
|||
|
|
"""审计日志。
|
|||
|
|
|
|||
|
|
每条记录包含 ``prev_hash`` 与 ``row_hash``,形成哈希链。
|
|||
|
|
任一条记录被篡改或删除,后续记录的链式校验都会失败,可被检测出来。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
__tablename__ = "audit_logs"
|
|||
|
|
|
|||
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|||
|
|
ts: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, index=True)
|
|||
|
|
actor: Mapped[str] = mapped_column(String(128), default="system")
|
|||
|
|
actor_role: Mapped[str] = mapped_column(String(32), default="system")
|
|||
|
|
action: Mapped[str] = mapped_column(String(64), index=True)
|
|||
|
|
target: Mapped[str] = mapped_column(String(256), default="")
|
|||
|
|
channel: Mapped[str] = mapped_column(String(64), default="")
|
|||
|
|
client_ip: Mapped[str] = mapped_column(String(64), default="")
|
|||
|
|
outcome: Mapped[str] = mapped_column(String(24), default="success")
|
|||
|
|
detail: Mapped[dict] = mapped_column(JSON, default=dict)
|
|||
|
|
prev_hash: Mapped[str] = mapped_column(String(64), default="")
|
|||
|
|
row_hash: Mapped[str] = mapped_column(String(64), default="")
|
|||
|
|
|
|||
|
|
__table_args__ = (Index("ix_audit_logs_action_ts", "action", "ts"),)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ConsoleUser(Base):
|
|||
|
|
__tablename__ = "console_users"
|
|||
|
|
|
|||
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|||
|
|
username: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|||
|
|
password_hash: Mapped[str] = mapped_column(String(256))
|
|||
|
|
role: Mapped[str] = mapped_column(String(32), default="admin")
|
|||
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|||
|
|
must_change_password: Mapped[bool] = mapped_column(Boolean, default=True)
|
|||
|
|
failed_attempts: Mapped[int] = mapped_column(Integer, default=0)
|
|||
|
|
locked_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|||
|
|
last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|||
|
|
password_changed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|||
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LoginAttempt(Base):
|
|||
|
|
__tablename__ = "login_attempts"
|
|||
|
|
|
|||
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|||
|
|
username: Mapped[str] = mapped_column(String(64), index=True)
|
|||
|
|
client_ip: Mapped[str] = mapped_column(String(64), default="")
|
|||
|
|
success: Mapped[bool] = mapped_column(Boolean, default=False)
|
|||
|
|
ts: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, index=True)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PolicyVersion(Base):
|
|||
|
|
__tablename__ = "policy_versions"
|
|||
|
|
|
|||
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|||
|
|
version: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|||
|
|
content: Mapped[dict] = mapped_column(JSON, default=dict)
|
|||
|
|
sha256: Mapped[str] = mapped_column(String(64), default="")
|
|||
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ReviewRecord(Base):
|
|||
|
|
__tablename__ = "review_records"
|
|||
|
|
|
|||
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|||
|
|
task_id: Mapped[str] = mapped_column(String(64), ForeignKey("scan_tasks.id", ondelete="CASCADE"), index=True)
|
|||
|
|
reviewer: Mapped[str] = mapped_column(String(64))
|
|||
|
|
decision: Mapped[str] = mapped_column(String(32))
|
|||
|
|
comment: Mapped[str] = mapped_column(Text, default="")
|
|||
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class IdempotencyRecord(Base):
|
|||
|
|
"""幂等记录:同一文件哈希 + 同一策略版本只扫描一次。"""
|
|||
|
|
|
|||
|
|
__tablename__ = "idempotency_records"
|
|||
|
|
|
|||
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|||
|
|
sha256: Mapped[str] = mapped_column(String(64), index=True)
|
|||
|
|
policy_version: Mapped[str] = mapped_column(String(64), index=True)
|
|||
|
|
task_id: Mapped[str] = mapped_column(String(64))
|
|||
|
|
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
|||
|
|
|
|||
|
|
__table_args__ = (UniqueConstraint("sha256", "policy_version", name="uq_idem_hash_policy"),)
|