mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 20:55:41 +02:00
153 lines
5.9 KiB
Python
153 lines
5.9 KiB
Python
"""
|
||
Servizio TenantSettings – lettura e aggiornamento configurazione per-tenant.
|
||
|
||
Responsabilità:
|
||
- Upsert della riga settings (crea se non esiste, aggiorna altrimenti)
|
||
- Cifratura/decifratura AES-256-GCM delle credenziali conservatore
|
||
- Validazione di consistenza (produzione richiede endpoint + credenziali)
|
||
"""
|
||
|
||
import uuid
|
||
|
||
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.core.exceptions import BadRequestError
|
||
from app.core.security import decrypt_credential, encrypt_credential
|
||
from app.models.tenant_settings import TenantSettings
|
||
from app.schemas.tenant_settings import TenantSettingsResponse, TenantSettingsUpdate
|
||
|
||
|
||
class TenantSettingsService:
|
||
def __init__(self, db: AsyncSession) -> None:
|
||
self.db = db
|
||
|
||
# ─── Lettura (o creazione defaults) ──────────────────────────────────────
|
||
|
||
async def get_or_create(self, tenant_id: uuid.UUID) -> TenantSettings:
|
||
"""
|
||
Restituisce le impostazioni del tenant.
|
||
Se non esistono, crea una riga con i valori di default (mock).
|
||
"""
|
||
result = await self.db.execute(
|
||
select(TenantSettings).where(TenantSettings.tenant_id == tenant_id)
|
||
)
|
||
settings = result.scalar_one_or_none()
|
||
|
||
if settings is None:
|
||
settings = TenantSettings(
|
||
tenant_id=tenant_id,
|
||
archival_mode="mock",
|
||
conservatore_id="mock",
|
||
)
|
||
self.db.add(settings)
|
||
await self.db.flush()
|
||
|
||
return settings
|
||
|
||
# ─── Aggiornamento ────────────────────────────────────────────────────────
|
||
|
||
async def update(
|
||
self,
|
||
tenant_id: uuid.UUID,
|
||
data: TenantSettingsUpdate,
|
||
) -> TenantSettings:
|
||
"""
|
||
Aggiornamento parziale delle impostazioni.
|
||
|
||
Regole:
|
||
- Il passaggio a 'production' è consentito solo se conservatore_endpoint
|
||
è già configurato oppure viene fornito nel body corrente.
|
||
- Le credenziali vengono cifrate prima del salvataggio.
|
||
- Una stringa vuota "" su username/password cancella la credenziale.
|
||
"""
|
||
settings = await self.get_or_create(tenant_id)
|
||
|
||
# Applica aggiornamenti campi non-credenziali
|
||
if data.archival_mode is not None:
|
||
settings.archival_mode = data.archival_mode
|
||
|
||
if data.conservatore_id is not None:
|
||
settings.conservatore_id = data.conservatore_id
|
||
|
||
if data.conservatore_endpoint is not None:
|
||
settings.conservatore_endpoint = data.conservatore_endpoint or None
|
||
|
||
if data.archival_notes is not None:
|
||
settings.archival_notes = data.archival_notes or None
|
||
|
||
# Aggiorna credenziali (cifra se fornite, cancella se stringa vuota)
|
||
if data.conservatore_username is not None:
|
||
if data.conservatore_username == "":
|
||
settings.conservatore_username_enc = None
|
||
else:
|
||
settings.conservatore_username_enc = encrypt_credential(
|
||
data.conservatore_username
|
||
)
|
||
|
||
if data.conservatore_password is not None:
|
||
if data.conservatore_password == "":
|
||
settings.conservatore_password_enc = None
|
||
else:
|
||
settings.conservatore_password_enc = encrypt_credential(
|
||
data.conservatore_password
|
||
)
|
||
|
||
# Validazione: produzione richiede endpoint configurato
|
||
if settings.archival_mode == "production":
|
||
if not settings.conservatore_endpoint:
|
||
raise BadRequestError(
|
||
"La modalità produzione richiede un endpoint conservatore valido. "
|
||
"Inserisci l'URL prima di attivare la modalità produzione."
|
||
)
|
||
|
||
await self.db.flush()
|
||
return settings
|
||
|
||
# ─── Costruzione response ─────────────────────────────────────────────────
|
||
|
||
@staticmethod
|
||
def to_response(settings: TenantSettings) -> TenantSettingsResponse:
|
||
"""
|
||
Converte il modello in DTO di risposta.
|
||
Le credenziali NON vengono mai esposte in chiaro.
|
||
"""
|
||
return TenantSettingsResponse(
|
||
id=settings.id,
|
||
tenant_id=settings.tenant_id,
|
||
archival_mode=settings.archival_mode, # type: ignore[arg-type]
|
||
conservatore_id=settings.conservatore_id,
|
||
conservatore_endpoint=settings.conservatore_endpoint,
|
||
conservatore_username_configured=settings.conservatore_username_enc is not None,
|
||
conservatore_password_configured=settings.conservatore_password_enc is not None,
|
||
archival_notes=settings.archival_notes,
|
||
created_at=settings.created_at,
|
||
updated_at=settings.updated_at,
|
||
)
|
||
|
||
# ─── Helper per il worker (recupera credenziali decifrate) ───────────────
|
||
|
||
async def get_conservatore_credentials(
|
||
self, tenant_id: uuid.UUID
|
||
) -> dict:
|
||
"""
|
||
Restituisce le credenziali del conservatore decifrate.
|
||
Usato dal worker per effettuare versamenti.
|
||
"""
|
||
settings = await self.get_or_create(tenant_id)
|
||
return {
|
||
"mode": settings.archival_mode,
|
||
"conservatore_id": settings.conservatore_id,
|
||
"endpoint": settings.conservatore_endpoint,
|
||
"username": (
|
||
decrypt_credential(settings.conservatore_username_enc)
|
||
if settings.conservatore_username_enc
|
||
else None
|
||
),
|
||
"password": (
|
||
decrypt_credential(settings.conservatore_password_enc)
|
||
if settings.conservatore_password_enc
|
||
else None
|
||
),
|
||
}
|