- P0: 实例访问控制全线接入权限组 (829测试) - P1: permissions字段激活 + 操作级权限检查 (843测试) - 前端: 删除数据权限页面, 权限组合并为唯一入口 - 新增: CodeMirror编辑器 + 查询模板 + 慢查询分析 - 新增: MongoDB管理页面 + Dashboard增强 - 修复: Pydantic V2迁移 + 前端TS错误修复
164 lines
4.9 KiB
Python
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
|