Files
opsxsql/backend/app/core/permissions.py
T
logan 4c9427e546 sync: 同步 sql-platform-pro 最新代码 — 权限体系安全修复 v0.8.1
- P0: 实例访问控制全线接入权限组 (829测试)
- P1: permissions字段激活 + 操作级权限检查 (843测试)
- 前端: 删除数据权限页面, 权限组合并为唯一入口
- 新增: CodeMirror编辑器 + 查询模板 + 慢查询分析
- 新增: MongoDB管理页面 + Dashboard增强
- 修复: Pydantic V2迁移 + 前端TS错误修复
2026-05-23 14:41:06 +08:00

164 lines
4.9 KiB
Python

"""Shared permission checking utilities.
Centralises instance-level access control logic used by both
the query endpoint and the ticket endpoint.
"""
import json
from fastapi import HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
from app.models.instance import Instance
async def check_instance_access(
user: User,
instance: Instance,
db: AsyncSession,
) -> None:
"""Verify that *user* is allowed to access *instance*.
Rules:
- ``admin`` role bypasses all checks.
- ``guest`` role is **not** handled here — callers must reject guests
before calling this function.
- If *instance.group_id* is ``None`` the instance is considered **public**
and any authenticated (non-guest) user may access it.
- If *instance.group_id* is set, the user must belong to that group.
Raises:
HTTPException(403): when access is denied.
"""
# Admin bypasses everything
if user.role == "admin":
return
# Public instance (no group) — accessible to all authenticated users
if instance.group_id is None:
return
# Check group membership — query the association table directly
# to avoid stale identity-map caches in the same session
from app.models.instance_group import user_groups
result = await db.execute(
select(user_groups.c.group_id)
.where(user_groups.c.user_id == user.id)
)
user_group_ids = [row[0] for row in result.all()]
if instance.group_id not in user_group_ids:
raise HTTPException(
status_code=403,
detail="您没有权限访问该数据源,请联系管理员申请权限",
)
async def get_user_accessible_group_ids(user: User, db: AsyncSession) -> list[int]:
"""获取用户可访问的所有权限组 ID 列表。
- admin 返回空列表(后续由调用方处理为"全部")
- 其他角色查询 user_groups 关联表
"""
if user.role == "admin":
return [] # 空列表表示"需要全部",调用方用这个区分
from app.models.instance_group import user_groups
result = await db.execute(
select(user_groups.c.group_id).where(user_groups.c.user_id == user.id)
)
return [row[0] for row in result.all()]
async def get_instance_or_403(
instance_id: int,
user: User,
db: AsyncSession,
) -> Instance:
"""获取实例并检查访问权限,无权限则 403。
供各端点复用,减少重复代码。
"""
result = await db.execute(select(Instance).where(Instance.id == instance_id))
instance = result.scalar_one_or_none()
if not instance:
raise HTTPException(status_code=404, detail="数据源不存在")
await check_instance_access(user, instance, db)
return instance
# 权限操作项定义
PERMISSION_ACTIONS = {
"query:select",
"query:write",
"ticket:create",
"ticket:approve",
"ticket:execute",
"ddl:execute",
"slowlog:view",
"backup:manage",
"instance:detail",
}
async def check_instance_permission(
user: User,
instance: Instance,
action: str,
db: AsyncSession,
) -> None:
"""检查用户对实例是否具有指定的操作权限。
在 check_instance_access 通过后调用。
- admin 跳过
- 公开实例 (group_id=None) 允许所有
- 查 user_groups.permissions (用户级覆盖) > InstanceGroup.permissions (组默认)
- 如果都没配置,允许所有(向后兼容)
"""
if user.role == "admin":
return
if instance.group_id is None:
return
effective_perms = await _get_effective_permissions(user, instance, db)
if action not in effective_perms:
raise HTTPException(
status_code=403,
detail=f"您没有 '{action}' 操作权限,请联系管理员",
)
async def _get_effective_permissions(user, instance, db) -> set:
"""获取用户对实例的有效权限集。"""
from app.models.instance_group import InstanceGroup, user_groups
# 1. 查用户级覆盖权限
ug_result = await db.execute(
select(user_groups.c.permissions).where(
user_groups.c.user_id == user.id,
user_groups.c.group_id == instance.group_id,
)
)
ug_row = ug_result.first()
if ug_row and ug_row[0]:
try:
perms = json.loads(ug_row[0])
return set(perms) if perms else set()
except (json.JSONDecodeError, TypeError):
pass
# 2. 查组默认权限
group_result = await db.execute(
select(InstanceGroup).where(InstanceGroup.id == instance.group_id)
)
group = group_result.scalar_one_or_none()
if group:
group_perms = group.get_permissions()
if group_perms:
return set(group_perms)
# 3. 没配置 → 全部允许(向后兼容)
return PERMISSION_ACTIONS