2026-06-29 01:17:21 +08:00
|
|
|
|
"""
|
feat: multi-tenant workspace isolation, RBAC, sidebar nav, billing, and Android enhancements
- Backend: workspace_id isolation for 14 model tables + safe migration/backfill
- Backend: RBAC system with 4 roles and 23 permissions, seeded on startup
- Backend: workspace admin endpoints (list/manage all workspaces)
- Backend: admin user management API (CRUD, reset password)
- Backend: billing API with subscription plans, usage tracking, rate limiting
- Backend: fix system_logs.py UNION query and wrong column references
- Backend: WebSocket JWT auth and workspace enforcement
- Frontend: sidebar navigation replacing top dropdown menu
- Frontend: user management page (Users.vue) for admins
- Frontend: enhanced Workspaces.vue with admin table view
- Frontend: workspace RBAC computed properties in user store
- Android: agent marketplace, billing/subscription UI, onboarding wizard
- Android: phone login, analytics tracker, crash handler, network diagnostics
- Android: splash screen, encrypted token storage, app update enhancements
- Docs: multi-tenant RBAC guide with 8 sections and role-permission matrix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-04 01:00:22 +08:00
|
|
|
|
FastAPI 依赖注入 — Workspace 上下文、RBAC 权限检查
|
2026-06-29 01:17:21 +08:00
|
|
|
|
"""
|
|
|
|
|
|
from dataclasses import dataclass
|
feat: multi-tenant workspace isolation, RBAC, sidebar nav, billing, and Android enhancements
- Backend: workspace_id isolation for 14 model tables + safe migration/backfill
- Backend: RBAC system with 4 roles and 23 permissions, seeded on startup
- Backend: workspace admin endpoints (list/manage all workspaces)
- Backend: admin user management API (CRUD, reset password)
- Backend: billing API with subscription plans, usage tracking, rate limiting
- Backend: fix system_logs.py UNION query and wrong column references
- Backend: WebSocket JWT auth and workspace enforcement
- Frontend: sidebar navigation replacing top dropdown menu
- Frontend: user management page (Users.vue) for admins
- Frontend: enhanced Workspaces.vue with admin table view
- Frontend: workspace RBAC computed properties in user store
- Android: agent marketplace, billing/subscription UI, onboarding wizard
- Android: phone login, analytics tracker, crash handler, network diagnostics
- Android: splash screen, encrypted token storage, app update enhancements
- Docs: multi-tenant RBAC guide with 8 sections and role-permission matrix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-04 01:00:22 +08:00
|
|
|
|
import logging
|
2026-06-29 01:17:21 +08:00
|
|
|
|
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
|
feat: multi-tenant workspace isolation, RBAC, sidebar nav, billing, and Android enhancements
- Backend: workspace_id isolation for 14 model tables + safe migration/backfill
- Backend: RBAC system with 4 roles and 23 permissions, seeded on startup
- Backend: workspace admin endpoints (list/manage all workspaces)
- Backend: admin user management API (CRUD, reset password)
- Backend: billing API with subscription plans, usage tracking, rate limiting
- Backend: fix system_logs.py UNION query and wrong column references
- Backend: WebSocket JWT auth and workspace enforcement
- Frontend: sidebar navigation replacing top dropdown menu
- Frontend: user management page (Users.vue) for admins
- Frontend: enhanced Workspaces.vue with admin table view
- Frontend: workspace RBAC computed properties in user store
- Android: agent marketplace, billing/subscription UI, onboarding wizard
- Android: phone login, analytics tracker, crash handler, network diagnostics
- Android: splash screen, encrypted token storage, app update enhancements
- Docs: multi-tenant RBAC guide with 8 sections and role-permission matrix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-04 01:00:22 +08:00
|
|
|
|
from app.models.workspace import WorkspaceMembership
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
2026-06-29 01:17:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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:
|
feat: multi-tenant workspace isolation, RBAC, sidebar nav, billing, and Android enhancements
- Backend: workspace_id isolation for 14 model tables + safe migration/backfill
- Backend: RBAC system with 4 roles and 23 permissions, seeded on startup
- Backend: workspace admin endpoints (list/manage all workspaces)
- Backend: admin user management API (CRUD, reset password)
- Backend: billing API with subscription plans, usage tracking, rate limiting
- Backend: fix system_logs.py UNION query and wrong column references
- Backend: WebSocket JWT auth and workspace enforcement
- Frontend: sidebar navigation replacing top dropdown menu
- Frontend: user management page (Users.vue) for admins
- Frontend: enhanced Workspaces.vue with admin table view
- Frontend: workspace RBAC computed properties in user store
- Android: agent marketplace, billing/subscription UI, onboarding wizard
- Android: phone login, analytics tracker, crash handler, network diagnostics
- Android: splash screen, encrypted token storage, app update enhancements
- Docs: multi-tenant RBAC guide with 8 sections and role-permission matrix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-04 01:00:22 +08:00
|
|
|
|
"""获取完整的 Workspace 上下文(用户 + 工作区)。
|
|
|
|
|
|
|
|
|
|
|
|
注意:此函数仅认证用户并提取 workspace_id,不验证 workspace 成员资格。
|
|
|
|
|
|
用于跨 workspace 或 admin 端点。需要强制成员资格验证的端点请使用
|
|
|
|
|
|
get_workspace_membership()。
|
|
|
|
|
|
"""
|
2026-06-29 01:17:21 +08:00
|
|
|
|
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="用户不存在")
|
|
|
|
|
|
|
feat: multi-tenant workspace isolation, RBAC, sidebar nav, billing, and Android enhancements
- Backend: workspace_id isolation for 14 model tables + safe migration/backfill
- Backend: RBAC system with 4 roles and 23 permissions, seeded on startup
- Backend: workspace admin endpoints (list/manage all workspaces)
- Backend: admin user management API (CRUD, reset password)
- Backend: billing API with subscription plans, usage tracking, rate limiting
- Backend: fix system_logs.py UNION query and wrong column references
- Backend: WebSocket JWT auth and workspace enforcement
- Frontend: sidebar navigation replacing top dropdown menu
- Frontend: user management page (Users.vue) for admins
- Frontend: enhanced Workspaces.vue with admin table view
- Frontend: workspace RBAC computed properties in user store
- Android: agent marketplace, billing/subscription UI, onboarding wizard
- Android: phone login, analytics tracker, crash handler, network diagnostics
- Android: splash screen, encrypted token storage, app update enhancements
- Docs: multi-tenant RBAC guide with 8 sections and role-permission matrix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-04 01:00:22 +08:00
|
|
|
|
if user.status == "deleted":
|
|
|
|
|
|
raise HTTPException(status_code=403, detail="账号已注销")
|
|
|
|
|
|
|
2026-06-29 01:17:21 +08:00
|
|
|
|
return WorkspaceContext(user=user, workspace_id=ws_id or "")
|
feat: multi-tenant workspace isolation, RBAC, sidebar nav, billing, and Android enhancements
- Backend: workspace_id isolation for 14 model tables + safe migration/backfill
- Backend: RBAC system with 4 roles and 23 permissions, seeded on startup
- Backend: workspace admin endpoints (list/manage all workspaces)
- Backend: admin user management API (CRUD, reset password)
- Backend: billing API with subscription plans, usage tracking, rate limiting
- Backend: fix system_logs.py UNION query and wrong column references
- Backend: WebSocket JWT auth and workspace enforcement
- Frontend: sidebar navigation replacing top dropdown menu
- Frontend: user management page (Users.vue) for admins
- Frontend: enhanced Workspaces.vue with admin table view
- Frontend: workspace RBAC computed properties in user store
- Android: agent marketplace, billing/subscription UI, onboarding wizard
- Android: phone login, analytics tracker, crash handler, network diagnostics
- Android: splash screen, encrypted token storage, app update enhancements
- Docs: multi-tenant RBAC guide with 8 sections and role-permission matrix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-04 01:00:22 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─── 多租户 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
|