mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
Multitenancy
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
"""
|
||||
Dependency FastAPI – get_db, get_current_user, require_admin, RLS middleware.
|
||||
Dependency FastAPI – get_db, get_current_user, get_current_tenant, role guards.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, Request
|
||||
from fastapi import Depends, Header, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from jose import JWTError
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.exceptions import ForbiddenError, TokenInvalidError
|
||||
from app.config import get_settings
|
||||
from app.core.exceptions import ForbiddenError, TenantSuspendedError, TokenInvalidError
|
||||
from app.core.security import decode_token
|
||||
from app.database import get_db
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User
|
||||
from sqlalchemy import select
|
||||
|
||||
@@ -58,7 +60,7 @@ async def get_current_user(
|
||||
) -> User:
|
||||
"""
|
||||
Estrae e valida il JWT dall'header Authorization: Bearer <token>.
|
||||
Carica l'utente dal DB e imposta RLS.
|
||||
Carica il tenant (verifica is_active), l'utente dal DB e imposta RLS.
|
||||
"""
|
||||
token = credentials.credentials
|
||||
|
||||
@@ -85,6 +87,13 @@ async def get_current_user(
|
||||
# Imposta RLS per questo tenant (no-op su SQLite/test)
|
||||
await _set_rls_tenant_id(db, tenant_id)
|
||||
|
||||
# Verifica tenant esiste e non è sospeso
|
||||
tenant = await db.get(Tenant, tenant_id)
|
||||
if not tenant:
|
||||
raise TokenInvalidError()
|
||||
if not tenant.is_active:
|
||||
raise TenantSuspendedError()
|
||||
|
||||
# Carica utente
|
||||
result = await db.execute(
|
||||
select(User).where(User.id == user_id, User.tenant_id == tenant_id)
|
||||
@@ -98,9 +107,32 @@ async def get_current_user(
|
||||
from app.core.exceptions import AccountDisabledError
|
||||
raise AccountDisabledError()
|
||||
|
||||
# Attacca il tenant all'utente per uso nei router (evita doppio caricamento)
|
||||
user._current_tenant = tenant # type: ignore[attr-defined]
|
||||
|
||||
return user
|
||||
|
||||
|
||||
# ─── Tenant corrente ──────────────────────────────────────────────────────────
|
||||
|
||||
async def get_current_tenant(
|
||||
current_user: Annotated[User, Depends(get_current_user)],
|
||||
db: AsyncSession = Depends(get_db),
|
||||
) -> Tenant:
|
||||
"""
|
||||
Restituisce l'oggetto Tenant dell'utente autenticato.
|
||||
|
||||
Il Tenant è già stato validato in get_current_user() – questa dependency
|
||||
utilizza la cache della sessione SQLAlchemy (identity map) per evitare
|
||||
un secondo accesso al DB.
|
||||
"""
|
||||
# Usa la cache della sessione ORM (non emette una seconda query)
|
||||
tenant = await db.get(Tenant, current_user.tenant_id)
|
||||
if not tenant:
|
||||
raise TokenInvalidError()
|
||||
return tenant
|
||||
|
||||
|
||||
# ─── Role guards ──────────────────────────────────────────────────────────────
|
||||
|
||||
async def require_admin(
|
||||
@@ -121,8 +153,27 @@ async def require_super_admin(
|
||||
return current_user
|
||||
|
||||
|
||||
# ─── Protezione endpoint admin con X-Admin-Key header ─────────────────────────
|
||||
|
||||
async def verify_admin_key(
|
||||
x_admin_key: str = Header(default="", alias="X-Admin-Key"),
|
||||
) -> None:
|
||||
"""
|
||||
Verifica l'header X-Admin-Key per gli endpoint di amministrazione tenant.
|
||||
Se ADMIN_SECRET_KEY non è configurata (ambiente di sviluppo), il check
|
||||
viene saltato per facilitare lo sviluppo locale.
|
||||
"""
|
||||
settings = get_settings()
|
||||
if not settings.admin_secret_key:
|
||||
# Sviluppo: nessuna chiave configurata → accesso libero
|
||||
return
|
||||
if x_admin_key != settings.admin_secret_key:
|
||||
raise ForbiddenError("X-Admin-Key non valida o mancante")
|
||||
|
||||
|
||||
# ─── Tipo annotato per ridurre boilerplate negli endpoint ─────────────────────
|
||||
CurrentUser = Annotated[User, Depends(get_current_user)]
|
||||
CurrentTenant = Annotated[Tenant, Depends(get_current_tenant)]
|
||||
AdminUser = Annotated[User, Depends(require_admin)]
|
||||
SuperAdminUser = Annotated[User, Depends(require_super_admin)]
|
||||
DB = Annotated[AsyncSession, Depends(get_db)]
|
||||
|
||||
Reference in New Issue
Block a user