deliver: 交付收口(引擎代为提交)
This commit is contained in:
parent
309860bfd5
commit
5edf35f666
@ -2,97 +2,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_common 模块 RBAC / 路径声明脚本(per-module,中央层 apps/pbls 聚合)。
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
结论:本模块 **零 Web 可访问路径**(NO_WEB_PATHS = True,显式声明)
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
pbl_common 是 PBL 公共内核(租户上下文 / DB 适配 / 错误码 / 审计 / CRUD 工厂),
|
||||
只交付 Python 库函数(``pbl_common/*.py``),由宿主应用进程内 import 调用,
|
||||
**不提供任何 .dspy / .ui / .js / .css 文件**,因此 RBAC permission 表零登记项。
|
||||
|
||||
为什么写成显式声明而不是留空(M11b-2c-T3 QC 退回意见 #2 的连带治理):
|
||||
中央聚合器 apps/pbls/scripts/load_path.py 现在把「有 scripts/load_path.py 但
|
||||
一条路径都没聚合到」判为**聚合失败**(rc=1),除非模块显式声明 ``NO_WEB_PATHS``。
|
||||
「无路径」是事实断言,必须可 grep、可复核,不允许用留空表达。
|
||||
|
||||
用法:
|
||||
python3 load_path.py # 打印声明(零路径,无需登记)
|
||||
python3 load_path.py --check # 自检:无通配符 + 与磁盘 wwwroot 一致性
|
||||
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_common_audit.dspy + api/pbl_common_ping.dspy),
|
||||
即「文件在、路由在、RBAC 不在」= 点进去一律 403 的同类缺口。
|
||||
本次补齐**逐条显式**登记,并把「声明与磁盘一致」做进 --check 门禁:
|
||||
磁盘有 Web 文件而未登记 → rc=1;登记了而磁盘没有 → rc=1。
|
||||
铁律 = 禁通配符(% / *),所有路径逐条显式;新增路径须在本台账追加一行,
|
||||
并确认模块名在中央 apps/pbls/scripts/load_path.py 的 MODULES 中(聚合式双写)。
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
MODULE = 'pbl_common'
|
||||
MODULE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
WWWROOT = os.path.join(MODULE_ROOT, 'wwwroot')
|
||||
|
||||
# ── 显式零路径声明(中央聚合器据此判定「合法的空」而非「聚合失败」)──────────
|
||||
NO_WEB_PATHS = True
|
||||
DECLARATION = (
|
||||
'pbl_common 仅提供 Python 库函数(租户上下文/DB 适配/错误码/审计/CRUD 工厂),'
|
||||
'不新增任何 .dspy/.ui/.js/.css 可访问路径,故 RBAC 零登记项;'
|
||||
'禁通配符(%/*),后续若新增 Web 路径须按 any/logined/owner.* 分层逐条登记。'
|
||||
)
|
||||
|
||||
# 分层常量保持为空列表(显式,非缺失)
|
||||
# ── any 层:无需登录即可访问的公共资源(本模块无)────────────────────────────
|
||||
PATHS_ANY = []
|
||||
PATHS_LOGINED = []
|
||||
|
||||
# ── logined 层:模块首页 + api/*.dspy(与磁盘 wwwroot 一一对应,逐条显式)────
|
||||
PATHS_LOGINED = [
|
||||
'/{m}'.format(m=MODULE),
|
||||
'/{m}/index.ui'.format(m=MODULE),
|
||||
'/{m}/api/pbl_common_audit.dspy'.format(m=MODULE),
|
||||
'/{m}/api/pbl_common_ping.dspy'.format(m=MODULE),
|
||||
]
|
||||
|
||||
# ── owner.* 层:当前无受限路径,显式声明为空(避免「漏登记」被误判为「待补」)─
|
||||
PATHS_OWNER_SUPERUSER = []
|
||||
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 = ('%', '*')
|
||||
WEB_SUFFIXES = ('.dspy', '.ui', '.js', '.css')
|
||||
|
||||
|
||||
def _wwwroot_files():
|
||||
"""列出本模块 wwwroot 下真实存在的 Web 文件(.dspy/.ui/.js/.css)。"""
|
||||
root = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
'wwwroot')
|
||||
found = []
|
||||
for base, _dirs, files in os.walk(root):
|
||||
for fn in files:
|
||||
if fn.endswith(('.dspy', '.ui', '.js', '.css')):
|
||||
found.append(os.path.relpath(os.path.join(base, fn), root))
|
||||
return sorted(found)
|
||||
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)
|
||||
print(' NO_WEB_PATHS = %s' % NO_WEB_PATHS)
|
||||
print(' 声明: %s' % DECLARATION)
|
||||
rc = 0
|
||||
tiers = (('any', PATHS_ANY), ('logined', PATHS_LOGINED),
|
||||
('owner.superuser', PATHS_OWNER_SUPERUSER),
|
||||
('owner.operator', PATHS_OWNER_OPERATOR))
|
||||
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(' FAIL 含禁用通配符: %s' % bad)
|
||||
rc = 1
|
||||
else:
|
||||
print(' OK 通配符检查通过(无任何路径,自然无通配符)')
|
||||
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 文件,与零路径声明一致')
|
||||
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()
|
||||
print('[%s] %s' % (MODULE, DECLARATION))
|
||||
print('[%s] 无路径需要登记(NO_WEB_PATHS=True);可执行 `--check` 离线复核。' % MODULE)
|
||||
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
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user