django-auditlog
django-auditlog copied to clipboard
X_Forwarded_For format is not guaranteed -> problems in Azure Web App
Hello!
At https://github.com/jjkester/django-auditlog/blob/bcf1dbf0bf1781553877c4e806bab85dde96673e/src/auditlog/middleware.py#L41-L42 you read X-Forwarded-For if it is present and use it as an IP. Unfortunately at least Azure Web App Service also appends a colon separated port number. Ie. the format is X-Forwarded-For: {ip_address}:{port_number} which then breaks logging at database insertion time.
Might you consider adding option for using different headers for this purpose, and/or disabling this entirely? FWIW, Azure implements a "X-Client-IP" headers containing only the IP-address.
I duplicated the middleware to fix this issue, might help someone in the future.
Couldn't just override as threading.local() stopped working that way.
import threading
import time
from functools import partial
from auditlog.models import LogEntry
from django.apps import apps
from django.conf import settings
from django.db.models.signals import pre_save
from django.utils.deprecation import MiddlewareMixin
threadlocal = threading.local()
class AuditlogMiddleware(MiddlewareMixin):
def process_request(self, request):
"""
Overridden to remove port numbers from remote_addr causing 500s on Azure.
"""
# Initialize thread local storage
threadlocal.auditlog = {
'signal_duid': (self.__class__, time.time()),
'remote_addr': request.META.get('REMOTE_ADDR'),
}
# In case of proxy, set 'original' address
if request.META.get('HTTP_X_FORWARDED_FOR'):
threadlocal.auditlog['remote_addr'] = \
request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0]
# Azure fix, remove port number from remote_addr
split_ip_and_port = threadlocal.auditlog['remote_addr'].split(':')
if len(split_ip_and_port) == 2:
threadlocal.auditlog['remote_addr'] = split_ip_and_port[0]
# Connect signal for automatic logging
if hasattr(request, 'user') and getattr(request.user, 'is_authenticated', False):
set_actor = partial(
self.set_actor,
user=request.user,
signal_duid=threadlocal.auditlog['signal_duid']
)
pre_save.connect(
set_actor,
sender=LogEntry,
dispatch_uid=threadlocal.auditlog['signal_duid'],
weak=False
)
def process_response(self, request, response):
"""
Disconnects the signal receiver to prevent it from staying active.
"""
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
return response
def process_exception(self, request, exception):
"""
Disconnects the signal receiver to prevent it from staying active in case of an exception.
"""
if hasattr(threadlocal, 'auditlog'):
pre_save.disconnect(sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'])
return None
@staticmethod
def set_actor(user, sender, instance, signal_duid, **kwargs):
"""
Signal receiver with an extra, required 'user' kwarg. This method becomes a real (valid) signal receiver when
it is curried with the actor.
"""
if hasattr(threadlocal, 'auditlog'):
if signal_duid != threadlocal.auditlog['signal_duid']:
return
try:
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
auth_user_model = apps.get_model(app_label, model_name)
except ValueError:
auth_user_model = apps.get_model('auth', 'user')
if sender == LogEntry and isinstance(user, auth_user_model) and instance.actor is None:
instance.actor = user
instance.remote_addr = threadlocal.auditlog['remote_addr']
Amazing answer @maziar-dandc why can't this be pushed as contrib PR?
Amazing answer @maziar-dandc why can't this be pushed as contrib PR?
Feel free to PR it.
I think I owe it that!
fixed in https://github.com/jazzband/django-auditlog/commit/18868aaaed06131ef927e951940e654b1e20cdcd