sync: 同步 sql-platform-pro 最新代码 — 权限体系安全修复 v0.8.1
- P0: 实例访问控制全线接入权限组 (829测试) - P1: permissions字段激活 + 操作级权限检查 (843测试) - 前端: 删除数据权限页面, 权限组合并为唯一入口 - 新增: CodeMirror编辑器 + 查询模板 + 慢查询分析 - 新增: MongoDB管理页面 + Dashboard增强 - 修复: Pydantic V2迁移 + 前端TS错误修复
This commit is contained in:
1 parent
6fcd9465cf
commit
4c9427e546
105 files changed
+23680
-471
No files matched your search
@@ -1,4 +1,4 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@ class Settings(BaseSettings):
|
||||
ADMIN_USERNAME: str = "admin"
|
||||
ADMIN_PASSWORD: str = "123456"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = "utf-8"
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
|
||||
|
||||
|
||||
@lru_cache()
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
"""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
|
||||
Reference in new issue
Block a user