Remove local auth and user-management endpoints in favor of Keycloak-backed metadata users, project context, admin metadata APIs, and agent auth context.
105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
from dataclasses import dataclass
|
|
from uuid import UUID
|
|
|
|
import logging
|
|
from fastapi import Depends, HTTPException, status
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.auth.keycloak_dependencies import get_current_keycloak_payload
|
|
from app.infra.db.metadb.database import get_metadata_session
|
|
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def get_metadata_repository(
|
|
session: AsyncSession = Depends(get_metadata_session),
|
|
) -> MetadataRepository:
|
|
return MetadataRepository(session)
|
|
|
|
|
|
def _keycloak_sub_from_payload(payload: dict) -> UUID:
|
|
sub = payload.get("sub")
|
|
if not sub:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Missing subject claim",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
try:
|
|
return UUID(str(sub))
|
|
except ValueError as exc:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid subject claim",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
) from exc
|
|
|
|
|
|
def _username_from_payload(payload: dict) -> str | None:
|
|
username = payload.get("preferred_username") or payload.get("username")
|
|
return str(username) if username else None
|
|
|
|
|
|
def _email_from_payload(payload: dict) -> str | None:
|
|
email = payload.get("email")
|
|
return str(email) if email else None
|
|
|
|
|
|
async def get_current_metadata_user(
|
|
keycloak_payload: dict = Depends(get_current_keycloak_payload),
|
|
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
|
|
):
|
|
keycloak_sub = _keycloak_sub_from_payload(keycloak_payload)
|
|
try:
|
|
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
|
|
except SQLAlchemyError as exc:
|
|
logger.error(
|
|
"Metadata DB error while resolving current user",
|
|
exc_info=True,
|
|
)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail=f"Metadata database error: {exc}",
|
|
) from exc
|
|
if not user or not user.is_active:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
|
|
)
|
|
try:
|
|
user = await metadata_repo.refresh_user_keycloak_snapshot(
|
|
user,
|
|
username=_username_from_payload(keycloak_payload),
|
|
email=_email_from_payload(keycloak_payload),
|
|
)
|
|
except SQLAlchemyError as exc:
|
|
logger.error(
|
|
"Metadata DB error while refreshing current user snapshot",
|
|
exc_info=True,
|
|
)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail=f"Metadata database error: {exc}",
|
|
) from exc
|
|
return user
|
|
|
|
|
|
async def get_current_metadata_admin(
|
|
user=Depends(get_current_metadata_user),
|
|
):
|
|
if user.is_superuser or user.role == "admin":
|
|
return user
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required"
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class _AuthBypassUser:
|
|
id: UUID = UUID(int=0)
|
|
role: str = "admin"
|
|
is_superuser: bool = True
|
|
is_active: bool = True
|