security: 财务后台权限收敛到财务角色,堵住全平台余额/账务泄漏
- scripts/load_path.py: 按角色分层重写权限矩阵(any/logined/财务角色/分销商角色), 默认先清理 /accounting/ 全部旧授权再重建;财务后台(余额台账/账户/科目/分录/日志/ 信用管理/币种汇率)从 logined 收敛到 owner.superuser/admin/account + reseller.admin/accountant/operator;客户自服务页(已按本机构过滤)保留 logined - accounting/init.py get_accdetail: 补 account.orgid=当前机构 过滤,堵按 accountid 越权查明细 - wwwroot/get_user_balance.dspy: 改为只查登录用户本人余额(原可按任意 username 查他人余额, 且存在 swait 拼写错误导致 500)
This commit is contained in:
parent
9b905836f9
commit
89592b9585
@ -37,17 +37,18 @@ async def get_accdetail(request, accountid, page=1):
|
|||||||
env = request._run_ns
|
env = request._run_ns
|
||||||
userorgid = await env.get_userorgid()
|
userorgid = await env.get_userorgid()
|
||||||
async with get_sor_context(env, 'accounting') as sor:
|
async with get_sor_context(env, 'accounting') as sor:
|
||||||
|
# 数据隔离:只允许查询本机构名下账户的明细,防止按 accountid 越权枚举他人账务
|
||||||
sql = """select a.*,
|
sql = """select a.*,
|
||||||
c.name
|
c.name
|
||||||
from acc_detail a, account b, subject c
|
from acc_detail a, account b, subject c
|
||||||
where b.subjectid = c.id
|
where b.subjectid = c.id
|
||||||
and a.accountid = b.id
|
and a.accountid = b.id
|
||||||
and b.id = ${accountid}$
|
and b.id = ${accountid}$
|
||||||
|
and b.orgid = ${orgid}$
|
||||||
"""
|
"""
|
||||||
ns = {
|
ns = {
|
||||||
'accountid': accountid,
|
'accountid': accountid,
|
||||||
'page': page,
|
'orgid': userorgid
|
||||||
'sort': 'acc_date desc'
|
|
||||||
}
|
}
|
||||||
ret = await sor.sqlExe(sql, ns)
|
ret = await sor.sqlExe(sql, ns)
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@ -1,22 +1,44 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
accounting 模块 RBAC 权限管理脚本
|
accounting 模块 RBAC 权限管理脚本(角色分层版,2026-08-27 重构)
|
||||||
|
|
||||||
|
重构原因(安全修复):
|
||||||
|
旧版把整个财务后台(余额台账/账户/科目/分录/日志/信用/币种/汇率的增删改查)
|
||||||
|
全部注册给 logined 角色,任何注册用户可见/可改/可删全平台余额与账务数据。
|
||||||
|
现按角色分层:
|
||||||
|
- any 免登录静态资源(菜单片段/图标)+ 带 localhost 校验的定时任务入口
|
||||||
|
- logined 客户自服务页面(后端均已按本人/本机构过滤:我的账户、账务明细、账单、我的信用额度)
|
||||||
|
- 财务角色 财务后台全部 CRUD(余额台账、账户、科目、分录配置、日志、信用管理、币种汇率)
|
||||||
|
- 分销商角色 代客充值(含按用户名查询客户余额,属分销商业务需要)
|
||||||
|
|
||||||
|
行为:
|
||||||
|
默认先清理 /accounting/ 下全部旧授权(rolepermission),再按新矩阵重新注册。
|
||||||
|
(旧授权里 logined、customer.customer 等角色持有财务后台增删改权限,不清理则泄漏依旧)
|
||||||
|
--add-only 跳过清理,仅增量注册(新路径/新宿主接入时用)
|
||||||
|
|
||||||
|
角色解析:
|
||||||
|
权限缓存按 role.id 关联(rbac/userperm.py load_roleperms),同名角色在不同宿主
|
||||||
|
的 role.id 可能不同(如 'owner.superuser' 字面量 vs 随机 id),因此本脚本
|
||||||
|
用 orgtypeid+name 查 role 表解析出真实 role.id 后注册,同名多条全部注册。
|
||||||
|
|
||||||
使用方法:
|
使用方法:
|
||||||
cd ~/repos/sage
|
cd <app root>(含 py3/ 与 set_role_perm.py 的目录,如 /d/pipeline/pipeline-app)
|
||||||
./py3/bin/python ~/repos/accounting/scripts/load_path.py
|
./py3/bin/python pkgs/accounting/scripts/load_path.py [--add-only]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
def find_sage_root():
|
def find_app_root():
|
||||||
candidates = [
|
candidates = [
|
||||||
os.path.expanduser("~/repos/sage"),
|
os.path.expanduser("~/repos/sage"),
|
||||||
os.path.expanduser("~/sage"),
|
os.path.expanduser("~/sage"),
|
||||||
|
# pkgs/accounting/scripts/load_path.py 的上三级 = 应用根目录
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
||||||
|
os.getcwd(),
|
||||||
]
|
]
|
||||||
for c in candidates:
|
for c in candidates:
|
||||||
if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")):
|
if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")):
|
||||||
@ -24,43 +46,84 @@ def find_sage_root():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
SAGE_ROOT = find_sage_root()
|
APP_ROOT = find_app_root()
|
||||||
if not SAGE_ROOT:
|
if not APP_ROOT:
|
||||||
print("ERROR: Cannot find Sage root directory")
|
print("ERROR: Cannot find app root directory")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
PYTHON = os.path.join(SAGE_ROOT, "py3", "bin", "python")
|
PYTHON = os.path.join(APP_ROOT, "py3", "bin", "python")
|
||||||
SET_PERM_SCRIPT = os.path.join(SAGE_ROOT, "set_role_perm.py")
|
SET_PERM_SCRIPT = os.path.join(APP_ROOT, "set_role_perm.py")
|
||||||
|
|
||||||
MOD = "accounting"
|
MOD = "accounting"
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 权限路径定义
|
# 角色定义(标签格式 <orgtypeid>.<name>,运行时解析为真实 role.id)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
# any — 无需登录
|
# 财务后台管理角色:平台方超管/管理员/会计 + 分销商管理员/会计/运营
|
||||||
|
FIN_ROLES = [
|
||||||
|
"owner.superuser",
|
||||||
|
"owner.admin",
|
||||||
|
"owner.account",
|
||||||
|
"reseller.admin",
|
||||||
|
"reseller.accountant",
|
||||||
|
"reseller.operator",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 代客充值角色:分销商侧可代客户充值(需按用户名查询客户账户,属业务需要)
|
||||||
|
PROXY_ROLES = [
|
||||||
|
"reseller.admin",
|
||||||
|
"reseller.accountant",
|
||||||
|
"reseller.operator",
|
||||||
|
"reseller.sale",
|
||||||
|
]
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 权限路径矩阵
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# any — 无需登录(静态资源 + 自带 localhost 校验的定时任务入口)
|
||||||
PATHS_ANY = [
|
PATHS_ANY = [
|
||||||
f"/{MOD}/usermenu.ui",
|
f"/{MOD}/usermenu.ui",
|
||||||
|
f"/{MOD}/imgs/accbalance.svg",
|
||||||
|
f"/{MOD}/imgs/accdetail.svg",
|
||||||
|
f"/{MOD}/imgs/account.svg",
|
||||||
|
f"/{MOD}/imgs/bill.svg",
|
||||||
|
# 定时任务入口(build.sh 注入的 cron 裸 curl 无会话,必须 any;
|
||||||
|
# dspy 内部已做 client_ip=localhost 校验,nginx XFF 追加模式伪造不成立)
|
||||||
f"/{MOD}/api/fetch_forex_rates.dspy",
|
f"/{MOD}/api/fetch_forex_rates.dspy",
|
||||||
]
|
]
|
||||||
|
|
||||||
# logined — 所有已登录用户
|
# logined — 客户自服务(后端查询均已按当前用户/本机构过滤)
|
||||||
PATHS_LOGINED = [
|
PATHS_LOGINED = [
|
||||||
|
f"/{MOD}/myaccounts.ui",
|
||||||
|
f"/{MOD}/myaccounts.dspy",
|
||||||
|
f"/{MOD}/mybalance.dspy",
|
||||||
|
f"/{MOD}/accdetail.ui",
|
||||||
|
f"/{MOD}/accdetail.dspy",
|
||||||
|
f"/{MOD}/billing.ui",
|
||||||
|
f"/{MOD}/billing.dspy",
|
||||||
|
f"/{MOD}/billing_download.dspy",
|
||||||
|
f"/{MOD}/get_user_balance.dspy",
|
||||||
|
f"/{MOD}/open_customer_accounts_with_orgid.dspy",
|
||||||
|
f"/{MOD}/error_accounting.ui",
|
||||||
|
# 我的信用额度(查询按本机构过滤)
|
||||||
|
f"/{MOD}/credit_limit/credit_overview.ui",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 财务后台 — 仅财务角色
|
||||||
|
PATHS_FIN = [
|
||||||
# 模块入口
|
# 模块入口
|
||||||
f"/{MOD}",
|
f"/{MOD}",
|
||||||
f"/{MOD}/index.ui",
|
f"/{MOD}/index.ui",
|
||||||
|
|
||||||
# 顶层 .ui 页面
|
# 统计卡片(按机构汇总的财务看板)
|
||||||
f"/{MOD}/myaccounts.ui",
|
f"/{MOD}/stat_total_balance.ui",
|
||||||
f"/{MOD}/accdetail.ui",
|
f"/{MOD}/stat_today_consumption.ui",
|
||||||
|
f"/{MOD}/stat_month_consumption.ui",
|
||||||
|
f"/{MOD}/stat_account_count.ui",
|
||||||
|
|
||||||
# 顶层 .dspy
|
# 开户管理(平台/分销商侧操作,可指定任意 orgid)
|
||||||
f"/{MOD}/accdetail.dspy",
|
|
||||||
f"/{MOD}/billing.dspy",
|
|
||||||
f"/{MOD}/billing_download.dspy",
|
|
||||||
f"/{MOD}/get_user_balance.dspy",
|
|
||||||
f"/{MOD}/myaccounts.dspy",
|
|
||||||
f"/{MOD}/mybalance.dspy",
|
|
||||||
f"/{MOD}/oca.dspy",
|
f"/{MOD}/oca.dspy",
|
||||||
f"/{MOD}/open_customer_accounts.dspy",
|
f"/{MOD}/open_customer_accounts.dspy",
|
||||||
f"/{MOD}/open_owner_accounts.dspy",
|
f"/{MOD}/open_owner_accounts.dspy",
|
||||||
@ -68,128 +131,223 @@ PATHS_LOGINED = [
|
|||||||
f"/{MOD}/open_reseller_accounts.dspy",
|
f"/{MOD}/open_reseller_accounts.dspy",
|
||||||
f"/{MOD}/open_reseller_provider_accounts.dspy",
|
f"/{MOD}/open_reseller_provider_accounts.dspy",
|
||||||
|
|
||||||
# 统计卡片 .ui
|
# acc_balance/(日终余额台账,全平台数据,无 orgid 列)
|
||||||
f"/{MOD}/stat_total_balance.ui",
|
|
||||||
f"/{MOD}/stat_today_consumption.ui",
|
|
||||||
f"/{MOD}/stat_month_consumption.ui",
|
|
||||||
f"/{MOD}/stat_account_count.ui",
|
|
||||||
|
|
||||||
# acc_balance/
|
|
||||||
f"/{MOD}/acc_balance/index.ui",
|
f"/{MOD}/acc_balance/index.ui",
|
||||||
f"/{MOD}/acc_balance/get_acc_balance.dspy",
|
f"/{MOD}/acc_balance/get_acc_balance.dspy",
|
||||||
f"/{MOD}/acc_balance/add_acc_balance.dspy",
|
f"/{MOD}/acc_balance/add_acc_balance.dspy",
|
||||||
f"/{MOD}/acc_balance/update_acc_balance.dspy",
|
f"/{MOD}/acc_balance/update_acc_balance.dspy",
|
||||||
f"/{MOD}/acc_balance/delete_acc_balance.dspy",
|
f"/{MOD}/acc_balance/delete_acc_balance.dspy",
|
||||||
|
|
||||||
# acc_detail/
|
# acc_detail/(记账明细台账)
|
||||||
f"/{MOD}/acc_detail/index.ui",
|
f"/{MOD}/acc_detail/index.ui",
|
||||||
f"/{MOD}/acc_detail/get_acc_detail.dspy",
|
f"/{MOD}/acc_detail/get_acc_detail.dspy",
|
||||||
f"/{MOD}/acc_detail/add_acc_detail.dspy",
|
f"/{MOD}/acc_detail/add_acc_detail.dspy",
|
||||||
f"/{MOD}/acc_detail/update_acc_detail.dspy",
|
f"/{MOD}/acc_detail/update_acc_detail.dspy",
|
||||||
f"/{MOD}/acc_detail/delete_acc_detail.dspy",
|
f"/{MOD}/acc_detail/delete_acc_detail.dspy",
|
||||||
|
|
||||||
# account/
|
# account/(账户管理)
|
||||||
f"/{MOD}/account/index.ui",
|
f"/{MOD}/account/index.ui",
|
||||||
f"/{MOD}/account/get_account.dspy",
|
f"/{MOD}/account/get_account.dspy",
|
||||||
f"/{MOD}/account/add_account.dspy",
|
f"/{MOD}/account/add_account.dspy",
|
||||||
f"/{MOD}/account/update_account.dspy",
|
f"/{MOD}/account/update_account.dspy",
|
||||||
f"/{MOD}/account/delete_account.dspy",
|
f"/{MOD}/account/delete_account.dspy",
|
||||||
|
|
||||||
# account_config/
|
# account_config/(参与方科目配置)
|
||||||
f"/{MOD}/account_config/index.ui",
|
f"/{MOD}/account_config/index.ui",
|
||||||
f"/{MOD}/account_config/get_account_config.dspy",
|
f"/{MOD}/account_config/get_account_config.dspy",
|
||||||
f"/{MOD}/account_config/add_account_config.dspy",
|
f"/{MOD}/account_config/add_account_config.dspy",
|
||||||
f"/{MOD}/account_config/update_account_config.dspy",
|
f"/{MOD}/account_config/update_account_config.dspy",
|
||||||
f"/{MOD}/account_config/delete_account_config.dspy",
|
f"/{MOD}/account_config/delete_account_config.dspy",
|
||||||
|
|
||||||
# accounting_config/
|
# accounting_config/(会计分录配置)
|
||||||
f"/{MOD}/accounting_config/index.ui",
|
f"/{MOD}/accounting_config/index.ui",
|
||||||
f"/{MOD}/accounting_config/get_accounting_config.dspy",
|
f"/{MOD}/accounting_config/get_accounting_config.dspy",
|
||||||
f"/{MOD}/accounting_config/add_accounting_config.dspy",
|
f"/{MOD}/accounting_config/add_accounting_config.dspy",
|
||||||
f"/{MOD}/accounting_config/update_accounting_config.dspy",
|
f"/{MOD}/accounting_config/update_accounting_config.dspy",
|
||||||
f"/{MOD}/accounting_config/delete_accounting_config.dspy",
|
f"/{MOD}/accounting_config/delete_accounting_config.dspy",
|
||||||
|
|
||||||
# accounting_log/
|
# accounting_log/(记账日志)
|
||||||
f"/{MOD}/accounting_log/index.ui",
|
f"/{MOD}/accounting_log/index.ui",
|
||||||
f"/{MOD}/accounting_log/get_accounting_log.dspy",
|
f"/{MOD}/accounting_log/get_accounting_log.dspy",
|
||||||
f"/{MOD}/accounting_log/add_accounting_log.dspy",
|
f"/{MOD}/accounting_log/add_accounting_log.dspy",
|
||||||
f"/{MOD}/accounting_log/update_accounting_log.dspy",
|
f"/{MOD}/accounting_log/update_accounting_log.dspy",
|
||||||
f"/{MOD}/accounting_log/delete_accounting_log.dspy",
|
f"/{MOD}/accounting_log/delete_accounting_log.dspy",
|
||||||
|
|
||||||
# subject/
|
# subject/(科目)
|
||||||
f"/{MOD}/subject/index.ui",
|
f"/{MOD}/subject/index.ui",
|
||||||
f"/{MOD}/subject/get_subject.dspy",
|
f"/{MOD}/subject/get_subject.dspy",
|
||||||
f"/{MOD}/subject/add_subject.dspy",
|
f"/{MOD}/subject/add_subject.dspy",
|
||||||
f"/{MOD}/subject/update_subject.dspy",
|
f"/{MOD}/subject/update_subject.dspy",
|
||||||
f"/{MOD}/subject/delete_subject.dspy",
|
f"/{MOD}/subject/delete_subject.dspy",
|
||||||
|
|
||||||
# credit_limit/
|
# credit_limit/(信用额度管理侧;客户自服务的 hub/overview 在 logined 组)
|
||||||
f"/{MOD}/credit_limit/index.ui",
|
f"/{MOD}/credit_limit/index.ui",
|
||||||
f"/{MOD}/credit_limit/get_credit_limit.dspy",
|
f"/{MOD}/credit_limit/get_credit_limit.dspy",
|
||||||
f"/{MOD}/credit_limit/add_credit_limit.dspy",
|
f"/{MOD}/credit_limit/add_credit_limit.dspy",
|
||||||
f"/{MOD}/credit_limit/update_credit_limit.dspy",
|
f"/{MOD}/credit_limit/update_credit_limit.dspy",
|
||||||
f"/{MOD}/credit_limit/delete_credit_limit.dspy",
|
f"/{MOD}/credit_limit/delete_credit_limit.dspy",
|
||||||
f"/{MOD}/credit_limit/hub.ui",
|
|
||||||
f"/{MOD}/credit_limit/credit_manage.ui",
|
f"/{MOD}/credit_limit/credit_manage.ui",
|
||||||
f"/{MOD}/credit_limit/credit_overview.ui",
|
|
||||||
f"/{MOD}/credit_limit/api/credit_summary.dspy",
|
f"/{MOD}/credit_limit/api/credit_summary.dspy",
|
||||||
f"/{MOD}/credit_limit/api/set_credit_form.ui",
|
f"/{MOD}/credit_limit/api/set_credit_form.ui",
|
||||||
f"/{MOD}/credit_limit/api/set_customer_credit.dspy",
|
f"/{MOD}/credit_limit/api/set_customer_credit.dspy",
|
||||||
|
|
||||||
# currency_list/
|
# currency/ + exchange_rate/(币种与汇率管理,目录名以 build 生成的 tblname 为准)
|
||||||
f"/{MOD}/currency_list/index.ui",
|
f"/{MOD}/currency/index.ui",
|
||||||
f"/{MOD}/currency_list/get_currency_list.dspy",
|
f"/{MOD}/currency/get_currency.dspy",
|
||||||
f"/{MOD}/currency_list/add_currency_list.dspy",
|
f"/{MOD}/currency/add_currency.dspy",
|
||||||
f"/{MOD}/currency_list/update_currency_list.dspy",
|
f"/{MOD}/currency/update_currency.dspy",
|
||||||
f"/{MOD}/currency_list/delete_currency_list.dspy",
|
f"/{MOD}/currency/delete_currency.dspy",
|
||||||
|
f"/{MOD}/exchange_rate/index.ui",
|
||||||
|
f"/{MOD}/exchange_rate/get_exchange_rate.dspy",
|
||||||
|
f"/{MOD}/exchange_rate/add_exchange_rate.dspy",
|
||||||
|
f"/{MOD}/exchange_rate/update_exchange_rate.dspy",
|
||||||
|
f"/{MOD}/exchange_rate/delete_exchange_rate.dspy",
|
||||||
|
|
||||||
# exchange_rate_list/
|
# api(币种/汇率手工维护入口)
|
||||||
f"/{MOD}/exchange_rate_list/index.ui",
|
|
||||||
f"/{MOD}/exchange_rate_list/get_exchange_rate_list.dspy",
|
|
||||||
f"/{MOD}/exchange_rate_list/add_exchange_rate_list.dspy",
|
|
||||||
f"/{MOD}/exchange_rate_list/update_exchange_rate_list.dspy",
|
|
||||||
f"/{MOD}/exchange_rate_list/delete_exchange_rate_list.dspy",
|
|
||||||
|
|
||||||
# api (manual DSPY)
|
|
||||||
f"/{MOD}/api/currency_create.dspy",
|
f"/{MOD}/api/currency_create.dspy",
|
||||||
f"/{MOD}/api/currency_update.dspy",
|
f"/{MOD}/api/currency_update.dspy",
|
||||||
f"/{MOD}/api/currency_delete.dspy",
|
f"/{MOD}/api/currency_delete.dspy",
|
||||||
f"/{MOD}/api/exchange_rate_create.dspy",
|
f"/{MOD}/api/exchange_rate_create.dspy",
|
||||||
f"/{MOD}/api/exchange_rate_update.dspy",
|
f"/{MOD}/api/exchange_rate_update.dspy",
|
||||||
f"/{MOD}/api/exchange_rate_delete.dspy",
|
f"/{MOD}/api/exchange_rate_delete.dspy",
|
||||||
|
]
|
||||||
|
|
||||||
# proxy_recharge/
|
# 代客充值 — 仅分销商角色
|
||||||
|
PATHS_PROXY = [
|
||||||
f"/{MOD}/proxy_recharge.ui",
|
f"/{MOD}/proxy_recharge.ui",
|
||||||
f"/{MOD}/proxy_recharge_submit.dspy",
|
f"/{MOD}/proxy_recharge_submit.dspy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 执行注册
|
# 数据库辅助(角色解析 + 旧授权清理)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
|
def _get_db():
|
||||||
def run_set_perm(role, path):
|
sys.path.insert(0, APP_ROOT)
|
||||||
cmd = [PYTHON, SET_PERM_SCRIPT, role, path]
|
from sqlor.dbpools import DBPools
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
from appPublic.jsonConfig import getConfig
|
||||||
return result.returncode == 0
|
config = getConfig(APP_ROOT, NS={'workdir': APP_ROOT})
|
||||||
|
return DBPools(config.databases)
|
||||||
|
|
||||||
|
|
||||||
def register_role_paths(role, paths):
|
def _row_get(row, key):
|
||||||
|
try:
|
||||||
|
return row[key]
|
||||||
|
except Exception:
|
||||||
|
return getattr(row, key, None)
|
||||||
|
|
||||||
|
|
||||||
|
async def _find_rbac_db(db):
|
||||||
|
"""遍历 config.databases,找到含 role 表的库(pipeline 应用是 pipeline 库,sage 是 sage 库)。"""
|
||||||
|
names = list(db.databases.keys()) if hasattr(db, 'databases') else []
|
||||||
|
for dbname in names:
|
||||||
|
try:
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.sqlExe('select count(*) as c from role', {})
|
||||||
|
await sor.sqlExe('COMMIT', {})
|
||||||
|
return dbname
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def resolve_role_ids(labels):
|
||||||
|
"""标签 'orgtypeid.name' → 真实 role.id 列表(同名多条全返回)。"""
|
||||||
|
db = _get_db()
|
||||||
|
dbname = await _find_rbac_db(db)
|
||||||
|
mapping = {}
|
||||||
|
if not dbname:
|
||||||
|
print('WARN: role 表不可达,按字面量注册(可能无效)')
|
||||||
|
return {lb: [lb] for lb in labels}
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
for label in labels:
|
||||||
|
if label in ('any', 'logined', 'anonymous'):
|
||||||
|
mapping[label] = [label]
|
||||||
|
continue
|
||||||
|
orgtypeid, _, name = label.partition('.')
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
'select id from role where orgtypeid=${o}$ and name=${n}$',
|
||||||
|
{'o': orgtypeid, 'n': name})
|
||||||
|
ids = [_row_get(r, 'id') for r in recs]
|
||||||
|
ids = [i for i in ids if i]
|
||||||
|
if not ids:
|
||||||
|
print(f'WARN: 角色不存在 {label},跳过')
|
||||||
|
mapping[label] = ids
|
||||||
|
await sor.sqlExe('COMMIT', {})
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
async def clean_accounting_roleperms():
|
||||||
|
"""删除 /accounting/ 下全部旧授权(保留 permission 行本身)。"""
|
||||||
|
db = _get_db()
|
||||||
|
dbname = await _find_rbac_db(db)
|
||||||
|
if not dbname:
|
||||||
|
print('ERROR: 无法定位 RBAC 库,清理中止')
|
||||||
|
return -1
|
||||||
|
total = 0
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
perms = await sor.sqlExe(
|
||||||
|
'select id from permission where path like ${p}$ or path=${p2}$',
|
||||||
|
{'p': f'/{MOD}/%', 'p2': f'/{MOD}'})
|
||||||
|
for p in perms:
|
||||||
|
pid = _row_get(p, 'id')
|
||||||
|
r = await sor.sqlExe('delete from rolepermission where permid=${pid}$', {'pid': pid})
|
||||||
|
total += 1
|
||||||
|
await sor.sqlExe('COMMIT', {})
|
||||||
|
print(f'清理完成:/accounting/ 下 {len(perms)} 个 permission 的旧授权已删除')
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 注册
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def run_set_perm(role_id, path):
|
||||||
|
cmd = [PYTHON, SET_PERM_SCRIPT, role_id, path]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, cwd=APP_ROOT)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f" FAIL {role_id} {path}: {(result.stderr or result.stdout).strip()[:200]}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def register_role_paths(role_label, role_ids, paths):
|
||||||
|
if not role_ids:
|
||||||
|
return 0
|
||||||
count = 0
|
count = 0
|
||||||
for p in paths:
|
for rid in role_ids:
|
||||||
if run_set_perm(role, p):
|
for p in paths:
|
||||||
count += 1
|
if run_set_perm(rid, p):
|
||||||
print(f" {role}: {count}/{len(paths)} paths registered")
|
count += 1
|
||||||
|
print(f" {role_label} ({','.join(role_ids)}): {count}/{len(paths) * len(role_ids)} entries registered")
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print(f"Sage root: {SAGE_ROOT}")
|
add_only = '--add-only' in sys.argv
|
||||||
|
print(f"App root: {APP_ROOT}")
|
||||||
|
|
||||||
|
if not add_only:
|
||||||
|
print('=== 清理旧授权 ===')
|
||||||
|
asyncio.run(clean_accounting_roleperms())
|
||||||
|
|
||||||
|
print('=== 解析角色 ===')
|
||||||
|
all_labels = sorted(set(FIN_ROLES + PROXY_ROLES))
|
||||||
|
role_map = asyncio.run(resolve_role_ids(all_labels))
|
||||||
|
for lb in all_labels:
|
||||||
|
print(f' {lb} -> {role_map.get(lb) or "(不存在)"}')
|
||||||
|
|
||||||
|
print('=== 注册新矩阵 ===')
|
||||||
total = 0
|
total = 0
|
||||||
total += register_role_paths("any", PATHS_ANY)
|
total += register_role_paths('any', ['any'], PATHS_ANY)
|
||||||
total += register_role_paths("logined", PATHS_LOGINED)
|
total += register_role_paths('logined', ['logined'], PATHS_LOGINED)
|
||||||
|
for label in FIN_ROLES:
|
||||||
|
total += register_role_paths(label, role_map.get(label, []), PATHS_FIN)
|
||||||
|
for label in PROXY_ROLES:
|
||||||
|
total += register_role_paths(label, role_map.get(label, []), PATHS_PROXY)
|
||||||
print(f"\nDone. Total {total} permission entries registered.")
|
print(f"\nDone. Total {total} permission entries registered.")
|
||||||
print("NOTE: Restart Sage after permission changes to reload RBAC cache.")
|
print("NOTE: 重启应用(或调用 /rbac/refresh_userperm.dspy)后权限生效。")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
username= params_kw.username
|
# 安全:只允许查询登录用户本人的余额。
|
||||||
|
# 历史问题:外部传任意 username 即可查询他人余额(且原文件有 swait 拼写错误,一调用就 500)。
|
||||||
|
userid = await get_user()
|
||||||
env = request._run_ns
|
env = request._run_ns
|
||||||
async with get_sor_context(env, 'accounting') as sor:
|
async with get_sor_context(env, 'accounting') as sor:
|
||||||
sql = """select
|
sql = """select
|
||||||
@ -8,12 +10,9 @@ a.balance
|
|||||||
from account a, subject c, users d
|
from account a, subject c, users d
|
||||||
where a.orgid = d.orgid
|
where a.orgid = d.orgid
|
||||||
and a.subjectid = c.id
|
and a.subjectid = c.id
|
||||||
and d.username = ${username}$
|
and d.id = ${userid}$
|
||||||
"""
|
"""
|
||||||
recs = swait or.sqlExe(sql, {
|
recs = await sor.sqlExe(sql, {'userid': userid})
|
||||||
'username': username,
|
|
||||||
'sort': 'username'
|
|
||||||
})
|
|
||||||
return {
|
return {
|
||||||
'status': 'ok',
|
'status': 'ok',
|
||||||
'data': recs
|
'data': recs
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user