""" 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()