ForcePilot/backend/package/yuxi/channel/gateway/rbac.py
Kris ecd3c90e80 feat(channel/gateway): 新增完整网关通道模块
新增设备身份管理、认证限流、并发通道、Webhook路由、RBAC权限控制、SSE/轮询降级等全套网关通道功能,包含:
1. 设备身份生成与签名验证
2. 设备令牌认证与速率限制
3. 内存+数据库双重设备注册表
4. 并发通道限流管理
5. Webhook安全处理与路由
6. RBAC权限校验系统
7. OpenAI API兼容适配层
8. Tailscale认证支持
9. HTTP轮询降级机制
2026-05-21 10:26:33 +08:00

150 lines
3.9 KiB
Python

import logging
from collections.abc import Callable
from enum import StrEnum
from functools import wraps
from typing import Any
logger = logging.getLogger(__name__)
class GatewayRole(StrEnum):
SUPERADMIN = "superadmin"
ADMIN = "admin"
OPERATOR = "operator"
VIEWER = "viewer"
ROLE_HIERARCHY: dict[GatewayRole, int] = {
GatewayRole.SUPERADMIN: 4,
GatewayRole.ADMIN: 3,
GatewayRole.OPERATOR: 2,
GatewayRole.VIEWER: 1,
}
_USER_ROLE_MAP: dict[str, GatewayRole] = {
"superadmin": GatewayRole.SUPERADMIN,
"admin": GatewayRole.ADMIN,
"user": GatewayRole.OPERATOR,
}
def map_user_role(user_role: str | None) -> GatewayRole:
if not user_role:
return GatewayRole.VIEWER
return _USER_ROLE_MAP.get(user_role.lower(), GatewayRole.VIEWER)
READ_ONLY_METHODS: set[str] = {
"system.health",
"system.version",
"channels.list",
"channels.status",
"channels.probe",
"chat.history",
"sessions.list",
"sessions.history",
"plugins.list",
"agentTools.list",
"config.get",
}
OPERATOR_METHODS: set[str] = READ_ONLY_METHODS | {
"chat.send",
"chat.stream",
"chat.cancel",
"sessions.create",
"sessions.delete",
"allowlist.get",
"cron.list",
"agentTools.execute",
"message.send",
"message.action",
}
ADMIN_METHODS: set[str] = OPERATOR_METHODS | {
"system.log.tail",
"system.config",
"channels.start",
"channels.stop",
"channels.restart",
"channels.configure",
"pairing.list",
"pairing.approve",
"pairing.reject",
"allowlist.add",
"allowlist.remove",
"identity_links.list",
"identity_links.add",
"identity_links.remove",
"plugins.install",
"config.set",
}
SUPERADMIN_METHODS: set[str] = ADMIN_METHODS | {
"cron.create",
"cron.delete",
"cron.force_run",
"cron.pause",
"cron.resume",
}
METHOD_MIN_ROLE: dict[str, GatewayRole] = {}
for _method in READ_ONLY_METHODS:
METHOD_MIN_ROLE[_method] = GatewayRole.VIEWER
for _method in OPERATOR_METHODS - READ_ONLY_METHODS:
METHOD_MIN_ROLE[_method] = GatewayRole.OPERATOR
for _method in ADMIN_METHODS - OPERATOR_METHODS:
METHOD_MIN_ROLE[_method] = GatewayRole.ADMIN
for _method in {"cron.create", "cron.delete", "cron.force_run", "cron.pause", "cron.resume"}:
METHOD_MIN_ROLE[_method] = GatewayRole.SUPERADMIN
def check_permission(role: GatewayRole | None, method: str) -> bool:
effective_role = role or GatewayRole.VIEWER
min_role = METHOD_MIN_ROLE.get(method)
if min_role is None:
return True
return ROLE_HIERARCHY[effective_role] >= ROLE_HIERARCHY[min_role]
def require_role(min_role: GatewayRole):
def decorator(
func: Callable[..., Any],
) -> Callable[..., Any]:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
caller_role: GatewayRole | None = kwargs.pop("_caller_role", None)
if caller_role is None:
logger.warning(
"RBAC: _caller_role not found in handler kwargs for %s, allowing by default",
func.__name__,
)
return await func(*args, **kwargs)
if ROLE_HIERARCHY[caller_role] < ROLE_HIERARCHY[min_role]:
from yuxi.channel.gateway.protocol import GatewayErrorCode, RpcResponse
logger.warning(
"RBAC: %s requires %s, caller has %s — denied",
func.__name__,
min_role.value,
caller_role.value,
)
return RpcResponse(
id=kwargs.get("request_id", ""),
ok=False,
error_code=GatewayErrorCode.PERMISSION_DENIED,
error_message=f"需要 {min_role.value} 权限,当前角色为 {caller_role.value}",
)
return await func(*args, **kwargs)
return wrapper
return decorator