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.