deliver: 交付收口(引擎代为提交)
This commit is contained in:
parent
a17330c395
commit
c787b2581c
@ -2,92 +2,186 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""pbl_appcodes 模块 RBAC / 路径声明脚本(per-module,中央层 apps/pbls 聚合)。
|
"""pbl_appcodes 模块 RBAC / 路径声明脚本(per-module,中央层 apps/pbls 聚合)。
|
||||||
|
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
|
||||||
结论:本模块 **零 Web 可访问路径**(NO_WEB_PATHS = True,显式声明)
|
|
||||||
═══════════════════════════════════════════════════════════════════════════════
|
|
||||||
pbl_appcodes 只做枚举编码注入(appcodes / appcodes_kv 种子,幂等,零自有表),
|
|
||||||
只交付 Python 库函数(``pbl_appcodes/*.py``)与 init 种子数据,
|
|
||||||
**不提供任何 .dspy / .ui / .js / .css 文件**,因此 RBAC permission 表零登记项。
|
|
||||||
|
|
||||||
为什么写成显式声明而不是留空(M11b-2c-T3 QC 退回意见 #2 的连带治理):
|
|
||||||
中央聚合器把「有 scripts/load_path.py 但一条路径都没聚合到」判为聚合失败
|
|
||||||
(rc=1),除非模块显式声明 ``NO_WEB_PATHS``。「无路径」是事实断言,须可 grep。
|
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
python3 load_path.py # 打印声明(零路径,无需登记)
|
python3 load_path.py # 找到 Sage 根时逐条 set_role_perm 登记;找不到则打印跳过说明
|
||||||
python3 load_path.py --check # 自检:无通配符 + 与磁盘 wwwroot 一致性
|
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 os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
MODULE = 'pbl_appcodes'
|
MODULE = 'pbl_appcodes'
|
||||||
|
MODULE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
WWWROOT = os.path.join(MODULE_ROOT, 'wwwroot')
|
||||||
|
|
||||||
NO_WEB_PATHS = True
|
# ── any 层:无需登录即可访问的公共资源(本模块无)────────────────────────────
|
||||||
DECLARATION = (
|
|
||||||
'pbl_appcodes 仅提供枚举编码注入(库函数 + init 种子数据,零自有表),'
|
|
||||||
'不新增任何 .dspy/.ui/.js/.css 可访问路径,故 RBAC 零登记项;'
|
|
||||||
'禁通配符(%/*),后续若新增 Web 路径须按 any/logined/owner.* 分层逐条登记。'
|
|
||||||
)
|
|
||||||
|
|
||||||
PATHS_ANY = []
|
PATHS_ANY = []
|
||||||
PATHS_LOGINED = []
|
|
||||||
|
# ── 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_SUPERUSER = []
|
||||||
PATHS_OWNER_OPERATOR = []
|
PATHS_OWNER_OPERATOR = []
|
||||||
PATHS = []
|
|
||||||
|
# ── 兼容扁平写法(中央聚合器两种写法都支持)────────────────────────────────
|
||||||
|
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 = ('%', '*')
|
FORBIDDEN_WILDCARDS = ('%', '*')
|
||||||
|
WEB_SUFFIXES = ('.dspy', '.ui', '.js', '.css')
|
||||||
|
|
||||||
|
|
||||||
def _wwwroot_files():
|
def _tiers():
|
||||||
root = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
return (('any', PATHS_ANY),
|
||||||
'wwwroot')
|
('logined', PATHS_LOGINED),
|
||||||
found = []
|
('owner.superuser', PATHS_OWNER_SUPERUSER),
|
||||||
for base, _dirs, files in os.walk(root):
|
('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:
|
for fn in files:
|
||||||
if fn.endswith(('.dspy', '.ui', '.js', '.css')):
|
if fn.endswith(WEB_SUFFIXES):
|
||||||
found.append(os.path.relpath(os.path.join(base, fn), root))
|
rel = os.path.relpath(os.path.join(base, fn), WWWROOT)
|
||||||
return sorted(found)
|
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():
|
def check():
|
||||||
print('=' * 78)
|
print('=' * 78)
|
||||||
print('[%s] RBAC 路径声明自检 (--check) module=%s' % (MODULE, MODULE))
|
print('[%s] RBAC 路径声明自检 (--check) module=%s' % (MODULE, MODULE))
|
||||||
print('=' * 78)
|
print('=' * 78)
|
||||||
print(' NO_WEB_PATHS = %s' % NO_WEB_PATHS)
|
rc = _assert_no_wildcard()
|
||||||
print(' 声明: %s' % DECLARATION)
|
print('\n[声明台账]')
|
||||||
rc = 0
|
for d in MILESTONE_DECLARATIONS:
|
||||||
tiers = (('any', PATHS_ANY), ('logined', PATHS_LOGINED),
|
print(' - %s: 新增路径 %d 条 -> %s'
|
||||||
('owner.superuser', PATHS_OWNER_SUPERUSER),
|
% (d['milestone'], d['new_paths'], d['declaration']))
|
||||||
('owner.operator', PATHS_OWNER_OPERATOR))
|
print('\n[路径登记(逐条显式)]')
|
||||||
bad = [t + ':' + p for t, ps in tiers for p in ps
|
for tier, paths in _tiers():
|
||||||
if any(w in p for w in FORBIDDEN_WILDCARDS)]
|
print(' tier=%s count=%d' % (tier, len(paths)))
|
||||||
if bad:
|
for p in paths:
|
||||||
print(' FAIL 含禁用通配符: %s' % bad)
|
print(' %s' % p)
|
||||||
rc = 1
|
rc |= _consistency()
|
||||||
else:
|
total = len(_declared_paths())
|
||||||
print(' OK 通配符检查通过(无任何路径,自然无通配符)')
|
print('\n[汇总] 登记路径合计 %d 条;M11b-2 新增 0 条(本次为存量补齐登记)' % total)
|
||||||
total = sum(len(ps) for _t, ps in tiers)
|
|
||||||
if total:
|
|
||||||
print(' FAIL NO_WEB_PATHS=True 但声明了 %d 条路径(自相矛盾)' % total)
|
|
||||||
rc = 1
|
|
||||||
else:
|
|
||||||
print(' OK 声明路径合计 0 条,与 NO_WEB_PATHS=True 一致')
|
|
||||||
on_disk = _wwwroot_files()
|
|
||||||
if on_disk:
|
|
||||||
print(' FAIL 磁盘 wwwroot 存在 %d 个 Web 文件却未登记: %s'
|
|
||||||
% (len(on_disk), on_disk))
|
|
||||||
rc = 1
|
|
||||||
else:
|
|
||||||
print(' OK 磁盘 wwwroot 无 .dspy/.ui/.js/.css 文件,与零路径声明一致')
|
|
||||||
print('[%s] --check 结论: %s' % (MODULE, 'PASS' if rc == 0 else 'FAIL'))
|
print('[%s] --check 结论: %s' % (MODULE, 'PASS' if rc == 0 else 'FAIL'))
|
||||||
return rc
|
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():
|
def main():
|
||||||
if '--check' in sys.argv[1:]:
|
if '--check' in sys.argv[1:]:
|
||||||
return check()
|
return check()
|
||||||
print('[%s] %s' % (MODULE, DECLARATION))
|
root = _find_sage_root()
|
||||||
print('[%s] 无路径需要登记(NO_WEB_PATHS=True);可执行 `--check` 离线复核。' % MODULE)
|
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
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user