"""权限中间件依赖。""" 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")