pbl_appcodes/scripts/load_path.py
2026-09-22 17:37:36 +08:00

190 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""pbl_appcodes 模块 RBAC / 路径声明脚本(per-module,中央层 apps/pbls 聚合)。
用法:
python3 load_path.py # 找到 Sage 根时逐条 set_role_perm 登记;找不到则打印跳过说明
python3 load_path.py --check # 离线自检:无通配符 + 声明与磁盘 wwwroot 一致
═══════════════════════════════════════════════════════════════════════════════
里程碑 RBAC 声明台账(每个里程碑必须显式登记一行,禁止留空/待补)
═══════════════════════════════════════════════════════════════════════════════
M11b-2(T3,2026-09-22):
结论 = 本里程碑新增路径 0 条(M11b-2 只交付库函数与表定义,见 world_sync 侧台账)。
顺带闭环(QC 退回意见 #2「同类缺陷未闭环」):
本模块此前 scripts/load_path.py 只有一行注释、**没有任何 PATHS 常量**,中央聚合器
把它当「empty (skipped)」静默跳过;而磁盘 wwwroot 下真实存在 3 个 Web 文件
(index.ui + api/pbl_appcodes_inject.dspy + api/pbl_appcodes_list.dspy),
即「文件在、路由在、RBAC 不在」= 点进去一律 403 的同类缺口。
本次补齐**逐条显式**登记,并把「声明与磁盘一致」做进 --check 门禁。
铁律 = 禁通配符(% / *),所有路径逐条显式;新增路径须在本台账追加一行,
并确认模块名在中央 apps/pbls/scripts/load_path.py 的 MODULES 中(聚合式双写)。
═══════════════════════════════════════════════════════════════════════════════
"""
import os
import sys
MODULE = 'pbl_appcodes'
MODULE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
WWWROOT = os.path.join(MODULE_ROOT, 'wwwroot')
# ── any 层:无需登录即可访问的公共资源(本模块无)────────────────────────────
PATHS_ANY = []
# ── logined 层:模块首页 + api/*.dspy(与磁盘 wwwroot 一一对应,逐条显式)────
PATHS_LOGINED = [
'/{m}'.format(m=MODULE),
'/{m}/index.ui'.format(m=MODULE),
'/{m}/api/pbl_appcodes_inject.dspy'.format(m=MODULE),
'/{m}/api/pbl_appcodes_list.dspy'.format(m=MODULE),
]
# ── owner.* 层:当前无受限路径,显式声明为空(避免「漏登记」被误判为「待补」)─
PATHS_OWNER_SUPERUSER = []
PATHS_OWNER_OPERATOR = []
# ── 兼容扁平写法(中央聚合器两种写法都支持)────────────────────────────────
PATHS = [(p, 'logined') for p in PATHS_LOGINED]
# 本模块**有** Web 路径,故不声明 NO_WEB_PATHS(该常量仅用于纯库函数模块)
NO_WEB_PATHS = False
# ── 里程碑声明台账(供 --check 回显,也是 QC 可 grep 的硬证据)────────────────
MILESTONE_DECLARATIONS = [
{
'milestone': 'M11b-2',
'new_paths': 0,
'declaration': (
'M11b-2 本里程碑不新增 .dspy/.ui/.js/.css 路径,仅提供库函数'
'(world_sync / pbl_runtime_ext 的 *.py 与 models/*.json),'
'故无 RBAC 登记项;禁通配符(%/*),'
'如有路径须按 any/logined/owner.* 分层逐条登记。'
'(本模块存量 4 条路径于 T3 补齐登记,闭环「有文件无 RBAC」的 403 缺口。)'
),
},
]
FORBIDDEN_WILDCARDS = ('%', '*')
WEB_SUFFIXES = ('.dspy', '.ui', '.js', '.css')
def _tiers():
return (('any', PATHS_ANY),
('logined', PATHS_LOGINED),
('owner.superuser', PATHS_OWNER_SUPERUSER),
('owner.operator', PATHS_OWNER_OPERATOR))
def _assert_no_wildcard():
"""通配符硬门禁:任何一层路径出现 % 或 * 直接判失败(非静默)。"""
bad = [t + ':' + p for t, ps in _tiers() for p in ps
if any(w in p for w in FORBIDDEN_WILDCARDS)]
if bad:
print('[%s] FAIL 路径声明含禁用通配符: %s' % (MODULE, bad))
return 1
print('[%s] OK 通配符检查通过(PATHS_ANY/LOGINED/OWNER.* 均为逐条显式路径,无 %% / *)'
% MODULE)
return 0
def _declared_paths():
out = set()
for _t, ps in _tiers():
out.update(ps)
return out
def _disk_web_paths():
"""磁盘 wwwroot 下真实 Web 文件对应的 URL 路径集合(含模块根目录本身)。"""
found = set()
if os.path.isdir(WWWROOT):
for base, _dirs, files in os.walk(WWWROOT):
for fn in files:
if fn.endswith(WEB_SUFFIXES):
rel = os.path.relpath(os.path.join(base, fn), WWWROOT)
found.add('/' + MODULE + '/' + rel.replace(os.sep, '/'))
if found:
found.add('/' + MODULE) # 目录访问(走 index.ui)
return found
def _consistency():
"""声明 ↔ 磁盘 一致性门禁:缺登记或多登记都判 FAIL。"""
declared, on_disk = _declared_paths(), _disk_web_paths()
rc = 0
missing = sorted(on_disk - declared)
extra = sorted(declared - on_disk)
if missing:
print('[%s] FAIL 磁盘存在 Web 文件但未登记 RBAC(点击即 403): %s'
% (MODULE, missing))
rc = 1
else:
print('[%s] OK 磁盘 %d 个 Web 路径已全部登记,无遗漏' % (MODULE, len(on_disk)))
if extra:
print('[%s] FAIL 登记了磁盘上不存在的多余路径: %s' % (MODULE, extra))
rc = 1
return rc
def check():
print('=' * 78)
print('[%s] RBAC 路径声明自检 (--check) module=%s' % (MODULE, MODULE))
print('=' * 78)
rc = _assert_no_wildcard()
print('\n[声明台账]')
for d in MILESTONE_DECLARATIONS:
print(' - %s: 新增路径 %d 条 -> %s'
% (d['milestone'], d['new_paths'], d['declaration']))
print('\n[路径登记(逐条显式)]')
for tier, paths in _tiers():
print(' tier=%s count=%d' % (tier, len(paths)))
for p in paths:
print(' %s' % p)
rc |= _consistency()
total = len(_declared_paths())
print('\n[汇总] 登记路径合计 %d 条;M11b-2 新增 0 条(本次为存量补齐登记)' % total)
print('[%s] --check 结论: %s' % (MODULE, 'PASS' if rc == 0 else 'FAIL'))
return rc
def _find_sage_root():
for c in (os.path.expanduser('~/repos/sage'), os.path.expanduser('~/sage'),
'/d/ymq/repos/sage'):
if os.path.isdir(os.path.join(c, 'wwwroot')) and \
os.path.isdir(os.path.join(c, 'py3', 'bin')):
return c
return None
def main():
if '--check' in sys.argv[1:]:
return check()
root = _find_sage_root()
if not root:
print('[%s] 未找到 Sage 根目录,跳过(中央 apps/pbls/scripts/load_path.py 会聚合登记)'
% MODULE)
print('[%s] 本里程碑 M11b-2 无新路径登记;本次补齐的是存量 %d 条路径。'
% (MODULE, len(_declared_paths())))
print('[%s] 可执行 `python3 load_path.py --check` 离线校验声明合规性与磁盘一致性。'
% MODULE)
return 0
sys.path.insert(0, root)
try:
from set_role_perm import set_role_perm
except ImportError:
print('[%s] set_role_perm 不可用,请改用中央 apps/pbls/scripts/load_path.py' % MODULE)
return 0
rc = _assert_no_wildcard()
if rc != 0:
return rc
for tier, paths in _tiers():
for p in paths:
set_role_perm(p, tier)
print('[%s] 已注册 %d 条 RBAC 路径(M11b-2 新增 0 条,全部为存量补齐)'
% (MODULE, len(_declared_paths())))
return 0
if __name__ == '__main__':
sys.exit(main())