From 20e2d41563a1f38c4c36b82a434080b3666d5148 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <yohann.danello@gmail.com>
Date: Tue, 10 Mar 2020 00:01:40 +0100
Subject: [PATCH] Use a middleware rather than inspect the stack to get current
 user and IP

---
 apps/logs/middlewares.py       | 55 ++++++++++++++++++++++++++++++++++
 apps/logs/signals.py           | 45 ++++------------------------
 note_kfet/settings/__init__.py |  6 +++-
 3 files changed, 65 insertions(+), 41 deletions(-)
 create mode 100644 apps/logs/middlewares.py

diff --git a/apps/logs/middlewares.py b/apps/logs/middlewares.py
new file mode 100644
index 00000000..69bbef92
--- /dev/null
+++ b/apps/logs/middlewares.py
@@ -0,0 +1,55 @@
+# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
+from threading import local
+
+
+USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user')
+IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip')
+
+_thread_locals = local()
+
+
+def _set_current_user_and_ip(user=None, ip=None):
+    """
+    Sets current user in local thread.
+    Can be used as a hook e.g. for shell jobs (when request object is not
+    available).
+    """
+    setattr(_thread_locals, USER_ATTR_NAME, user)
+    setattr(_thread_locals, IP_ATTR_NAME, ip)
+
+
+def get_current_user():
+    return getattr(_thread_locals, USER_ATTR_NAME, None)
+
+
+def get_current_ip():
+    return getattr(_thread_locals, IP_ATTR_NAME, None)
+
+
+def get_current_authenticated_user():
+    current_user = get_current_user()
+    if isinstance(current_user, AnonymousUser):
+        return None
+    return current_user
+
+
+class LogsMiddleware(object):
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        user = request.user
+        if 'HTTP_X_FORWARDED_FOR' in request.META:
+            ip = request.META.get('HTTP_X_FORWARDED_FOR')
+        else:
+            ip = request.META.get('REMOTE_ADDR')
+
+        _set_current_user_and_ip(user, ip)
+
+        response = self.get_response(request)
+
+        return response
diff --git a/apps/logs/signals.py b/apps/logs/signals.py
index 415e7c1c..e4e47e18 100644
--- a/apps/logs/signals.py
+++ b/apps/logs/signals.py
@@ -1,48 +1,15 @@
 # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
-import inspect
-
 from django.contrib.contenttypes.models import ContentType
 from django.core import serializers
 from django.db.models.signals import pre_save, post_save, post_delete
 from django.dispatch import receiver
 
+from .middlewares import get_current_authenticated_user, get_current_ip
 from .models import Changelog
 
 
-def get_request_in_signal(sender):
-    req = None
-    for entry in reversed(inspect.stack()):
-        try:
-            req = entry[0].f_locals['request']
-            # Check if there is a user
-            # noinspection PyStatementEffect
-            req.user
-            break
-        except:
-            pass
-
-    if not req:
-        print("WARNING: Attempt to save " + str(sender) + " with no user")
-
-    return req
-
-
-def get_user_and_ip(sender):
-    req = get_request_in_signal(sender)
-    try:
-        user = req.user
-        if 'HTTP_X_FORWARDED_FOR' in req.META:
-            ip = req.META.get('HTTP_X_FORWARDED_FOR')
-        else:
-            ip = req.META.get('REMOTE_ADDR')
-    except:
-        user = None
-        ip = None
-    return user, ip
-
-
 EXCLUDED = [
     'admin.logentry',
     'authtoken.token',
@@ -75,13 +42,11 @@ def save_object(sender, instance, **kwargs):
     if instance._meta.label_lower in EXCLUDED:
         return
 
-    previous = instance._previous
+    print("LOGGING SOMETHING")
 
-    user, ip = get_user_and_ip(sender)
+    previous = instance._previous
 
-    from django.contrib.auth.models import AnonymousUser
-    if isinstance(user, AnonymousUser):
-        user = None
+    user, ip = get_current_authenticated_user(), get_current_ip()
 
     if user is not None and instance._meta.label_lower == "auth.user" and previous:
         # Don't save last login modifications
@@ -111,7 +76,7 @@ def delete_object(sender, instance, **kwargs):
     if instance._meta.label_lower in EXCLUDED:
         return
 
-    user, ip = get_user_and_ip(sender)
+    user, ip = get_current_authenticated_user(), get_current_ip()
 
     instance_json = serializers.serialize('json', [instance, ])[1:-1]
     Changelog.objects.create(user=user,
diff --git a/note_kfet/settings/__init__.py b/note_kfet/settings/__init__.py
index c1df7477..b370a430 100644
--- a/note_kfet/settings/__init__.py
+++ b/note_kfet/settings/__init__.py
@@ -73,7 +73,11 @@ if "cas" in INSTALLED_APPS:
         'cas_explained',
     ]
     AUTHENTICATION_BACKENDS += ('cas.backends.CASBackend',)
-    
+
+
+if "logs" in INSTALLED_APPS:
+    MIDDLEWARE += ('logs.middlewares.LogsMiddleware',)
+
 if "debug_toolbar" in INSTALLED_APPS:
     MIDDLEWARE.insert(1,"debug_toolbar.middleware.DebugToolbarMiddleware")
     INTERNAL_IPS = [ '127.0.0.1']
-- 
GitLab