refactor: 权限组从一组一类型改为一组同时包含readonly+write实例
- 新增 instance_group_members 中间表关联权限组和实例 - InstanceGroup 模型移除旧的 instances relationship,改为通过中间表查询 - Instance 模型保留 group_id FK(兼容)但移除 group relationship - Schema 新增 readonly_instance_ids / write_instance_ids 字段 - InstanceGroupResponse 改为 readonly_instance_count + write_instance_count - InstanceGroupDetailResponse 返回 readonly_instances + write_instances - InstanceAssignRequest 新增 access_type 参数 - API create/update 支持同步写入 instance_group_members - role_permissions 服务更新为使用中间表查询 - 新增迁移 SQL 并应用到 sqlite3.db - 测试全部通过 (20/20)
This commit is contained in:
1 parent
40868c61bf
commit
39d9600e82
8 files changed
+418
-97
No files matched your search
@@ -2,60 +2,79 @@ import json
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select, func as sa_func
|
||||
from sqlalchemy import select, delete as sa_delete, func as sa_func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import get_current_user, require_role
|
||||
from app.models.user import User
|
||||
from app.models.instance import Instance
|
||||
from app.models.instance_group import InstanceGroup, user_groups
|
||||
from app.models.instance_group import InstanceGroup, user_groups, instance_group_members
|
||||
from app.schemas.instance_group import (
|
||||
InstanceGroupCreate, InstanceGroupUpdate,
|
||||
InstanceGroupResponse, InstanceGroupDetailResponse,
|
||||
InstanceAssignRequest, UserAssignRequest, UserGroupUpdateRequest,
|
||||
InstanceAssignRequest, InstanceRemoveRequest,
|
||||
UserAssignRequest, UserGroupUpdateRequest,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/instance-groups", tags=["权限组管理"])
|
||||
|
||||
INSTANCE_FIELDS = ["id", "name", "db_type", "host", "port", "env", "status"]
|
||||
|
||||
def _serialize_group(group: InstanceGroup, instance_count: int = 0, user_count: int = 0) -> dict:
|
||||
|
||||
def _serialize_instance(inst: Instance) -> dict:
|
||||
return {k: getattr(inst, k, None) for k in INSTANCE_FIELDS}
|
||||
|
||||
|
||||
async def _count_members(db: AsyncSession, group_id: int, access_type: str) -> int:
|
||||
result = await db.execute(
|
||||
select(sa_func.count()).select_from(instance_group_members).where(
|
||||
instance_group_members.c.group_id == group_id,
|
||||
instance_group_members.c.access_type == access_type,
|
||||
)
|
||||
)
|
||||
return result.scalar() or 0
|
||||
|
||||
|
||||
async def _get_member_instances(db: AsyncSession, group_id: int, access_type: str) -> List[dict]:
|
||||
"""查询权限组中指定 access_type 的实例列表。"""
|
||||
# Join instance_group_members with instances
|
||||
result = await db.execute(
|
||||
select(Instance, instance_group_members.c.access_type)
|
||||
.join(instance_group_members, Instance.id == instance_group_members.c.instance_id)
|
||||
.where(
|
||||
instance_group_members.c.group_id == group_id,
|
||||
instance_group_members.c.access_type == access_type,
|
||||
)
|
||||
)
|
||||
rows = result.all()
|
||||
return [_serialize_instance(row[0]) for row in rows]
|
||||
|
||||
|
||||
async def _serialize_group(group: InstanceGroup, db: AsyncSession) -> dict:
|
||||
ro_count = await _count_members(db, group.id, "readonly")
|
||||
wr_count = await _count_members(db, group.id, "write")
|
||||
user_count = len(group.users) if group.users else 0
|
||||
return {
|
||||
"id": group.id,
|
||||
"name": group.name,
|
||||
"description": group.description,
|
||||
"db_type": group.db_type,
|
||||
# Legacy field
|
||||
"permissions": group.get_permissions(),
|
||||
# New fields
|
||||
"access_type": group.access_type,
|
||||
"allowed_databases": group.get_allowed_databases(),
|
||||
"is_active": group.is_active,
|
||||
"created_at": group.created_at,
|
||||
"updated_at": group.updated_at,
|
||||
"instance_count": instance_count,
|
||||
"readonly_instance_count": ro_count,
|
||||
"write_instance_count": wr_count,
|
||||
"user_count": user_count,
|
||||
}
|
||||
|
||||
|
||||
def _serialize_group_detail(group: InstanceGroup) -> dict:
|
||||
d = _serialize_group(
|
||||
group,
|
||||
instance_count=len(group.instances) if group.instances else 0,
|
||||
user_count=len(group.users) if group.users else 0,
|
||||
)
|
||||
d["instances"] = [
|
||||
{
|
||||
"id": inst.id,
|
||||
"name": inst.name,
|
||||
"db_type": inst.db_type,
|
||||
"host": inst.host,
|
||||
"port": inst.port,
|
||||
"env": inst.env,
|
||||
"status": inst.status,
|
||||
}
|
||||
for inst in (group.instances or [])
|
||||
]
|
||||
async def _serialize_group_detail(group: InstanceGroup, db: AsyncSession) -> dict:
|
||||
d = await _serialize_group(group, db)
|
||||
d["readonly_instances"] = await _get_member_instances(db, group.id, "readonly")
|
||||
d["write_instances"] = await _get_member_instances(db, group.id, "write")
|
||||
d["users"] = [
|
||||
{
|
||||
"id": u.id,
|
||||
@@ -68,6 +87,36 @@ def _serialize_group_detail(group: InstanceGroup) -> dict:
|
||||
return d
|
||||
|
||||
|
||||
async def _sync_members(
|
||||
db: AsyncSession,
|
||||
group_id: int,
|
||||
readonly_instance_ids: List[int],
|
||||
write_instance_ids: List[int],
|
||||
):
|
||||
"""Clear and rewrite all instance_group_members for a group."""
|
||||
await db.execute(
|
||||
sa_delete(instance_group_members).where(
|
||||
instance_group_members.c.group_id == group_id
|
||||
)
|
||||
)
|
||||
for inst_id in readonly_instance_ids:
|
||||
await db.execute(
|
||||
instance_group_members.insert().values(
|
||||
group_id=group_id,
|
||||
instance_id=inst_id,
|
||||
access_type="readonly",
|
||||
)
|
||||
)
|
||||
for inst_id in write_instance_ids:
|
||||
await db.execute(
|
||||
instance_group_members.insert().values(
|
||||
group_id=group_id,
|
||||
instance_id=inst_id,
|
||||
access_type="write",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=List[InstanceGroupResponse])
|
||||
async def list_instance_groups(
|
||||
db_type: Optional[str] = None,
|
||||
@@ -79,7 +128,7 @@ async def list_instance_groups(
|
||||
stmt = stmt.where(InstanceGroup.db_type == db_type)
|
||||
result = await db.execute(stmt)
|
||||
groups = result.scalars().all()
|
||||
return [_serialize_group_detail(g) for g in groups]
|
||||
return [await _serialize_group_detail(g, db) for g in groups]
|
||||
|
||||
|
||||
@router.post("", response_model=InstanceGroupResponse, status_code=201)
|
||||
@@ -99,21 +148,23 @@ async def create_instance_group(
|
||||
name=data.name,
|
||||
description=data.description,
|
||||
db_type=data.db_type,
|
||||
# New fields
|
||||
access_type=data.access_type,
|
||||
allowed_databases=(
|
||||
json.dumps(data.allowed_databases, ensure_ascii=False)
|
||||
if data.allowed_databases
|
||||
else None
|
||||
),
|
||||
# Legacy field (kept for compat)
|
||||
permissions=json.dumps(data.permissions, ensure_ascii=False) if data.permissions else None,
|
||||
is_active=data.is_active,
|
||||
)
|
||||
db.add(group)
|
||||
await db.commit()
|
||||
await db.refresh(group)
|
||||
return _serialize_group(group)
|
||||
|
||||
# Write members
|
||||
await _sync_members(db, group.id, data.readonly_instance_ids, data.write_instance_ids)
|
||||
await db.commit()
|
||||
|
||||
return await _serialize_group(group, db)
|
||||
|
||||
|
||||
@router.get("/{group_id}", response_model=InstanceGroupDetailResponse)
|
||||
@@ -128,7 +179,7 @@ async def get_instance_group(
|
||||
group = result.scalar_one_or_none()
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="权限组不存在")
|
||||
return _serialize_group_detail(group)
|
||||
return await _serialize_group_detail(group, db)
|
||||
|
||||
|
||||
@router.put("/{group_id}", response_model=InstanceGroupResponse)
|
||||
@@ -173,11 +224,25 @@ async def update_instance_group(
|
||||
else None
|
||||
)
|
||||
|
||||
# Extract member lists before setting attributes
|
||||
ro_ids = update_data.pop("readonly_instance_ids", None)
|
||||
wr_ids = update_data.pop("write_instance_ids", None)
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(group, key, value)
|
||||
await db.commit()
|
||||
await db.refresh(group)
|
||||
return _serialize_group(group)
|
||||
|
||||
# Update members if provided
|
||||
if ro_ids is not None or wr_ids is not None:
|
||||
await _sync_members(
|
||||
db, group_id,
|
||||
ro_ids if ro_ids is not None else [],
|
||||
wr_ids if wr_ids is not None else [],
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
return await _serialize_group(group, db)
|
||||
|
||||
|
||||
@router.delete("/{group_id}")
|
||||
@@ -193,12 +258,12 @@ async def delete_instance_group(
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="权限组不存在")
|
||||
|
||||
# Detach instances
|
||||
inst_result = await db.execute(
|
||||
select(Instance).where(Instance.group_id == group_id)
|
||||
# Delete members first
|
||||
await db.execute(
|
||||
sa_delete(instance_group_members).where(
|
||||
instance_group_members.c.group_id == group_id
|
||||
)
|
||||
)
|
||||
for inst in inst_result.scalars().all():
|
||||
inst.group_id = None
|
||||
|
||||
await db.delete(group)
|
||||
await db.commit()
|
||||
@@ -225,7 +290,22 @@ async def assign_instances(
|
||||
)
|
||||
inst = inst_result.scalar_one_or_none()
|
||||
if inst:
|
||||
inst.group_id = group_id
|
||||
# Check for duplicate
|
||||
existing = await db.execute(
|
||||
select(instance_group_members).where(
|
||||
instance_group_members.c.group_id == group_id,
|
||||
instance_group_members.c.instance_id == inst_id,
|
||||
instance_group_members.c.access_type == data.access_type,
|
||||
)
|
||||
)
|
||||
if not existing.first():
|
||||
await db.execute(
|
||||
instance_group_members.insert().values(
|
||||
group_id=group_id,
|
||||
instance_id=inst_id,
|
||||
access_type=data.access_type,
|
||||
)
|
||||
)
|
||||
await db.commit()
|
||||
return {"message": "实例已关联"}
|
||||
|
||||
@@ -234,16 +314,22 @@ async def assign_instances(
|
||||
async def remove_instance(
|
||||
group_id: int,
|
||||
instance_id: int,
|
||||
access_type: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
inst_result = await db.execute(
|
||||
select(Instance).where(Instance.id == instance_id, Instance.group_id == group_id)
|
||||
conditions = [
|
||||
instance_group_members.c.group_id == group_id,
|
||||
instance_group_members.c.instance_id == instance_id,
|
||||
]
|
||||
if access_type:
|
||||
conditions.append(instance_group_members.c.access_type == access_type)
|
||||
|
||||
result = await db.execute(
|
||||
sa_delete(instance_group_members).where(*conditions)
|
||||
)
|
||||
inst = inst_result.scalar_one_or_none()
|
||||
if not inst:
|
||||
if result.rowcount == 0:
|
||||
raise HTTPException(status_code=404, detail="实例未关联该权限组")
|
||||
inst.group_id = None
|
||||
await db.commit()
|
||||
return {"message": "实例已取消关联"}
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from app.models.instance_group import InstanceGroup, user_groups # noqa: F401
|
||||
from app.models.instance_group import InstanceGroup, user_groups, instance_group_members # noqa: F401
|
||||
from app.models.system_setting import SystemSetting # noqa: F401
|
||||
from app.models.query_history import QueryHistory, QuickQueryModel # noqa: F401
|
||||
@@ -26,12 +26,11 @@ class Instance(Base):
|
||||
excluded_databases = Column(Text, nullable=True) # JSON: ["sys", "information_schema", ...]
|
||||
mask_fields = Column(Text, nullable=True) # JSON: [{table, field, rule}]
|
||||
|
||||
# 权限组关联
|
||||
# 权限组关联 (legacy FK, kept for compat)
|
||||
group_id = Column(Integer, ForeignKey("instance_groups.id", ondelete="SET NULL"), nullable=True)
|
||||
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
# 关系
|
||||
group = relationship("InstanceGroup", back_populates="instances")
|
||||
# 关系 — legacy group relationship removed; use instance_group_members instead
|
||||
workflow_def = relationship("WorkflowDefinition")
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, Table, func
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, Table, func, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
@@ -17,6 +17,17 @@ user_groups = Table(
|
||||
Column("permissions", Text, nullable=True), # JSON: 覆盖组默认权限 (legacy, kept for compat)
|
||||
)
|
||||
|
||||
# 实例-权限组关联表(一个权限组可同时包含 readonly 和 write 实例)
|
||||
instance_group_members = Table(
|
||||
"instance_group_members",
|
||||
Base.metadata,
|
||||
Column("id", Integer, primary_key=True, autoincrement=True),
|
||||
Column("group_id", Integer, ForeignKey("instance_groups.id", ondelete="CASCADE"), nullable=False),
|
||||
Column("instance_id", Integer, ForeignKey("instances.id", ondelete="CASCADE"), nullable=False),
|
||||
Column("access_type", String(20), nullable=False, default="readonly"), # readonly / write
|
||||
UniqueConstraint("group_id", "instance_id", "access_type", name="uq_group_instance_access"),
|
||||
)
|
||||
|
||||
|
||||
class InstanceGroup(Base):
|
||||
__tablename__ = "instance_groups"
|
||||
@@ -26,14 +37,13 @@ class InstanceGroup(Base):
|
||||
description = Column(Text, nullable=True)
|
||||
db_type = Column(String(20), nullable=False, default="all") # mysql/redis/pg/mongo/all
|
||||
permissions = Column(Text, nullable=True) # JSON: ["query:select", ...] (legacy, kept for compat)
|
||||
access_type = Column(String(20), nullable=True, default="readonly") # readonly / write
|
||||
access_type = Column(String(20), nullable=True, default="readonly") # legacy, kept for compat
|
||||
allowed_databases = Column(Text, nullable=True) # JSON array of database names; empty = all databases
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
# 关系
|
||||
instances = relationship("Instance", back_populates="group", lazy="selectin")
|
||||
# 关系 — users 保持不变
|
||||
users = relationship("User", secondary=user_groups, back_populates="groups", lazy="selectin")
|
||||
|
||||
# ---- Legacy helpers (kept for backward compatibility) ----
|
||||
|
||||
@@ -7,11 +7,6 @@ class InstanceGroupCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: Optional[str] = None
|
||||
db_type: str = Field(default="all", pattern=r"^(mysql|redis|pg|mongo|all)$")
|
||||
access_type: str = Field(
|
||||
default="readonly",
|
||||
pattern=r"^(readonly|write)$",
|
||||
description="权限组类型: readonly(只读查看) / write(写入变更)",
|
||||
)
|
||||
allowed_databases: Optional[List[str]] = Field(
|
||||
default=None,
|
||||
description="允许访问的数据库名列表,为空表示关联实例的所有库",
|
||||
@@ -19,17 +14,15 @@ class InstanceGroupCreate(BaseModel):
|
||||
# Legacy field kept for backward compatibility
|
||||
permissions: Optional[List[str]] = None
|
||||
is_active: bool = True
|
||||
# New: instance ids by access_type
|
||||
readonly_instance_ids: List[int] = Field(default_factory=list)
|
||||
write_instance_ids: List[int] = Field(default_factory=list)
|
||||
|
||||
|
||||
class InstanceGroupUpdate(BaseModel):
|
||||
name: Optional[str] = Field(default=None, min_length=1, max_length=100)
|
||||
description: Optional[str] = None
|
||||
db_type: Optional[str] = Field(default=None, pattern=r"^(mysql|redis|pg|mongo|all)$")
|
||||
access_type: Optional[str] = Field(
|
||||
default=None,
|
||||
pattern=r"^(readonly|write)$",
|
||||
description="权限组类型: readonly(只读查看) / write(写入变更)",
|
||||
)
|
||||
allowed_databases: Optional[List[str]] = Field(
|
||||
default=None,
|
||||
description="允许访问的数据库名列表,为空表示关联实例的所有库",
|
||||
@@ -37,6 +30,9 @@ class InstanceGroupUpdate(BaseModel):
|
||||
# Legacy field kept for backward compatibility
|
||||
permissions: Optional[List[str]] = None
|
||||
is_active: Optional[bool] = None
|
||||
# New: instance ids by access_type
|
||||
readonly_instance_ids: Optional[List[int]] = None
|
||||
write_instance_ids: Optional[List[int]] = None
|
||||
|
||||
|
||||
class InstanceGroupResponse(BaseModel):
|
||||
@@ -47,24 +43,30 @@ class InstanceGroupResponse(BaseModel):
|
||||
# Legacy field
|
||||
permissions: Optional[List[str]] = None
|
||||
# New fields
|
||||
access_type: Optional[str] = None
|
||||
allowed_databases: Optional[List[str]] = None
|
||||
is_active: bool
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
instance_count: int = 0
|
||||
readonly_instance_count: int = 0
|
||||
write_instance_count: int = 0
|
||||
user_count: int = 0
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class InstanceGroupDetailResponse(InstanceGroupResponse):
|
||||
instances: List[dict] = []
|
||||
readonly_instances: List[dict] = []
|
||||
write_instances: List[dict] = []
|
||||
users: List[dict] = []
|
||||
|
||||
|
||||
class InstanceAssignRequest(BaseModel):
|
||||
instance_ids: List[int] = Field(..., min_length=1)
|
||||
access_type: str = Field(default="readonly", pattern=r"^(readonly|write)$")
|
||||
|
||||
|
||||
class InstanceRemoveRequest(BaseModel):
|
||||
instance_ids: List[int] = Field(..., min_length=1)
|
||||
|
||||
|
||||
class UserAssignRequest(BaseModel):
|
||||
|
||||
@@ -21,7 +21,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.user import User
|
||||
from app.models.instance import Instance
|
||||
from app.models.instance_group import InstanceGroup, user_groups
|
||||
from app.models.instance_group import InstanceGroup, user_groups, instance_group_members
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -132,7 +132,7 @@ async def get_accessible_instances(
|
||||
|
||||
- admin/dba: 返回所有实例
|
||||
- auditor: readonly 返回所有,write 返回空
|
||||
- developer: 返回其权限组关联的实例
|
||||
- developer: 返回其权限组关联的实例(通过 instance_group_members)
|
||||
"""
|
||||
role_config = _get_role_config(user.role)
|
||||
access_value = role_config.get(access_type)
|
||||
@@ -144,27 +144,26 @@ async def get_accessible_instances(
|
||||
if access_value is None:
|
||||
return []
|
||||
|
||||
# "groups" — 查询权限组关联的实例
|
||||
# "groups" — 查询权限组关联的实例 via instance_group_members
|
||||
group_ids = await _get_user_group_ids(db, user)
|
||||
if not group_ids:
|
||||
return []
|
||||
|
||||
# 只返回匹配 access_type 的权限组关联的实例
|
||||
# Query instance_group_members for the matching access_type
|
||||
result = await db.execute(
|
||||
select(InstanceGroup).where(InstanceGroup.id.in_(group_ids))
|
||||
select(instance_group_members.c.instance_id)
|
||||
.where(
|
||||
instance_group_members.c.group_id.in_(group_ids),
|
||||
instance_group_members.c.access_type == access_type,
|
||||
)
|
||||
)
|
||||
groups = result.scalars().all()
|
||||
matched_instance_ids: set = set()
|
||||
for g in groups:
|
||||
if g.access_type == access_type:
|
||||
for inst in (g.instances or []):
|
||||
matched_instance_ids.add(inst.id)
|
||||
instance_ids = list({row[0] for row in result.all()})
|
||||
|
||||
if not matched_instance_ids:
|
||||
if not instance_ids:
|
||||
return []
|
||||
|
||||
inst_result = await db.execute(
|
||||
select(Instance).where(Instance.id.in_(matched_instance_ids))
|
||||
select(Instance).where(Instance.id.in_(instance_ids))
|
||||
)
|
||||
return list(inst_result.scalars().all())
|
||||
|
||||
@@ -195,12 +194,26 @@ async def _get_user_groups_detail(db: AsyncSession, user: User) -> List[dict]:
|
||||
|
||||
details = []
|
||||
for g in groups:
|
||||
instance_ids = [inst.id for inst in (g.instances or [])]
|
||||
# Get instance IDs by access_type from instance_group_members
|
||||
ro_result = await db.execute(
|
||||
select(instance_group_members.c.instance_id).where(
|
||||
instance_group_members.c.group_id == g.id,
|
||||
instance_group_members.c.access_type == "readonly",
|
||||
)
|
||||
)
|
||||
wr_result = await db.execute(
|
||||
select(instance_group_members.c.instance_id).where(
|
||||
instance_group_members.c.group_id == g.id,
|
||||
instance_group_members.c.access_type == "write",
|
||||
)
|
||||
)
|
||||
ro_ids = [row[0] for row in ro_result.all()]
|
||||
wr_ids = [row[0] for row in wr_result.all()]
|
||||
details.append({
|
||||
"group_id": g.id,
|
||||
"group_name": g.name,
|
||||
"access_type": g.access_type,
|
||||
"instance_ids": instance_ids,
|
||||
"readonly_instance_ids": ro_ids,
|
||||
"write_instance_ids": wr_ids,
|
||||
"allowed_databases": g.get_allowed_databases(),
|
||||
})
|
||||
return details
|
||||
@@ -221,28 +234,44 @@ async def _check_group_access(
|
||||
if not instance:
|
||||
return False
|
||||
|
||||
# Public instance (no group_id) — readonly allowed
|
||||
if instance.group_id is None:
|
||||
return access_type == "readonly"
|
||||
|
||||
group_ids = await _get_user_group_ids(db, user)
|
||||
if instance.group_id not in group_ids:
|
||||
|
||||
# Check if any of the user's groups contain this instance with matching access_type
|
||||
result = await db.execute(
|
||||
select(instance_group_members).where(
|
||||
instance_group_members.c.instance_id == instance_id,
|
||||
instance_group_members.c.group_id.in_(group_ids),
|
||||
instance_group_members.c.access_type == access_type,
|
||||
)
|
||||
)
|
||||
member_row = result.first()
|
||||
if not member_row:
|
||||
# For readonly access, write access_type also grants readonly
|
||||
if access_type == "readonly":
|
||||
wr_result = await db.execute(
|
||||
select(instance_group_members).where(
|
||||
instance_group_members.c.instance_id == instance_id,
|
||||
instance_group_members.c.group_id.in_(group_ids),
|
||||
instance_group_members.c.access_type == "write",
|
||||
)
|
||||
)
|
||||
member_row = wr_result.first()
|
||||
|
||||
if not member_row:
|
||||
return False
|
||||
|
||||
# Check allowed_databases from the group
|
||||
group_result = await db.execute(
|
||||
select(InstanceGroup).where(InstanceGroup.id == instance.group_id)
|
||||
select(InstanceGroup).where(InstanceGroup.id == member_row.group_id)
|
||||
)
|
||||
group = group_result.scalar_one_or_none()
|
||||
if not group:
|
||||
return False
|
||||
|
||||
# readonly 权限组也可以被 write 请求使用(读+写包含只读)
|
||||
if access_type == "write" and group.access_type == "readonly":
|
||||
return False
|
||||
|
||||
# readonly 请求可以用 readonly 或 write 权限组
|
||||
if access_type == "readonly" and group.access_type in ("readonly", "write"):
|
||||
pass # ok
|
||||
|
||||
allowed_dbs = group.get_allowed_databases()
|
||||
if not allowed_dbs:
|
||||
return True
|
||||
|
||||
Reference in new issue
Block a user