379 lines
12 KiB
Python
379 lines
12 KiB
Python
"""Fixed permission catalog: menus, button actions, and data scope.
|
|
|
|
UI only selects from this list; new codes must be added in code.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Menu visibility
|
|
# ---------------------------------------------------------------------------
|
|
MENU_DASHBOARD = "menu.dashboard"
|
|
MENU_ACCOUNTS = "menu.accounts"
|
|
MENU_MESSAGES = "menu.messages"
|
|
MENU_RULES = "menu.rules"
|
|
MENU_LOGS = "menu.logs"
|
|
MENU_RECEIVED_MESSAGES = "menu.received_messages"
|
|
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"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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,
|
|
MENU_ACCOUNTS,
|
|
MENU_MESSAGES,
|
|
MENU_RULES,
|
|
MENU_LOGS,
|
|
MENU_RECEIVED_MESSAGES,
|
|
MENU_SYSTEM_LOGS,
|
|
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)
|
|
|
|
WRITE_PERMISSIONS = frozenset(
|
|
{
|
|
ACCOUNTS_WRITE,
|
|
*ACCOUNTS_WRITE_GRANULAR,
|
|
MESSAGES_WRITE,
|
|
RULES_WRITE,
|
|
LINK_CARDS_WRITE,
|
|
}
|
|
)
|
|
|
|
_PERMISSION_META: dict[str, dict[str, str]] = {
|
|
MENU_DASHBOARD: {"group": "menu", "label": "数据概览"},
|
|
MENU_ACCOUNTS: {"group": "menu", "label": "账号管理"},
|
|
MENU_MESSAGES: {"group": "menu", "label": "私信收发"},
|
|
MENU_RULES: {"group": "menu", "label": "自动回复规则"},
|
|
MENU_LOGS: {"group": "menu", "label": "回复日志面板"},
|
|
MENU_RECEIVED_MESSAGES: {"group": "menu", "label": "接收消息日志"},
|
|
MENU_SYSTEM_LOGS: {"group": "menu", "label": "系统诊断日志"},
|
|
MENU_DOWNLOAD: {"group": "menu", "label": "软件下载"},
|
|
MENU_HELP: {"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_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": "查看系统诊断日志"},
|
|
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": "管理支付配置与全部订单"},
|
|
ORDERS_READ: {"group": "action", "label": "查看我的订单"},
|
|
ORDERS_CREATE: {"group": "action", "label": "购买额度 / 创建订单"},
|
|
DATA_SCOPE_ALL: {"group": "data", "label": "查看全部用户数据(全局数据范围)"},
|
|
}
|
|
|
|
OPERATOR_PERMISSIONS: tuple[str, ...] = (
|
|
MENU_DASHBOARD,
|
|
MENU_ACCOUNTS,
|
|
MENU_MESSAGES,
|
|
MENU_RULES,
|
|
MENU_LOGS,
|
|
MENU_RECEIVED_MESSAGES,
|
|
MENU_DOWNLOAD,
|
|
MENU_HELP,
|
|
MENU_PAYMENT_ORDERS,
|
|
*ACCOUNTS_WRITE_GRANULAR,
|
|
MESSAGES_WRITE,
|
|
RULES_WRITE,
|
|
LINK_CARDS_WRITE,
|
|
LOGS_READ,
|
|
RECEIVED_MESSAGES_READ,
|
|
ORDERS_READ,
|
|
ORDERS_CREATE,
|
|
)
|
|
|
|
VIEWER_PERMISSIONS: tuple[str, ...] = (
|
|
MENU_DASHBOARD,
|
|
MENU_ACCOUNTS,
|
|
MENU_MESSAGES,
|
|
MENU_RULES,
|
|
MENU_LOGS,
|
|
MENU_RECEIVED_MESSAGES,
|
|
MENU_DOWNLOAD,
|
|
MENU_HELP,
|
|
LOGS_READ,
|
|
RECEIVED_MESSAGES_READ,
|
|
)
|
|
|
|
|
|
def normalize_permissions(codes: list[str] | tuple[str, ...] | None) -> list[str]:
|
|
if not codes:
|
|
return []
|
|
seen: set[str] = set()
|
|
result: list[str] = []
|
|
for code in codes:
|
|
value = str(code or "").strip()
|
|
if not value or value not in PERMISSION_SET or value in seen:
|
|
continue
|
|
seen.add(value)
|
|
result.append(value)
|
|
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"]}
|
|
meta_by_code[code] = meta
|
|
group = meta["group"]
|
|
if group == "menu":
|
|
menus.append(item)
|
|
elif group == "data":
|
|
data.append(item)
|
|
else:
|
|
actions.append(item)
|
|
|
|
# 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
|
|
],
|
|
}
|