103 lines
3.5 KiB
Python
103 lines
3.5 KiB
Python
"""权限中间件依赖。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
|
|
from fastapi import Depends, HTTPException
|
|
from sqlalchemy import and_, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from ..core.database import get_db
|
|
from ..models import Project, User, UserProject
|
|
from ..services.auth import MANAGER_ROLES, parse_role_values
|
|
from ..services.platform_access_service import has_platform_capability
|
|
from .auth import get_current_user
|
|
|
|
|
|
def _role_value(role: object) -> str:
|
|
return str(role.value if isinstance(role, Enum) else str(role or "")).strip().upper()
|
|
|
|
|
|
async def require_manager_user(
|
|
current_user: User = Depends(get_current_user),
|
|
) -> User:
|
|
"""要求当前用户是管理角色。"""
|
|
user_roles = set(parse_role_values(_role_value(current_user.role)))
|
|
if not user_roles.intersection(MANAGER_ROLES):
|
|
raise HTTPException(status_code=403, detail="Manager permission required")
|
|
return current_user
|
|
|
|
|
|
async def require_self_or_manager(
|
|
user_id: int,
|
|
current_user: User = Depends(get_current_user),
|
|
) -> User:
|
|
"""要求“本人或管理角色”。"""
|
|
user_roles = set(parse_role_values(_role_value(current_user.role)))
|
|
if int(current_user.id) == int(user_id) or user_roles.intersection(MANAGER_ROLES):
|
|
return current_user
|
|
raise HTTPException(status_code=403, detail="No permission")
|
|
|
|
|
|
async def require_project_member_or_manager(
|
|
project_id: str,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
) -> User:
|
|
"""
|
|
要求为项目成员或管理角色。
|
|
|
|
适用于所有带 `project_id` 的 mutation 端点。
|
|
"""
|
|
user_roles = set(parse_role_values(_role_value(current_user.role)))
|
|
if user_roles.intersection(MANAGER_ROLES):
|
|
return current_user
|
|
|
|
try:
|
|
pid = int(project_id)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail="Invalid project_id") from exc
|
|
|
|
access = await db.execute(
|
|
select(Project.id)
|
|
.outerjoin(
|
|
UserProject,
|
|
and_(
|
|
UserProject.tenant_id == Project.tenant_id,
|
|
UserProject.project_id == Project.id,
|
|
UserProject.user_id == int(current_user.id),
|
|
),
|
|
)
|
|
.where(
|
|
and_(
|
|
Project.tenant_id == int(current_user.tenant_id),
|
|
Project.id == pid,
|
|
(Project.owner_id == int(current_user.id)) | (UserProject.user_id.is_not(None)),
|
|
)
|
|
)
|
|
)
|
|
if access.scalar_one_or_none() is None:
|
|
raise HTTPException(status_code=403, detail="Project membership required")
|
|
return current_user
|
|
|
|
|
|
def require_platform_capability(capability: str):
|
|
"""Require a PMFlow platform capability, not a customer workspace role."""
|
|
|
|
async def checker(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
) -> User:
|
|
if await has_platform_capability(db, user=current_user, capability=capability):
|
|
return current_user
|
|
raise HTTPException(status_code=403, detail=f"Platform capability required: {capability}")
|
|
|
|
return checker
|
|
|
|
|
|
require_platform_admin_access = require_platform_capability("platform.admin.access")
|
|
require_platform_ai_ops = require_platform_capability("platform.ai_routing.manage")
|
|
require_platform_route_governance = require_platform_capability("platform.route_governance.manage")
|
|
require_platform_semantic_facts = require_platform_capability("platform.semantic_facts.manage")
|