mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-15 21:35:34 +00:00
fix(backend): 安全默认值、异常语义、代码生成注入面与定时任务内置处理器
本批为 t10 → t13 → t15 三轮实现 + 一轮复核驳回整改的最终形态(pytest 83 passed / ruff clean,
已部署并线上验证)。
异常语义
- CustomException 默认状态码 500 → 400(约 433 处裸调用不再被计为服务端故障),
且「包装底层异常」的 130 处显式恢复 500;get_or_404 → 404;响应体结构不变
代码生成注入面(复核 blocker F1,二次修复)
- column_type 不再做字符串拼接:改为常量白名单派生(类型名 ∈ DB_TO_SQLALCHEMY ∪ {Boolean},
长度仅取数字/正则捕获),彻底移除 split("(")[1]
- 列类型加形状白名单;字符串/注释/文档字符串用 \uXXXX,标识符过 safe_ident(含 Python/JS 关键字),
.vue 文本与静态属性改 HTML 实体
- 落盘闸门由 ast.parse 升级为 assert_python_structure(语法 + 顶层语句白名单 + 与哨兵骨架逐节点比对)
与 assert_text_structure(.vue/.ts/.toml 行结构);U+2028/2029 与 C1 控制符纳入转义
安全默认值
- 生产 CORS 不再回落 ["*"](未配置即拒绝跨域,可选 CORS_STRICT_STARTUP 直接启动失败)
- OAUTH_ALLOWED_HOSTS 不再默认 "*";新增 ALLOW_LOCALHOST_HOSTS 控制 Host 校验范围
- ALLOWED_HOSTS 收窄为按配置派生;健康检查暴露 code_exec_enabled / cors_origins_configured
定时任务
- SCHEDULER_ALLOW_CODE_EXEC 默认 False;新增 builtin:<模块名>[.<函数名>] 内置处理器路线(不经 exec)
- 判定收敛到 describe_schedulability;列表返回 schedulable / unschedulable_reason;
启动 reconcile 与状态查询对「已启用但不可调度」打 WARN(去重、恢复后清除)
- 加固:内置处理器必须在本模块内定义或经 HANDLERS 声明,拒绝模块内导入的外部对象(复核 R1)
- 顺带修复:_WARNED_UNSCHEDULABLE 的 set.pop TypeError、job.next_run_time 缺失导致的列表 500
测试:新增 tests/test_generator_template_safety.py、test_security_defaults.py、
test_scheduler_schedulability.py(含恶意载荷、闸门红绿、静态护栏)
This commit is contained in:
@@ -4,6 +4,8 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from app.core.base_schema import BaseQueryParam, BaseSchema, UserByQueryParam, UserBySchema
|
||||
|
||||
from .template_safety import is_safe_identifier, is_valid_column_type, normalize_column_type, reserved_word_reason
|
||||
|
||||
|
||||
class GenDBTableSchema(BaseModel):
|
||||
"""数据库中的表信息(跨方言统一结构)。
|
||||
@@ -59,6 +61,71 @@ class GenTableColumnSchema(BaseModel):
|
||||
dict_type: str | None = Field(default="", description="字典类型")
|
||||
sort: int = Field(default=0, description="排序")
|
||||
|
||||
@field_validator("column_name", mode="before")
|
||||
@classmethod
|
||||
def validate_column_name(cls, v: str | None) -> str:
|
||||
"""列名白名单校验:必须能直接充当 Python / JS 标识符。
|
||||
|
||||
列名会被原样写进 ``Mapped[..]`` 属性名、``class`` 字段名与前端 ``prop``,
|
||||
是注入面之一(见 backend/audit-backend.md B1)。非法名(含引号/空格/换行/数字开头等)
|
||||
在此直接拒绝,避免生成出语法不合法或可被注入的代码。
|
||||
|
||||
参数:
|
||||
- v (str | None): 原始列名。
|
||||
|
||||
返回:
|
||||
- str: 去空白后的合法列名;未填写时返回空串(沿用历史行为)。
|
||||
|
||||
异常:
|
||||
- ValueError: 列名不是合法标识符时抛出。
|
||||
"""
|
||||
if v is None:
|
||||
return ""
|
||||
text = str(v).strip()
|
||||
if not text:
|
||||
return ""
|
||||
if not is_safe_identifier(text):
|
||||
reason = reserved_word_reason(text)
|
||||
if reason:
|
||||
raise ValueError(
|
||||
f"列名 {text!r} 是 {reason},作为字段名会生成非法代码(如 class: Mapped[...]),"
|
||||
f"请改名后再导入/保存(可改为 {text}_ 之类)",
|
||||
)
|
||||
raise ValueError(f"列名 {text!r} 不是合法的标识符(仅允许字母/数字/下划线,且不能以数字开头),代码生成器无法为它生成合法代码,请先改名")
|
||||
return text
|
||||
|
||||
@field_validator("column_type", mode="before")
|
||||
@classmethod
|
||||
def validate_column_type(cls, v: str | None) -> str:
|
||||
"""列类型形状白名单校验(结构化,非法即拒绝)。
|
||||
|
||||
允许的形状:``基名`` + 可选 ``(数字)`` / ``(数字,数字)`` + 可选 ``[]`` + 可选 ``unsigned``。
|
||||
MySQL 的 ``enum(...)``/``set(...)`` 会先归一化为基名 ``enum``/``set``(其成员列表既无用途也含引号)。
|
||||
|
||||
为什么必须校验:列类型会参与「代码位置」派生(SQLAlchemy 类型名 + 长度/精度),
|
||||
任意文本一旦被拼接进生成代码就等于注入面(见 backend/audit-backend.md B1)。
|
||||
|
||||
参数:
|
||||
- v (str | None): 原始列类型。
|
||||
|
||||
返回:
|
||||
- str: 归一化后的列类型;未填写时返回空串(由 SQLAlchemy 映射回落 String)。
|
||||
|
||||
异常:
|
||||
- ValueError: 形状非法时抛出(报错带上列类型原文)。
|
||||
"""
|
||||
if v is None:
|
||||
return ""
|
||||
text = str(v).strip()
|
||||
if not text:
|
||||
return ""
|
||||
if not is_valid_column_type(text):
|
||||
raise ValueError(
|
||||
f"列类型 {text!r} 形状非法:仅允许「类型名 + 可选(长度[,精度]) + 可选[] + 可选 unsigned」,例如 varchar(64)、decimal(10,2)、tinyint(1) unsigned;"
|
||||
"含引号/分号/括号内非数字等内容会被拒绝(代码生成器不接收自由文本类型)",
|
||||
)
|
||||
return normalize_column_type(text)
|
||||
|
||||
|
||||
class GenTableColumnOutSchema(GenTableColumnSchema, BaseSchema):
|
||||
"""业务表字段输出模型
|
||||
|
||||
Reference in New Issue
Block a user