180 lines
5.6 KiB
Python
180 lines
5.6 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""pbl_blueprint RBAC 路径注册清单(显式枚举,无 %/* 通配符)。
|
||
|
||
本文件是 RBAC 路径的唯一声明源:init.py 的 RBAC_PATHS 必须与此处 PATHS 完全一致,
|
||
selfcheck.py 会逐条比对(不一致即退出码非 0)。
|
||
|
||
用法:
|
||
python3 modules/pbl_blueprint/scripts/load_path.py # 打印清单
|
||
python3 modules/pbl_blueprint/scripts/load_path.py --check # 与 init.py 比对
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
|
||
MODULE_NAME = "pbl_blueprint"
|
||
|
||
# 蓝图主表
|
||
PATHS_BLUEPRINT = (
|
||
"/api/pbl_blueprint/list.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",
|
||
)
|
||
|
||
# 子对象节点
|
||
PATHS_NODE = (
|
||
"/api/pbl_blueprint_node/list.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",
|
||
)
|
||
|
||
# 关系边
|
||
PATHS_EDGE = (
|
||
"/api/pbl_blueprint_edge/list.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",
|
||
)
|
||
|
||
# 版本与差异
|
||
PATHS_VERSION = (
|
||
"/api/pbl_blueprint_version/list.dspy",
|
||
"/api/pbl_blueprint_version/get.dspy",
|
||
"/api/pbl_blueprint_version/save.dspy",
|
||
"/api/pbl_blueprint_version/rollback.dspy",
|
||
"/api/pbl_blueprint_version_delta/list.dspy",
|
||
"/api/pbl_blueprint_version_delta/get.dspy",
|
||
)
|
||
|
||
# 模板
|
||
PATHS_TEMPLATE = (
|
||
"/api/pbl_blueprint_template/list.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",
|
||
)
|
||
|
||
# 发布
|
||
PATHS_PUBLISH = (
|
||
"/api/pbl_blueprint_publish/list.dspy",
|
||
"/api/pbl_blueprint_publish/create.dspy",
|
||
"/api/pbl_blueprint_publish/get.dspy",
|
||
"/api/pbl_blueprint_publish/revoke.dspy",
|
||
)
|
||
|
||
# 离线包
|
||
PATHS_OFFLINE = (
|
||
"/api/pbl_blueprint_offline/list.dspy",
|
||
"/api/pbl_blueprint_offline/export.dspy",
|
||
"/api/pbl_blueprint_offline/import.dspy",
|
||
"/api/pbl_blueprint_offline/get.dspy",
|
||
)
|
||
|
||
# 血缘
|
||
PATHS_FORK = (
|
||
"/api/pbl_blueprint_fork/list.dspy",
|
||
"/api/pbl_blueprint_fork/get.dspy",
|
||
)
|
||
|
||
# 编辑锁
|
||
PATHS_LOCK = (
|
||
"/api/pbl_blueprint_lock/lock.dspy",
|
||
"/api/pbl_blueprint_lock/unlock.dspy",
|
||
"/api/pbl_blueprint_lock/list.dspy",
|
||
"/api/pbl_blueprint_lock/clean_expired.dspy",
|
||
)
|
||
|
||
# 审计(只读)
|
||
PATHS_AUDIT = (
|
||
"/api/pbl_blueprint_audit/list.dspy",
|
||
"/api/pbl_blueprint_audit/get.dspy",
|
||
)
|
||
|
||
PATHS = (
|
||
PATHS_BLUEPRINT + PATHS_NODE + PATHS_EDGE + PATHS_VERSION + PATHS_TEMPLATE +
|
||
PATHS_PUBLISH + PATHS_OFFLINE + PATHS_FORK + PATHS_LOCK + PATHS_AUDIT
|
||
)
|
||
|
||
# 只读路径(RBAC 上归为 read 权限组)
|
||
READONLY_PATHS = tuple(p for p in PATHS if p.rsplit("/", 1)[-1].split(".")[0] in
|
||
("list", "get", "tree", "forks", "stats"))
|
||
WRITE_PATHS = tuple(p for p in PATHS if p not in READONLY_PATHS)
|
||
|
||
|
||
def validate(paths=None):
|
||
"""校验路径合法性:非空、以 / 开头、.dspy 结尾、无通配符、无重复。返回错误列表。"""
|
||
ps = list(paths if paths is not None else PATHS)
|
||
errs = []
|
||
seen = set()
|
||
for p in ps:
|
||
if not p or not isinstance(p, str):
|
||
errs.append("空路径或非字符串: %r" % (p,))
|
||
continue
|
||
if not p.startswith("/api/"):
|
||
errs.append("路径必须以 /api/ 开头: %s" % p)
|
||
if not p.endswith(".dspy"):
|
||
errs.append("路径必须以 .dspy 结尾: %s" % p)
|
||
if "%" in p or "*" in p or "?" in p:
|
||
errs.append("路径含通配符(禁止): %s" % p)
|
||
if " " in p:
|
||
errs.append("路径含空格: %s" % p)
|
||
if p in seen:
|
||
errs.append("路径重复: %s" % p)
|
||
seen.add(p)
|
||
return errs
|
||
|
||
|
||
def check_against_init():
|
||
"""与 pbl_blueprint/init.py 的 RBAC_PATHS 逐条比对,返回 (ok, diff_msg)。"""
|
||
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
if root not in sys.path:
|
||
sys.path.insert(0, root)
|
||
try:
|
||
from pbl_blueprint import init as _init # noqa
|
||
except Exception as e:
|
||
return False, "无法导入 pbl_blueprint.init: %s" % e
|
||
a = list(PATHS)
|
||
b = list(getattr(_init, "RBAC_PATHS", ()))
|
||
if a == b:
|
||
return True, "RBAC 路径一致(%d 条,顺序相同)" % len(a)
|
||
only_a = [x for x in a if x not in b]
|
||
only_b = [x for x in b if x not in a]
|
||
return False, "RBAC 路径不一致: 仅 load_path=%s / 仅 init=%s / 顺序差异=%s" % (
|
||
only_a, only_b, a != b and not only_a and not only_b)
|
||
|
||
|
||
def main(argv=None):
|
||
argv = list(sys.argv[1:] if argv is None else argv)
|
||
if "--check" in argv:
|
||
errs = validate()
|
||
good, msg = check_against_init()
|
||
for e in errs:
|
||
print("[PATH-ERR] %s" % e)
|
||
print("[CHECK] %s" % msg)
|
||
print("[STATS] total=%d readonly=%d write=%d" % (len(PATHS), len(READONLY_PATHS), len(WRITE_PATHS)))
|
||
return 0 if (good and not errs) else 1
|
||
print("# %s RBAC 路径清单(显式枚举,共 %d 条,无通配符)" % (MODULE_NAME, len(PATHS)))
|
||
for p in PATHS:
|
||
print(p)
|
||
errs = validate()
|
||
if errs:
|
||
for e in errs:
|
||
print("[PATH-ERR] %s" % e, file=sys.stderr)
|
||
return 1
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|