Implémentation d'un middleware d'audit avec Django

Description du besoin

Mettre en place un middleware qui intercepte toutes les requêtes pour les valider et enregistrer les informations d'accès associées. Principalement utilisé à des fins d'audit, il doit consigner les données utilisateur, les ressources consultées, la méthode de requête ainsi que le résultat de la requête.

Réalisation

On combine les méthodes process_request et process_response du middleware pour capturer les informations nécessaires.

Les informations de requête sont extraites via les attributs Django suivants :

request.path          # URL demandée
request.user          # Utilisateur connecté
request.META['REMOTE_ADDR']  # Adresse IP du client
request.GET           # Paramètres GET
request.body          # Corps de la requête POST
response.status_code  # Code de statut HTTP
response.reason_phrase  # Message de statut

Problèmes rencontrés

On peut rencontrer des erreurs comme :

'WSGIRequest' object has no attribute 'data'

Dans les vues, request.data permet d'accéder aux paramètres POST, mais ce n'est pas disponible dans le middleware.

You cannot access body after reading from request's data stream

Cette erreur est plus complexe : elle indique qu'il n'est pas permis d'accéder directement au corps après l'avoir déjà lu. Une solution simple consiste à copier les données dans une nouvelle variable.

corps_requete = getattr(request, '_body', request.body)
print(corps_requete)

Note : cette astuce ne fonctionne que dans process_request.

Solution complète

Les données partagées entre les méthodes du middleware se trouvent dans le même contexte d'instance. Il suffit donc de stocker temporairement le corps de la requête dans une variable d'instance, puis de la transmettre.

Code détaillé

class AuditMiddleware(MiddlewareMixin):
    _corps_request = None

    def process_request(self, request):
        self._corps_request = None
        if request.method != "GET":
            raw_body = getattr(request, '_body', request.body)
            try:
                self._corps_request = eval(raw_body.decode("utf-8"))
            except:
                self._corps_request = raw_body.decode("utf-8")
            print(self._corps_request)

    def enregistrer_audit(self, request, response):
        corps = self._corps_request
        Audit.objects.create(
            chemin=request.path,
            methode=request.method,
            utilisateur=request.user,
            parametres=request.GET if request.method == "GET" else corps,
            ip=request.META['REMOTE_ADDR'],
            statut=response.status_code,
            raison=response.reason_phrase,
        )
        self._corps_request = None  # Nettoyage après utilisation

    def process_response(self, request, response):
        self.enregistrer_audit(request, response)
        return response

Remarque sur le cycle de vie

Il n'est pas clair si le middleware est instancié pour chaque requête ou s'il est partagé (signleton). Pour éviter que les données d'une requête POST précédente ne polluent la suivante, on réinitialise systématiquement la variable après utilisation.

Étiquettes: Django middleware Audit WSGI process_request

Publié le 27 juin à 20h16