112 lines
4.8 KiB
Python
112 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""pbl_scense_ext RBAC 路径注册(硬门禁 6.6 / QC #11 / M9 重做 QC #1)。
|
||
|
||
约定:
|
||
- 路径 = 模块自动路由 `/pbl_scense_ext/...`,不带端口、不带 /wss 前缀;
|
||
- 角色 `any` = 免登录可取(覆盖层引导脚本 loader.js、覆盖层主题 CSS——须在
|
||
鉴权完成前即可注入,否则预鉴权页壳拿不到 loader);
|
||
- 角色 `logined` = 登录后可访问(契约 .dspy 接口、覆盖层业务 JS、12 个页面壳 .ui);
|
||
- 禁止 `%` / `*` 通配符(module-development-spec「load_path.py Wildcards are FORBIDDEN」),
|
||
每个文件一条显式条目;新增/删除静态资源必须同步本表;
|
||
- 由 apps/pbls/build.sh 第 8 步调用 `register()`;rbac CLI 不在位时打印清单(不静默跳过)。
|
||
"""
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
|
||
MODULE = 'pbl_scense_ext'
|
||
|
||
# 覆盖层 12 页面壳(wwwroot/pbl_overlay/*.ui)——与 wwwroot/overlay/pbl_overlay_core.js
|
||
# PAGES 注册表 route 同源(route '/pbl_overlay/<key>.ui' → 本模块目录 pbl_overlay/<key>.ui)
|
||
OVERLAY_PAGES = [
|
||
'home', 'blueprint_list', 'blueprint_editor', 'validation_report',
|
||
'agent_console', 'approval_center', 'play_preview', 'mission_board',
|
||
'role_panel', 'artifact_studio', 'evidence_wall', 'assessment_report',
|
||
]
|
||
|
||
# (path, role)
|
||
PATHS = [
|
||
# ── 契约接口(4 个 .dspy,均需登录) ──
|
||
('/pbl_scense_ext/api/pbl_session_context_get.dspy', 'logined'),
|
||
('/pbl_scense_ext/api/pbl_page_registry_list.dspy', 'logined'),
|
||
('/pbl_scense_ext/api/pbl_playtest_feedback_save.dspy', 'logined'),
|
||
('/pbl_scense_ext/api/pbl_overlay_bootstrap.dspy', 'logined'),
|
||
|
||
# ── 模块入口页 ──
|
||
('/pbl_scense_ext', 'logined'),
|
||
('/pbl_scense_ext/index.ui', 'logined'),
|
||
|
||
# ── 覆盖层静态资源(QC #1 硬门禁:ahserver 自动托管 .css/.js 但仍受 RBAC 约束,
|
||
# 未注册即 403,M9 前端全废) ──
|
||
# loader.js 是页壳引导注入器,须在预鉴权页壳里就能加载 → any
|
||
('/pbl_scense_ext/overlay/pbl_overlay_loader.js', 'any'),
|
||
# 主题 CSS 同理(页壳首屏样式,不含业务数据)→ any
|
||
('/pbl_scense_ext/overlay/pbl_overlay.css', 'any'),
|
||
# 业务 JS 只在登录后由 loader 注入 → logined
|
||
('/pbl_scense_ext/overlay/pbl_overlay_core.js', 'logined'),
|
||
('/pbl_scense_ext/overlay/pbl_overlay_transport.js', 'logined'),
|
||
('/pbl_scense_ext/overlay/pbl_overlay_play.js', 'logined'),
|
||
]
|
||
|
||
# 覆盖层页面壳 .ui:目录 + 每个文件显式注册(登录后可见,含世界内覆盖层页)
|
||
for _key in OVERLAY_PAGES:
|
||
PATHS.append(('/pbl_scense_ext/pbl_overlay/%s.ui' % _key, 'logined'))
|
||
PATHS.append(('/pbl_scense_ext/pbl_overlay', 'logined'))
|
||
|
||
|
||
def register():
|
||
tool = os.environ.get('RBAC_SET_PERM', 'set_role_perm.py')
|
||
done, missing = 0, []
|
||
for path, role in PATHS:
|
||
if subprocess.call([sys.executable if os.environ.get('PY') else 'python3',
|
||
tool, role, path],
|
||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
|
||
done += 1
|
||
else:
|
||
missing.append((path, role))
|
||
print('[%s] rbac paths: total=%d ok=%d pending=%d' % (MODULE, len(PATHS), done, len(missing)))
|
||
for path, role in missing:
|
||
print(' PENDING %-12s %s' % (role, path))
|
||
return len(missing) == 0
|
||
|
||
|
||
def audit():
|
||
"""自检:PATHS 中的 overlay/pbl_overlay 条目与磁盘文件一一对应(无悬空、无漏注册)。"""
|
||
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
problems = []
|
||
# 1) 磁盘上有但未注册
|
||
for sub in ('overlay', 'pbl_overlay'):
|
||
d = os.path.join(root, 'wwwroot', sub)
|
||
if not os.path.isdir(d):
|
||
continue
|
||
for fn in sorted(os.listdir(d)):
|
||
if not fn.endswith(('.js', '.css', '.ui')):
|
||
continue
|
||
p = '/%s/%s/%s' % (MODULE, sub, fn)
|
||
if p not in [x[0] for x in PATHS]:
|
||
problems.append('UNREGISTERED %s' % p)
|
||
# 2) 注册了但磁盘缺文件(页面壳条目)
|
||
have = set('/%s/pbl_overlay/%s.ui' % (MODULE, k) for k in OVERLAY_PAGES)
|
||
for path, _role in PATHS:
|
||
if '/pbl_overlay/' in path and path not in have:
|
||
problems.append('BAD ENTRY %s' % path)
|
||
for path in sorted(have):
|
||
f = os.path.join(root, 'wwwroot', 'pbl_overlay', os.path.basename(path))
|
||
if not os.path.isfile(f):
|
||
problems.append('MISSING FILE %s' % path)
|
||
# 3) 通配符零容忍
|
||
for path, _role in PATHS:
|
||
if '%' in path or '*' in path:
|
||
problems.append('WILDCARD FORBIDDEN %s' % path)
|
||
return problems
|
||
|
||
|
||
if __name__ == '__main__':
|
||
bad = audit()
|
||
for b in bad:
|
||
print('AUDIT %s' % b)
|
||
if '--audit' in sys.argv:
|
||
sys.exit(1 if bad else 0)
|
||
sys.exit(0 if register() else 1)
|