Files
aiagent/backend/app/api/deps.py
renjianbo f2e65a8fbb feat: virtual company module, team projects, PWA dishes app, and startup scripts overhaul
- Add company module (3-tier org, CEO planning, parallel departments)
- Add company orchestrator, knowledge extractor, presets, scheduler
- Add company API endpoints, models, and frontend views
- Add 今天吃啥 PWA app (69 dishes, real images, offline support)
- Add team_projects output directory structure
- Add unified manage.ps1 for service lifecycle
- Add Windows startup guide v1.0
- Add TTS troubleshooting doc
- Update frontend (AgentChat UX overhaul, new views)
- Update backend (voice engine fix, multi-tenant, RBAC)
- Remove deprecated startup scripts and old docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-10 22:37:56 +08:00

282 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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
# ─── 统一权限检查依赖 (v2.0) ───
class require_permission:
"""统一权限检查依赖 — 替换散落的 ``role == "admin"`` 判断。
用法::
@router.get("/agents")
async def list_agents(
ctx: WorkspaceContext = Depends(require_permission("agent:read")),
):
...
检查顺序:
1. 平台管理员user.role == "admin")→ 直接通过
2. Workspace 管理员 → 在该 workspace 内通过
3. 系统 RBAC → 检查用户角色是否拥有该权限码
"""
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
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
if _user_has_permission(db, ctx.user.id, self.permission_code):
return ctx
raise HTTPException(status_code=403, detail=f"缺少权限: {self.permission_code}")
class require_admin:
"""平台管理员权限检查 — 仅平台管理员可访问。
用法::
@router.delete("/users/{id}")
async def delete_user(
current_user: User = Depends(require_admin()),
):
...
"""
async def __call__(
self,
request: Request,
db: Session = Depends(get_db),
) -> User:
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")
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="账号已注销")
if user.role != "admin":
raise HTTPException(status_code=403, detail="仅平台管理员可执行此操作")
return user