256 lines
9.2 KiB
Python
256 lines
9.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""pbl_blueprint RBAC 路径注册清单(显式枚举,无 %/* 通配符)。
|
||
|
||
本文件是 RBAC 路径的**唯一声明源**。规则:
|
||
1. 每条路径都是完整字面量,以 /api/ 开头、以 .dspy 结尾;
|
||
2. 严禁 % / * 通配符(RBAC 必须逐条授权,不可模糊匹配);
|
||
3. 严禁重复;
|
||
4. 必须**覆盖** pbl_blueprint/json/*.json 中 editable 声明的全部 3×11 条路径
|
||
(selfcheck.py F 组逐条比对,缺一条即退出码非 0);
|
||
5. 表名以设计定稿 B1~B11 为权威
|
||
(projects/pbls/docs/01-design/modules/pbl_blueprint.md §2)。
|
||
|
||
用法:
|
||
python3 modules/pbl_blueprint/scripts/load_path.py # 打印清单
|
||
python3 modules/pbl_blueprint/scripts/load_path.py --check # 与 json editable 比对
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
MODULE_NAME = "pbl_blueprint"
|
||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
JSON_DIR = os.path.join(REPO_ROOT, "pbl_blueprint", "json")
|
||
|
||
# 设计定稿 B1~B11 主清单(权威表名,顺序即编号)
|
||
MAIN_TABLES = (
|
||
"pbl_blueprint", # B1 聚合根
|
||
"pbl_blueprint_version", # B2 版本(append-only)
|
||
"pbl_blueprint_approval", # B3 审批记录
|
||
"pbl_learner", # B4 学习者画像
|
||
"pbl_learning_goal", # B5 学习目标
|
||
"pbl_problem", # B6 真实世界问题
|
||
"pbl_driving_question", # B7 驱动问题
|
||
"pbl_project", # B8 项目(constraints)
|
||
"pbl_role", # B9 角色
|
||
"pbl_mission", # B10 任务
|
||
"pbl_artifact_def", # B11 产出物定义
|
||
)
|
||
|
||
# 运维支撑表(在途已有实现,归档保留,路径仍需授权)
|
||
SUPPORT_TABLES = (
|
||
"pbl_blueprint_audit",
|
||
"pbl_blueprint_edge",
|
||
"pbl_blueprint_fork",
|
||
"pbl_blueprint_lock",
|
||
"pbl_blueprint_node",
|
||
"pbl_blueprint_offline",
|
||
"pbl_blueprint_publish",
|
||
"pbl_blueprint_template",
|
||
"pbl_blueprint_version_delta",
|
||
)
|
||
|
||
# ---------------------------------------------------------------- B1 聚合根
|
||
PATHS_BLUEPRINT = (
|
||
"/api/pbl_blueprint/list.dspy",
|
||
"/api/pbl_blueprint/edit.dspy",
|
||
"/api/pbl_blueprint/view.dspy",
|
||
"/api/pbl_blueprint/create.dspy",
|
||
"/api/pbl_blueprint/get.dspy",
|
||
"/api/pbl_blueprint/update.dspy",
|
||
"/api/pbl_blueprint/delete.dspy",
|
||
"/api/pbl_blueprint/tree.dspy",
|
||
"/api/pbl_blueprint/fork.dspy",
|
||
"/api/pbl_blueprint/forks.dspy",
|
||
"/api/pbl_blueprint/stats.dspy",
|
||
"/api/pbl_blueprint/change_status.dspy",
|
||
"/api/pbl_blueprint/set_quality_level.dspy",
|
||
"/api/pbl_blueprint/submit_approval.dspy",
|
||
"/api/pbl_blueprint/approve.dspy",
|
||
"/api/pbl_blueprint/reject.dspy",
|
||
)
|
||
|
||
# ---------------------------------------------------------------- B2 版本
|
||
PATHS_VERSION = (
|
||
"/api/pbl_blueprint_version/list.dspy",
|
||
"/api/pbl_blueprint_version/edit.dspy",
|
||
"/api/pbl_blueprint_version/view.dspy",
|
||
"/api/pbl_blueprint_version/get.dspy",
|
||
"/api/pbl_blueprint_version/commit.dspy",
|
||
"/api/pbl_blueprint_version/delta.dspy",
|
||
"/api/pbl_blueprint_version/rollback.dspy",
|
||
)
|
||
|
||
# ---------------------------------------------------------------- B3 审批
|
||
PATHS_APPROVAL = (
|
||
"/api/pbl_blueprint_approval/list.dspy",
|
||
"/api/pbl_blueprint_approval/edit.dspy",
|
||
"/api/pbl_blueprint_approval/view.dspy",
|
||
"/api/pbl_blueprint_approval/get.dspy",
|
||
"/api/pbl_blueprint_approval/create.dspy",
|
||
"/api/pbl_blueprint_approval/update.dspy",
|
||
"/api/pbl_blueprint_approval/delete.dspy",
|
||
)
|
||
|
||
# ------------------------------------------- B4~B11 七类子对象(统一 5 动作 CRUD)
|
||
_SUBOBJECT_ACTIONS = ("list", "edit", "view", "create", "get", "update", "delete")
|
||
|
||
PATHS_LEARNER = tuple(
|
||
"/api/pbl_learner/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
|
||
PATHS_LEARNING_GOAL = tuple(
|
||
"/api/pbl_learning_goal/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
|
||
PATHS_PROBLEM = tuple(
|
||
"/api/pbl_problem/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
|
||
PATHS_DRIVING_QUESTION = tuple(
|
||
"/api/pbl_driving_question/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
|
||
PATHS_PROJECT = tuple(
|
||
"/api/pbl_project/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
|
||
PATHS_ROLE = tuple(
|
||
"/api/pbl_role/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
|
||
PATHS_MISSION = tuple(
|
||
"/api/pbl_mission/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
|
||
PATHS_ARTIFACT_DEF = tuple(
|
||
"/api/pbl_artifact_def/%s.dspy" % a for a in _SUBOBJECT_ACTIONS)
|
||
|
||
# ---------------------------------------------------------------- 运维支撑表
|
||
PATHS_SUPPORT = (
|
||
# 审计(append-only,只读)
|
||
"/api/pbl_blueprint_audit/list.dspy",
|
||
"/api/pbl_blueprint_audit/edit.dspy",
|
||
"/api/pbl_blueprint_audit/view.dspy",
|
||
"/api/pbl_blueprint_audit/get.dspy",
|
||
# 关系边
|
||
"/api/pbl_blueprint_edge/list.dspy",
|
||
"/api/pbl_blueprint_edge/edit.dspy",
|
||
"/api/pbl_blueprint_edge/view.dspy",
|
||
"/api/pbl_blueprint_edge/create.dspy",
|
||
"/api/pbl_blueprint_edge/get.dspy",
|
||
"/api/pbl_blueprint_edge/update.dspy",
|
||
"/api/pbl_blueprint_edge/delete.dspy",
|
||
# fork 溯源
|
||
"/api/pbl_blueprint_fork/list.dspy",
|
||
"/api/pbl_blueprint_fork/edit.dspy",
|
||
"/api/pbl_blueprint_fork/view.dspy",
|
||
"/api/pbl_blueprint_fork/get.dspy",
|
||
"/api/pbl_blueprint_fork/lineage.dspy",
|
||
# 编辑锁
|
||
"/api/pbl_blueprint_lock/list.dspy",
|
||
"/api/pbl_blueprint_lock/edit.dspy",
|
||
"/api/pbl_blueprint_lock/view.dspy",
|
||
"/api/pbl_blueprint_lock/lock.dspy",
|
||
"/api/pbl_blueprint_lock/unlock.dspy",
|
||
"/api/pbl_blueprint_lock/clean_expired.dspy",
|
||
# 泛化节点(在途实现)
|
||
"/api/pbl_blueprint_node/list.dspy",
|
||
"/api/pbl_blueprint_node/edit.dspy",
|
||
"/api/pbl_blueprint_node/view.dspy",
|
||
"/api/pbl_blueprint_node/create.dspy",
|
||
"/api/pbl_blueprint_node/get.dspy",
|
||
"/api/pbl_blueprint_node/update.dspy",
|
||
"/api/pbl_blueprint_node/delete.dspy",
|
||
# 离线包
|
||
"/api/pbl_blueprint_offline/list.dspy",
|
||
"/api/pbl_blueprint_offline/edit.dspy",
|
||
"/api/pbl_blueprint_offline/view.dspy",
|
||
"/api/pbl_blueprint_offline/get.dspy",
|
||
"/api/pbl_blueprint_offline/export.dspy",
|
||
"/api/pbl_blueprint_offline/import.dspy",
|
||
# 发布
|
||
"/api/pbl_blueprint_publish/list.dspy",
|
||
"/api/pbl_blueprint_publish/edit.dspy",
|
||
"/api/pbl_blueprint_publish/view.dspy",
|
||
"/api/pbl_blueprint_publish/get.dspy",
|
||
"/api/pbl_blueprint_publish/create.dspy",
|
||
"/api/pbl_blueprint_publish/revoke.dspy",
|
||
# 模板
|
||
"/api/pbl_blueprint_template/list.dspy",
|
||
"/api/pbl_blueprint_template/edit.dspy",
|
||
"/api/pbl_blueprint_template/view.dspy",
|
||
"/api/pbl_blueprint_template/create.dspy",
|
||
"/api/pbl_blueprint_template/get.dspy",
|
||
"/api/pbl_blueprint_template/update.dspy",
|
||
"/api/pbl_blueprint_template/delete.dspy",
|
||
"/api/pbl_blueprint_template/instantiate.dspy",
|
||
# 版本增量明细
|
||
"/api/pbl_blueprint_version_delta/list.dspy",
|
||
"/api/pbl_blueprint_version_delta/edit.dspy",
|
||
"/api/pbl_blueprint_version_delta/view.dspy",
|
||
"/api/pbl_blueprint_version_delta/get.dspy",
|
||
)
|
||
|
||
# ---------------------------------------------------------------- 汇总(唯一出口)
|
||
PATHS = (
|
||
PATHS_BLUEPRINT
|
||
+ PATHS_VERSION
|
||
+ PATHS_APPROVAL
|
||
+ PATHS_LEARNER
|
||
+ PATHS_LEARNING_GOAL
|
||
+ PATHS_PROBLEM
|
||
+ PATHS_DRIVING_QUESTION
|
||
+ PATHS_PROJECT
|
||
+ PATHS_ROLE
|
||
+ PATHS_MISSION
|
||
+ PATHS_ARTIFACT_DEF
|
||
+ PATHS_SUPPORT
|
||
)
|
||
|
||
|
||
def json_editable_paths():
|
||
"""读取 json/*.json 的 editable,返回其对应的 /api/ 路径集合。"""
|
||
out = set()
|
||
if not os.path.isdir(JSON_DIR):
|
||
return out
|
||
for fn in sorted(os.listdir(JSON_DIR)):
|
||
if not fn.endswith(".json"):
|
||
continue
|
||
try:
|
||
with open(os.path.join(JSON_DIR, fn), "r", encoding="utf-8") as f:
|
||
d = json.load(f)
|
||
except Exception: # noqa: BLE001
|
||
continue
|
||
for u in (d.get("editable") or []):
|
||
if isinstance(u, str) and u.startswith("api/"):
|
||
out.add("/" + u)
|
||
return out
|
||
|
||
|
||
def validate():
|
||
"""自检:无通配符 / 全部 /api/ 开头 / 无重复 / 覆盖 json editable。"""
|
||
errs = []
|
||
wild = [p for p in PATHS if "%" in p or "*" in p]
|
||
if wild:
|
||
errs.append("存在通配路径: {}".format(wild))
|
||
bad = [p for p in PATHS if not p.startswith("/api/") or not p.endswith(".dspy")]
|
||
if bad:
|
||
errs.append("非法路径格式: {}".format(bad))
|
||
dup = sorted({p for p in PATHS if PATHS.count(p) > 1})
|
||
if dup:
|
||
errs.append("重复路径: {}".format(dup))
|
||
need = json_editable_paths()
|
||
miss = sorted(need - set(PATHS))
|
||
if miss:
|
||
errs.append("未覆盖 json editable 路径: {}".format(miss))
|
||
return errs
|
||
|
||
|
||
def main(argv):
|
||
if "--check" in argv:
|
||
errs = validate()
|
||
if errs:
|
||
print("RBAC 路径校验失败:")
|
||
for e in errs:
|
||
print(" - " + e)
|
||
return 1
|
||
print("RBAC 路径校验通过: {} 条(无通配符/无重复/覆盖 json editable {} 条)".format(
|
||
len(PATHS), len(json_editable_paths())))
|
||
return 0
|
||
print("# {} RBAC 路径清单({} 条,显式枚举)".format(MODULE_NAME, len(PATHS)))
|
||
for p in PATHS:
|
||
print(p)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main(sys.argv[1:]))
|