""" FastAPI 依赖注入 — Workspace 上下文、RBAC 权限检查 """ from dataclasses import dataclass import logging from typing import Optional from fastapi import Depends, HTTPException, Request from sqlalchemy.orm import Session from app.core.database import get_db from app.core.security import decode_access_token from app.models.user import User from app.models.workspace import WorkspaceMembership logger = logging.getLogger(__name__) @dataclass class WorkspaceContext: """工作区上下文 — 包装当前用户 + 当前工作区 ID""" user: User workspace_id: str def get_current_workspace_id(request: Request) -> str: """从 JWT 的 `ws` 字段提取当前工作区 ID。""" auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): raise HTTPException(status_code=401, detail="未提供有效的认证令牌") token = auth_header[7:] payload = decode_access_token(token) if payload is None: raise HTTPException(status_code=401, detail="无效的访问令牌") ws_id = payload.get("ws") if not ws_id: raise HTTPException(status_code=400, detail="令牌中未包含工作区信息,请重新登录") return ws_id def get_workspace_context( request: Request, db: Session = Depends(get_db), ) -> WorkspaceContext: """获取完整的 Workspace 上下文(用户 + 工作区)。 注意:此函数仅认证用户并提取 workspace_id,不验证 workspace 成员资格。 用于跨 workspace 或 admin 端点。需要强制成员资格验证的端点请使用 get_workspace_membership()。 """ auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): raise HTTPException(status_code=401, detail="未提供有效的认证令牌") token = auth_header[7:] payload = decode_access_token(token) if payload is None: raise HTTPException(status_code=401, detail="无效的访问令牌") user_id = payload.get("sub") ws_id = payload.get("ws") if not user_id: raise HTTPException(status_code=401, detail="无效的访问令牌") user = db.query(User).filter(User.id == user_id).first() if not user: raise HTTPException(status_code=401, detail="用户不存在") if user.status == "deleted": raise HTTPException(status_code=403, detail="账号已注销") return WorkspaceContext(user=user, workspace_id=ws_id or "") # ─── 多租户 RBAC 依赖 (v1.3) ─── def get_workspace_membership( request: Request, db: Session = Depends(get_db), ) -> WorkspaceContext: """认证用户 + 验证 workspace 成员资格。 平台管理员(user.role == 'admin')绕过 workspace 成员资格检查。 普通用户必须属于 JWT 中 `ws` 指定的 workspace。 """ ctx = get_workspace_context(request, db) # 平台管理员可访问所有 workspace if ctx.user.role == "admin": return ctx if not ctx.workspace_id: raise HTTPException(status_code=400, detail="未选择工作区") membership = ( db.query(WorkspaceMembership) .filter( WorkspaceMembership.workspace_id == ctx.workspace_id, WorkspaceMembership.user_id == ctx.user.id, ) .first() ) if not membership: logger.warning("用户 %s 尝试访问未加入的工作区 %s", ctx.user.id, ctx.workspace_id) raise HTTPException(status_code=403, detail="无权访问此工作区") return ctx async def require_workspace_admin( ctx: WorkspaceContext = Depends(get_workspace_membership), db: Session = Depends(get_db), ) -> WorkspaceContext: """要求 workspace 管理员或平台管理员权限。 用于删除/发布/修改工作区设置等敏感操作。 """ # 平台管理员 if ctx.user.role == "admin": return ctx # workspace 管理员 membership = ( db.query(WorkspaceMembership) .filter( WorkspaceMembership.workspace_id == ctx.workspace_id, WorkspaceMembership.user_id == ctx.user.id, ) .first() ) if not membership or membership.role != "admin": raise HTTPException(status_code=403, detail="需要工作区管理员权限") return ctx class WorkspacePermission: """细粒度权限检查依赖 — 桥接系统 RBAC 与 workspace 上下文。 用法: @router.delete("/agents/{id}") async def delete_agent( ..., ctx: WorkspaceContext = Depends(WorkspacePermission("agent:delete")), ): """ def __init__(self, permission_code: str): self.permission_code = permission_code async def __call__( self, ctx: WorkspaceContext = Depends(get_workspace_membership), db: Session = Depends(get_db), ) -> WorkspaceContext: # 平台管理员总是通过 if ctx.user.role == "admin": return ctx # workspace 管理员拥有该 workspace 内所有权限 membership = ( db.query(WorkspaceMembership) .filter( WorkspaceMembership.workspace_id == ctx.workspace_id, WorkspaceMembership.user_id == ctx.user.id, ) .first() ) if membership and membership.role == "admin": return ctx # 检查系统级 RBAC 权限 if _user_has_permission(db, ctx.user.id, self.permission_code): return ctx raise HTTPException(status_code=403, detail=f"缺少权限: {self.permission_code}") def _user_has_permission(db: Session, user_id: str, permission_code: str) -> bool: """检查用户是否拥有指定权限(通过系统级 RBAC)。""" from app.models.permission import Role, Permission from app.models.user import User result = ( db.query(Permission) .join(Permission.roles) .join(Role.users) .filter( Permission.code == permission_code, User.id == user_id, ) .first() ) return result is not None