#!/usr/bin/env python3 # -*- coding: utf-8 -*- """RBAC 单条权限登记 CLI(M9-收口C,QC 退回意见 #10 方案 a)。 接口(与 scripts/load_path.py register() 的 subprocess 调用契约严格一致): python3 scripts/rbac_set_perm.py 例:python3 scripts/rbac_set_perm.py logined /pbl_scense_ext/api/pbl_overlay_bootstrap.dspy 退出码:rc=0 表示该 (role, path) 已幂等落库 permission + rolepermission; rc!=0 表示未落库(参数非法 / 路径不在种子清单 / DB 不可达)。 **不猜、不静默成功**——DB 不可达时返回 2,由 load_path.py 计入 pending, 使 `total=26 ok=26 pending=0` 只在真实落库时才成立。 实现下沉到 scripts/m9c_rbac_seed.py 的 --upsert-one(幂等 upsert,id 由 path/role 的 md5 派生,重复执行零差异),本文件只做 CLI 适配与参数校验, 不重复实现导入逻辑。 被 apps/pbls/build.sh 第 9 步(import_rp)经 load_path.register() 调用; RBAC_SET_PERM 环境变量可覆盖本工具路径(保持向后兼容)。 """ import os import re import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) SEED_TOOL = os.path.join(HERE, 'm9c_rbac_seed.py') MODULE = 'pbl_scense_ext' PSEUDO_ROLES = ('logined', 'any') WILDCARD_RE = re.compile(r'[%*?]') def main(argv): if len(argv) != 3: print('usage: rbac_set_perm.py (role in %s)' % (PSEUDO_ROLES,)) return 64 role, path = argv[1], argv[2] # 参数硬校验:与 module-development-spec 硬门禁 6.6 一致,通配符零容忍 if role not in PSEUDO_ROLES: print('FAIL 角色必须是伪角色 %s(rbac userperm.py 语义),收到 %s' % (PSEUDO_ROLES, role)) return 64 if WILDCARD_RE.search(path): print('FAIL 禁止通配符(硬政策): %s' % path) return 64 if not path.startswith('/%s' % MODULE): print('FAIL 路径必须落在模块自动路由 /%s 下: %s' % (MODULE, path)) return 64 if not os.path.isfile(SEED_TOOL): print('FAIL 缺少导入器 %s' % SEED_TOOL) return 66 cmd = [sys.executable, SEED_TOOL, '--upsert-one', role, path] if os.environ.get('PBL_SCENSE_EXT_DBNAME'): cmd += ['--dbname', os.environ['PBL_SCENSE_EXT_DBNAME']] rc = subprocess.call(cmd) if rc == 0: print('OK %-8s %s' % (role, path)) else: print('PENDING(rc=%d) %-8s %s' % (rc, role, path)) return rc if __name__ == '__main__': sys.exit(main(sys.argv))