89 lines
3.4 KiB
Python
89 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
||
"""RBAC path registration for ticket module (pipeline format).
|
||
|
||
从应用根执行:py3/bin/python pkgs/ticket/scripts/load_path.py
|
||
(load_path.sh 自动扫描 pkgs/*/scripts/load_path.py 并调用本脚本)
|
||
|
||
权限设计(设计文档 §7.2,逐个精确配不用统一兜底):
|
||
- logined:模块页面壳 + 全部 API。
|
||
⚠ staff 动作端点(claim/staff_reply/transfer/transfer_candidates/staff_tickets)
|
||
只能授 logined 而不能授固定角色——D4 决策:转派目标是 owner 组织类型下的
|
||
**任意动态角色**(role 表运行时可增删),RBAC 路径权限无法枚举动态角色,
|
||
业务权限由服务端 _check_staff_access 校验(操作者须持有工单 assignee_role,
|
||
admin/superuser 豁免)。客户侧数据由 I5 服务端自过滤(customer_user_id/org_id)。
|
||
- owner.maintainer / owner.admin / owner.superuser:管理页壳 /ticket/manage/**。
|
||
菜单「工单管理」入口也按角色 Jinja 分支渲染,客户直接访问 URL 403。
|
||
"""
|
||
import sys
|
||
import os
|
||
import asyncio
|
||
|
||
# 应用根:本脚本在 pkgs/ticket/scripts/ 下,上溯三级
|
||
APP_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../.."))
|
||
sys.path.insert(0, os.path.join(APP_ROOT, "py3", "lib", "python3.10", "site-packages"))
|
||
sys.path.insert(0, APP_ROOT)
|
||
|
||
from sqlor.dbpools import DBPools
|
||
from appPublic.jsonConfig import getConfig
|
||
from appPublic.uniqueID import getID
|
||
|
||
MOD = "ticket"
|
||
|
||
PATHS_LOGINED = [
|
||
# 客户侧页面
|
||
f"/{MOD}/my",
|
||
f"/{MOD}/my/index.ui",
|
||
f"/{MOD}/my/new_ticket_form.ui",
|
||
# 客户侧 API(I5 服务端自过滤)
|
||
f"/{MOD}/api/ticket_create.dspy",
|
||
f"/{MOD}/api/my_tickets.dspy",
|
||
f"/{MOD}/api/ticket_detail.dspy",
|
||
f"/{MOD}/api/ticket_detail_popup.dspy",
|
||
f"/{MOD}/api/ticket_followup.dspy",
|
||
f"/{MOD}/api/ticket_confirm.dspy",
|
||
f"/{MOD}/api/ticket_cancel.dspy",
|
||
# staff 侧 API(服务端 _check_staff_access 按工单 assignee_role 动态校验)
|
||
f"/{MOD}/api/staff_tickets.dspy",
|
||
f"/{MOD}/api/ticket_claim.dspy",
|
||
f"/{MOD}/api/ticket_staff_reply.dspy",
|
||
f"/{MOD}/api/ticket_transfer.dspy",
|
||
f"/{MOD}/api/transfer_candidates.dspy",
|
||
]
|
||
|
||
# 管理页壳:运维三角色
|
||
PATHS_STAFF = [
|
||
f"/{MOD}/manage",
|
||
f"/{MOD}/manage/index.ui",
|
||
]
|
||
|
||
STAFF_ROLES = ["owner.maintainer", "owner.admin", "owner.superuser"]
|
||
|
||
|
||
async def main():
|
||
config = getConfig(APP_ROOT, NS={"workdir": APP_ROOT})
|
||
DBPools(config.databases)
|
||
cnt = 0
|
||
async with DBPools().sqlorContext("pipeline") as sor:
|
||
pairs = [("logined", p) for p in PATHS_LOGINED]
|
||
for role in STAFF_ROLES:
|
||
pairs += [(role, p) for p in PATHS_STAFF]
|
||
for role, path in pairs:
|
||
recs = await sor.R("permission", {"path": path})
|
||
if recs:
|
||
permid = recs[0].id
|
||
else:
|
||
permid = getID()
|
||
await sor.C("permission", {"id": permid, "path": path})
|
||
rp = await sor.R("rolepermission", {"roleid": role, "permid": permid})
|
||
if not rp:
|
||
await sor.C("rolepermission", {
|
||
"id": getID(), "roleid": role, "permid": permid})
|
||
cnt += 1
|
||
await sor.sqlExe("COMMIT", {})
|
||
print(f"ticket load_path: {cnt} path-role entries ensured "
|
||
f"({len(PATHS_LOGINED)} logined + {len(PATHS_STAFF)}x{len(STAFF_ROLES)} staff)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main())
|