loko/streetup/common/middleware.py
2026-07-22 14:48:40 +02:00

208 lines
7.1 KiB
Python

"""
Middleware pour la gestion de la déconnexion automatique des utilisateurs.
Ce middleware vérifie si l'utilisateur connecté a une configuration de
déconnexion automatique (auto_logout_hours) et le déconnecte si le temps
depuis sa dernière connexion dépasse cette durée.
Typiquement utilisé pour les utilisateurs qui partagent un ordinateur
en équipes de travail (shifts).
"""
from django.contrib.auth import logout
from django.shortcuts import redirect
from django.utils import timezone
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
import traceback
import sys
# Clé de session pour stocker l'heure de connexion
SESSION_LOGIN_TIME_KEY = 'login_timestamp'
class RequestLoggingMiddleware:
"""
Middleware qui capture les erreurs HTTP (4xx, 5xx) et les exceptions
non gérées pour les enregistrer dans la base de données.
Cela permet de visualiser les erreurs dans l'admin Django.
"""
# URLs à ignorer pour éviter de logger des requêtes non pertinentes
IGNORED_PATHS = [
'/static/',
'/media/',
'/favicon.ico',
'/__debug__/',
]
# Codes de statut à logger
LOGGED_STATUS_CODES = [400, 401, 403, 404, 405, 500, 502, 503, 504]
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Vérifier si cette URL doit être ignorée
if self._should_ignore(request):
return self.get_response(request)
response = None
try:
response = self.get_response(request)
# Logger les erreurs HTTP (4xx, 5xx)
if response.status_code in self.LOGGED_STATUS_CODES:
self._log_request(
request,
status_code=response.status_code,
level=self._get_log_level(response.status_code)
)
return response
except Exception as exc:
# Logger les exceptions non gérées
self._log_request(
request,
exception=exc,
level='CRITICAL',
status_code=500,
traceback_str=traceback.format_exc()
)
raise # Re-raise l'exception pour que Django la gère
def process_exception(self, request, exception):
"""
Appelé quand une vue lève une exception.
"""
if self._should_ignore(request):
return None
self._log_request(
request,
exception=exception,
level='ERROR',
status_code=500,
traceback_str=traceback.format_exc()
)
return None # Laisser Django gérer l'exception
def _should_ignore(self, request):
"""Vérifie si la requête doit être ignorée."""
for path in self.IGNORED_PATHS:
if request.path.startswith(path):
return True
return False
def _get_log_level(self, status_code):
"""Retourne le niveau de log approprié selon le code de statut."""
if status_code >= 500:
return 'ERROR'
elif status_code == 404:
return 'WARNING'
elif status_code >= 400:
return 'WARNING'
return 'INFO'
def _log_request(self, request, exception=None, level='ERROR', status_code=None, traceback_str=None):
"""Enregistre le log dans la base de données."""
try:
from common.models import RequestLog
RequestLog.log_request(
request,
exception=exception,
level=level,
status_code=status_code,
traceback_str=traceback_str
)
except Exception as e:
# En cas d'erreur lors de l'enregistrement du log, ne pas faire planter la requête
# On peut quand même logger dans le fichier standard
import logging
logger = logging.getLogger('django')
logger.error(f"Erreur lors de l'enregistrement du log: {e}")
class AutoLogoutMiddleware:
"""
Middleware qui déconnecte automatiquement les utilisateurs après un
certain nombre d'heures configuré dans leur UserConfig.
Si l'utilisateur a auto_logout_hours défini dans son UserConfig,
il sera automatiquement déconnecté après ce nombre d'heures depuis
sa dernière connexion.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Ne vérifier que pour les utilisateurs authentifiés
if request.user.is_authenticated:
# Enregistrer l'heure de connexion si pas encore fait
if SESSION_LOGIN_TIME_KEY not in request.session:
request.session[SESSION_LOGIN_TIME_KEY] = timezone.now().isoformat()
# Vérifier si l'utilisateur doit être déconnecté
if self._should_logout(request):
# Stocker un message avant la déconnexion
messages.info(
request,
_("Votre session a expiré après le délai configuré. Veuillez vous reconnecter.")
)
logout(request)
return redirect('login')
response = self.get_response(request)
return response
def _should_logout(self, request):
"""
Vérifie si l'utilisateur doit être déconnecté en fonction de
son auto_logout_hours configuré.
"""
try:
# Récupérer la configuration utilisateur
user_config = getattr(request.user, 'config', None)
if not user_config:
return False
# Vérifier si auto_logout_hours est défini
auto_logout_hours = user_config.auto_logout_hours
if not auto_logout_hours:
return False
# Récupérer l'heure de connexion depuis la session
login_time_str = request.session.get(SESSION_LOGIN_TIME_KEY)
if not login_time_str:
return False
# Parser l'heure de connexion
from datetime import datetime
login_time = datetime.fromisoformat(login_time_str)
if timezone.is_naive(login_time):
login_time = timezone.make_aware(login_time)
# Calculer la durée depuis la connexion
now = timezone.now()
hours_since_login = (now - login_time).total_seconds() / 3600
# Déconnecter si le temps dépasse la limite
return hours_since_login >= auto_logout_hours
except Exception:
# En cas d'erreur, ne pas déconnecter
return False
def set_login_time(sender, user, request, **kwargs):
"""
Signal handler pour enregistrer l'heure de connexion quand un
utilisateur se connecte.
À connecter au signal user_logged_in de Django.
"""
request.session[SESSION_LOGIN_TIME_KEY] = timezone.now().isoformat()