新增
This commit is contained in:
+228
-12
@@ -1,6 +1,6 @@
|
||||
"""Fixed permission catalog for menus and actions.
|
||||
"""Fixed permission catalog: menus, button actions, and data scope.
|
||||
|
||||
UI and APIs only select from this list; new codes must be added in code.
|
||||
UI only selects from this list; new codes must be added in code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -8,7 +8,9 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Menu visibility
|
||||
# ---------------------------------------------------------------------------
|
||||
MENU_DASHBOARD = "menu.dashboard"
|
||||
MENU_ACCOUNTS = "menu.accounts"
|
||||
MENU_MESSAGES = "menu.messages"
|
||||
@@ -19,23 +21,63 @@ MENU_SYSTEM_LOGS = "menu.system_logs"
|
||||
MENU_DOWNLOAD = "menu.download"
|
||||
MENU_HELP = "menu.help"
|
||||
MENU_USERS = "menu.users"
|
||||
MENU_ROLES = "menu.roles"
|
||||
MENU_SETTINGS = "menu.settings"
|
||||
MENU_DESKTOP_UPDATE = "menu.desktop_update"
|
||||
MENU_PAYMENT_SETTINGS = "menu.payment_settings"
|
||||
MENU_PAYMENT_ORDERS = "menu.payment_orders"
|
||||
|
||||
# Actions
|
||||
ACCOUNTS_WRITE = "accounts.write"
|
||||
# ---------------------------------------------------------------------------
|
||||
# Button / action permissions
|
||||
# ---------------------------------------------------------------------------
|
||||
# Accounts (granular). Legacy ``accounts.write`` expands to the full set.
|
||||
ACCOUNTS_CREATE = "accounts.create"
|
||||
ACCOUNTS_UPDATE = "accounts.update"
|
||||
ACCOUNTS_DELETE = "accounts.delete"
|
||||
ACCOUNTS_START = "accounts.start"
|
||||
ACCOUNTS_STOP = "accounts.stop"
|
||||
ACCOUNTS_COOKIE = "accounts.cookie"
|
||||
ACCOUNTS_WRITE = "accounts.write" # legacy bundle
|
||||
|
||||
MESSAGES_WRITE = "messages.write"
|
||||
RULES_WRITE = "rules.write"
|
||||
LINK_CARDS_WRITE = "link_cards.write"
|
||||
|
||||
LOGS_READ = "logs.read"
|
||||
RECEIVED_MESSAGES_READ = "received_messages.read"
|
||||
SYSTEM_LOGS_READ = "system_logs.read"
|
||||
SYSTEM_LOGS_CLEAR = "system_logs.clear"
|
||||
|
||||
USERS_MANAGE = "users.manage"
|
||||
ROLES_MANAGE = "roles.manage"
|
||||
SETTINGS_MANAGE = "settings.manage"
|
||||
SETTINGS_DATABASE = "settings.database"
|
||||
DESKTOP_MANAGE = "desktop.manage"
|
||||
PAYMENTS_MANAGE = "payments.manage"
|
||||
ORDERS_READ = "orders.read"
|
||||
ORDERS_CREATE = "orders.create"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Data scope
|
||||
# ---------------------------------------------------------------------------
|
||||
# Without this (and without is_admin), users only see their own data.
|
||||
DATA_SCOPE_ALL = "data.scope_all"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Bundles / aliases expanded on save & when checking permissions
|
||||
# ---------------------------------------------------------------------------
|
||||
ACCOUNTS_WRITE_GRANULAR: tuple[str, ...] = (
|
||||
ACCOUNTS_CREATE,
|
||||
ACCOUNTS_UPDATE,
|
||||
ACCOUNTS_DELETE,
|
||||
ACCOUNTS_START,
|
||||
ACCOUNTS_STOP,
|
||||
ACCOUNTS_COOKIE,
|
||||
)
|
||||
|
||||
LEGACY_BUNDLES: dict[str, tuple[str, ...]] = {
|
||||
ACCOUNTS_WRITE: ACCOUNTS_WRITE_GRANULAR,
|
||||
}
|
||||
|
||||
ALL_PERMISSIONS: tuple[str, ...] = (
|
||||
MENU_DASHBOARD,
|
||||
@@ -48,21 +90,34 @@ ALL_PERMISSIONS: tuple[str, ...] = (
|
||||
MENU_DOWNLOAD,
|
||||
MENU_HELP,
|
||||
MENU_USERS,
|
||||
MENU_ROLES,
|
||||
MENU_SETTINGS,
|
||||
MENU_DESKTOP_UPDATE,
|
||||
MENU_PAYMENT_SETTINGS,
|
||||
MENU_PAYMENT_ORDERS,
|
||||
ACCOUNTS_CREATE,
|
||||
ACCOUNTS_UPDATE,
|
||||
ACCOUNTS_DELETE,
|
||||
ACCOUNTS_START,
|
||||
ACCOUNTS_STOP,
|
||||
ACCOUNTS_COOKIE,
|
||||
ACCOUNTS_WRITE,
|
||||
MESSAGES_WRITE,
|
||||
RULES_WRITE,
|
||||
LINK_CARDS_WRITE,
|
||||
LOGS_READ,
|
||||
RECEIVED_MESSAGES_READ,
|
||||
SYSTEM_LOGS_READ,
|
||||
SYSTEM_LOGS_CLEAR,
|
||||
USERS_MANAGE,
|
||||
ROLES_MANAGE,
|
||||
SETTINGS_MANAGE,
|
||||
SETTINGS_DATABASE,
|
||||
DESKTOP_MANAGE,
|
||||
PAYMENTS_MANAGE,
|
||||
ORDERS_READ,
|
||||
ORDERS_CREATE,
|
||||
DATA_SCOPE_ALL,
|
||||
)
|
||||
|
||||
PERMISSION_SET = frozenset(ALL_PERMISSIONS)
|
||||
@@ -70,8 +125,10 @@ PERMISSION_SET = frozenset(ALL_PERMISSIONS)
|
||||
WRITE_PERMISSIONS = frozenset(
|
||||
{
|
||||
ACCOUNTS_WRITE,
|
||||
*ACCOUNTS_WRITE_GRANULAR,
|
||||
MESSAGES_WRITE,
|
||||
RULES_WRITE,
|
||||
LINK_CARDS_WRITE,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -85,22 +142,35 @@ _PERMISSION_META: dict[str, dict[str, str]] = {
|
||||
MENU_SYSTEM_LOGS: {"group": "menu", "label": "系统诊断日志"},
|
||||
MENU_DOWNLOAD: {"group": "menu", "label": "软件下载"},
|
||||
MENU_HELP: {"group": "menu", "label": "帮助中心"},
|
||||
MENU_USERS: {"group": "menu", "label": "用户与角色"},
|
||||
MENU_USERS: {"group": "menu", "label": "用户管理"},
|
||||
MENU_ROLES: {"group": "menu", "label": "角色设定"},
|
||||
MENU_SETTINGS: {"group": "menu", "label": "系统设置"},
|
||||
MENU_DESKTOP_UPDATE: {"group": "menu", "label": "桌面端升级"},
|
||||
MENU_PAYMENT_SETTINGS: {"group": "menu", "label": "支付配置"},
|
||||
MENU_PAYMENT_ORDERS: {"group": "menu", "label": "我的订单"},
|
||||
ACCOUNTS_WRITE: {"group": "action", "label": "账号写操作(启动/停止/改凭证/删除)"},
|
||||
MESSAGES_WRITE: {"group": "action", "label": "发送私信"},
|
||||
ACCOUNTS_CREATE: {"group": "action", "label": "新增抖音账号"},
|
||||
ACCOUNTS_UPDATE: {"group": "action", "label": "编辑账号信息"},
|
||||
ACCOUNTS_DELETE: {"group": "action", "label": "删除账号"},
|
||||
ACCOUNTS_START: {"group": "action", "label": "启动托管 / 批量启动"},
|
||||
ACCOUNTS_STOP: {"group": "action", "label": "停止托管"},
|
||||
ACCOUNTS_COOKIE: {"group": "action", "label": "查看/修改 Cookie 与凭证"},
|
||||
ACCOUNTS_WRITE: {"group": "action", "label": "账号全部写操作(兼容旧版,等同下列细项)"},
|
||||
MESSAGES_WRITE: {"group": "action", "label": "发送私信 / 队列立即发送"},
|
||||
RULES_WRITE: {"group": "action", "label": "编辑自动回复规则"},
|
||||
LINK_CARDS_WRITE: {"group": "action", "label": "上传素材 / 生成链接卡片"},
|
||||
LOGS_READ: {"group": "action", "label": "查看回复日志"},
|
||||
RECEIVED_MESSAGES_READ: {"group": "action", "label": "查看接收消息日志"},
|
||||
SYSTEM_LOGS_READ: {"group": "action", "label": "查看系统诊断日志"},
|
||||
USERS_MANAGE: {"group": "action", "label": "管理用户与角色"},
|
||||
SYSTEM_LOGS_CLEAR: {"group": "action", "label": "清空系统诊断日志"},
|
||||
USERS_MANAGE: {"group": "action", "label": "管理用户"},
|
||||
ROLES_MANAGE: {"group": "action", "label": "管理角色"},
|
||||
SETTINGS_MANAGE: {"group": "action", "label": "管理系统设置"},
|
||||
SETTINGS_DATABASE: {"group": "action", "label": "管理数据库配置与迁移"},
|
||||
DESKTOP_MANAGE: {"group": "action", "label": "管理桌面端升级"},
|
||||
PAYMENTS_MANAGE: {"group": "action", "label": "管理支付配置"},
|
||||
PAYMENTS_MANAGE: {"group": "action", "label": "管理支付配置与全部订单"},
|
||||
ORDERS_READ: {"group": "action", "label": "查看我的订单"},
|
||||
ORDERS_CREATE: {"group": "action", "label": "购买额度 / 创建订单"},
|
||||
DATA_SCOPE_ALL: {"group": "data", "label": "查看全部用户数据(全局数据范围)"},
|
||||
}
|
||||
|
||||
OPERATOR_PERMISSIONS: tuple[str, ...] = (
|
||||
@@ -113,12 +183,14 @@ OPERATOR_PERMISSIONS: tuple[str, ...] = (
|
||||
MENU_DOWNLOAD,
|
||||
MENU_HELP,
|
||||
MENU_PAYMENT_ORDERS,
|
||||
ACCOUNTS_WRITE,
|
||||
*ACCOUNTS_WRITE_GRANULAR,
|
||||
MESSAGES_WRITE,
|
||||
RULES_WRITE,
|
||||
LINK_CARDS_WRITE,
|
||||
LOGS_READ,
|
||||
RECEIVED_MESSAGES_READ,
|
||||
ORDERS_READ,
|
||||
ORDERS_CREATE,
|
||||
)
|
||||
|
||||
VIEWER_PERMISSIONS: tuple[str, ...] = (
|
||||
@@ -149,14 +221,158 @@ def normalize_permissions(codes: list[str] | tuple[str, ...] | None) -> list[str
|
||||
return result
|
||||
|
||||
|
||||
def expand_legacy_bundles(codes: set[str]) -> set[str]:
|
||||
"""Expand legacy bundle codes into granular permissions."""
|
||||
expanded = set(codes)
|
||||
for bundle, parts in LEGACY_BUNDLES.items():
|
||||
if bundle in expanded:
|
||||
expanded.update(parts)
|
||||
return expanded
|
||||
|
||||
|
||||
# Menu → required action. Selecting a menu always grants the action.
|
||||
MENU_REQUIRED_ACTIONS: dict[str, str | tuple[str, ...]] = {
|
||||
MENU_USERS: USERS_MANAGE,
|
||||
MENU_ROLES: ROLES_MANAGE,
|
||||
MENU_SETTINGS: SETTINGS_MANAGE,
|
||||
MENU_DESKTOP_UPDATE: DESKTOP_MANAGE,
|
||||
MENU_PAYMENT_SETTINGS: PAYMENTS_MANAGE,
|
||||
MENU_PAYMENT_ORDERS: (ORDERS_READ, ORDERS_CREATE),
|
||||
MENU_LOGS: LOGS_READ,
|
||||
MENU_RECEIVED_MESSAGES: RECEIVED_MESSAGES_READ,
|
||||
MENU_SYSTEM_LOGS: SYSTEM_LOGS_READ,
|
||||
MENU_ACCOUNTS: (), # page access only; buttons are separate
|
||||
MENU_MESSAGES: (),
|
||||
MENU_RULES: (),
|
||||
}
|
||||
|
||||
# Action → primary menu only.
|
||||
ACTION_PRIMARY_MENU: dict[str, str] = {
|
||||
USERS_MANAGE: MENU_USERS,
|
||||
ROLES_MANAGE: MENU_ROLES,
|
||||
SETTINGS_MANAGE: MENU_SETTINGS,
|
||||
SETTINGS_DATABASE: MENU_SETTINGS,
|
||||
DESKTOP_MANAGE: MENU_DESKTOP_UPDATE,
|
||||
PAYMENTS_MANAGE: MENU_PAYMENT_SETTINGS,
|
||||
ORDERS_READ: MENU_PAYMENT_ORDERS,
|
||||
ORDERS_CREATE: MENU_PAYMENT_ORDERS,
|
||||
LOGS_READ: MENU_LOGS,
|
||||
RECEIVED_MESSAGES_READ: MENU_RECEIVED_MESSAGES,
|
||||
SYSTEM_LOGS_READ: MENU_SYSTEM_LOGS,
|
||||
SYSTEM_LOGS_CLEAR: MENU_SYSTEM_LOGS,
|
||||
ACCOUNTS_CREATE: MENU_ACCOUNTS,
|
||||
ACCOUNTS_UPDATE: MENU_ACCOUNTS,
|
||||
ACCOUNTS_DELETE: MENU_ACCOUNTS,
|
||||
ACCOUNTS_START: MENU_ACCOUNTS,
|
||||
ACCOUNTS_STOP: MENU_ACCOUNTS,
|
||||
ACCOUNTS_COOKIE: MENU_ACCOUNTS,
|
||||
ACCOUNTS_WRITE: MENU_ACCOUNTS,
|
||||
MESSAGES_WRITE: MENU_MESSAGES,
|
||||
RULES_WRITE: MENU_RULES,
|
||||
LINK_CARDS_WRITE: MENU_RULES,
|
||||
}
|
||||
|
||||
|
||||
def _iter_required_actions(menu: str) -> tuple[str, ...]:
|
||||
raw = MENU_REQUIRED_ACTIONS.get(menu)
|
||||
if raw is None:
|
||||
return ()
|
||||
if isinstance(raw, str):
|
||||
return (raw,)
|
||||
return tuple(raw)
|
||||
|
||||
|
||||
# Catalog / UI pairs (menu → first required action for checkbox hints).
|
||||
MENU_ACTION_PAIRS: tuple[tuple[str, str], ...] = tuple(
|
||||
(menu, actions[0])
|
||||
for menu, actions in (
|
||||
(m, _iter_required_actions(m)) for m in MENU_REQUIRED_ACTIONS
|
||||
)
|
||||
if actions
|
||||
)
|
||||
|
||||
|
||||
def expand_paired_permissions(codes: list[str] | tuple[str, ...] | None) -> list[str]:
|
||||
"""Normalize, expand legacy bundles, and auto-complete menu/action pairs."""
|
||||
selected = expand_legacy_bundles(set(normalize_permissions(codes)))
|
||||
for menu in list(selected):
|
||||
for action in _iter_required_actions(menu):
|
||||
selected.add(action)
|
||||
for action, menu in ACTION_PRIMARY_MENU.items():
|
||||
if action in selected:
|
||||
selected.add(menu)
|
||||
# If every granular account write is present, keep the legacy bundle flag.
|
||||
if all(code in selected for code in ACCOUNTS_WRITE_GRANULAR):
|
||||
selected.add(ACCOUNTS_WRITE)
|
||||
return [code for code in ALL_PERMISSIONS if code in selected]
|
||||
|
||||
|
||||
def permission_implies(held: set[str], needed: str) -> bool:
|
||||
"""Whether a held permission set satisfies ``needed`` (incl. legacy bundles)."""
|
||||
if needed in held:
|
||||
return True
|
||||
for bundle, parts in LEGACY_BUNDLES.items():
|
||||
if needed in parts and bundle in held:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def permission_catalog() -> dict[str, Any]:
|
||||
menus = []
|
||||
actions = []
|
||||
data = []
|
||||
meta_by_code: dict[str, dict[str, str]] = {}
|
||||
for code in ALL_PERMISSIONS:
|
||||
meta = _PERMISSION_META[code]
|
||||
item = {"code": code, "label": meta["label"]}
|
||||
if meta["group"] == "menu":
|
||||
meta_by_code[code] = meta
|
||||
group = meta["group"]
|
||||
if group == "menu":
|
||||
menus.append(item)
|
||||
elif group == "data":
|
||||
data.append(item)
|
||||
else:
|
||||
actions.append(item)
|
||||
return {"menus": menus, "actions": actions}
|
||||
|
||||
# Reverse index: menu → child action codes (preserve ALL_PERMISSIONS order).
|
||||
children_by_menu: dict[str, list[dict[str, str]]] = {m["code"]: [] for m in menus}
|
||||
for code in ALL_PERMISSIONS:
|
||||
if meta_by_code[code]["group"] != "action":
|
||||
continue
|
||||
parent = ACTION_PRIMARY_MENU.get(code)
|
||||
if parent and parent in children_by_menu:
|
||||
children_by_menu[parent].append(
|
||||
{"code": code, "label": meta_by_code[code]["label"]}
|
||||
)
|
||||
|
||||
tree: list[dict[str, Any]] = []
|
||||
for menu in menus:
|
||||
node: dict[str, Any] = {
|
||||
"code": menu["code"],
|
||||
"label": menu["label"],
|
||||
"kind": "menu",
|
||||
"children": children_by_menu.get(menu["code"], []),
|
||||
}
|
||||
tree.append(node)
|
||||
|
||||
if data:
|
||||
tree.append(
|
||||
{
|
||||
"code": "__group.data__",
|
||||
"label": "数据权限",
|
||||
"kind": "group",
|
||||
"children": [
|
||||
{"code": item["code"], "label": item["label"]} for item in data
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"menus": menus,
|
||||
"actions": actions,
|
||||
"data": data,
|
||||
"tree": tree,
|
||||
"pairs": [
|
||||
{"menu": menu, "action": action} for menu, action in MENU_ACTION_PAIRS
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user