From 6cfd57c31fccbb2a82350aeecdbdec6ba3064770 Mon Sep 17 00:00:00 2001 From: Cleiton de Lima Date: Mon, 7 Oct 2024 10:52:34 -0300 Subject: [PATCH] Added remote port (#671) --- .gitignore | 1 + CHANGELOG.md | 3 +++ auditlog/context.py | 4 +++- auditlog/middleware.py | 16 +++++++++++++++- .../migrations/0018_logentry_remote_port.py | 17 +++++++++++++++++ auditlog/models.py | 3 +++ auditlog_tests/tests.py | 15 ++++++++++++++- 7 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 auditlog/migrations/0018_logentry_remote_port.py diff --git a/.gitignore b/.gitignore index 155da2b..96288a1 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ venv.bak/ ### JetBrains .idea/ +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 07427ee..ad6760e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Next Release +#### Improvements +- feat: Added `LogEntry.remote_port` field. ([#671](https://github.com/jazzband/django-auditlog/pull/671)) + #### Fixes - Fixed problem when setting `django.db.models.functions.Now()` in `DateTimeField` ([#635](https://github.com/jazzband/django-auditlog/pull/635)) diff --git a/auditlog/context.py b/auditlog/context.py index 644c6ce..ecb034c 100644 --- a/auditlog/context.py +++ b/auditlog/context.py @@ -13,12 +13,13 @@ auditlog_disabled = ContextVar("auditlog_disabled", default=False) @contextlib.contextmanager -def set_actor(actor, remote_addr=None): +def set_actor(actor, remote_addr=None, remote_port=None): """Connect a signal receiver with current user attached.""" # Initialize thread local storage context_data = { "signal_duid": ("set_actor", time.time()), "remote_addr": remote_addr, + "remote_port": remote_port, } auditlog_value.set(context_data) @@ -63,6 +64,7 @@ def _set_actor(user, sender, instance, signal_duid, **kwargs): instance.actor = user instance.remote_addr = auditlog["remote_addr"] + instance.remote_port = auditlog["remote_port"] @contextlib.contextmanager diff --git a/auditlog/middleware.py b/auditlog/middleware.py index c47666b..bd01da3 100644 --- a/auditlog/middleware.py +++ b/auditlog/middleware.py @@ -1,3 +1,5 @@ +from typing import Optional + from django.conf import settings from django.contrib.auth import get_user_model @@ -36,6 +38,17 @@ class AuditlogMiddleware: return remote_addr + @staticmethod + def _get_remote_port(request) -> Optional[int]: + remote_port = request.headers.get("X-Forwarded-Port", "") + + try: + remote_port = int(remote_port) + except ValueError: + remote_port = None + + return remote_port + @staticmethod def _get_actor(request): user = getattr(request, "user", None) @@ -45,9 +58,10 @@ class AuditlogMiddleware: def __call__(self, request): remote_addr = self._get_remote_addr(request) + remote_port = self._get_remote_port(request) user = self._get_actor(request) set_cid(request) - with set_actor(actor=user, remote_addr=remote_addr): + with set_actor(actor=user, remote_addr=remote_addr, remote_port=remote_port): return self.get_response(request) diff --git a/auditlog/migrations/0018_logentry_remote_port.py b/auditlog/migrations/0018_logentry_remote_port.py new file mode 100644 index 0000000..0bfeaef --- /dev/null +++ b/auditlog/migrations/0018_logentry_remote_port.py @@ -0,0 +1,17 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("auditlog", "0017_alter_logentry_changes"), + ] + + operations = [ + migrations.AddField( + model_name="logentry", + name="remote_port", + field=models.PositiveIntegerField( + blank=True, null=True, verbose_name="remote port" + ), + ), + ] diff --git a/auditlog/models.py b/auditlog/models.py index c34469a..66eee71 100644 --- a/auditlog/models.py +++ b/auditlog/models.py @@ -358,6 +358,9 @@ class LogEntry(models.Model): remote_addr = models.GenericIPAddressField( blank=True, null=True, verbose_name=_("remote address") ) + remote_port = models.PositiveIntegerField( + blank=True, null=True, verbose_name=_("remote port") + ) timestamp = models.DateTimeField( default=django_timezone.now, db_index=True, diff --git a/auditlog_tests/tests.py b/auditlog_tests/tests.py index 3e4209a..228ddd7 100644 --- a/auditlog_tests/tests.py +++ b/auditlog_tests/tests.py @@ -537,6 +537,13 @@ class MiddlewareTest(TestCase): self.middleware._get_remote_addr(request), expected_remote_addr ) + def test_get_remote_port(self): + headers = { + "HTTP_X_FORWARDED_PORT": "12345", + } + request = self.factory.get("/", **headers) + self.assertEqual(self.middleware._get_remote_port(request), 12345) + def test_cid(self): header = str(settings.AUDITLOG_CID_HEADER).lstrip("HTTP_").replace("_", "-") header_meta = "HTTP_" + header.upper().replace("-", "_") @@ -574,9 +581,10 @@ class MiddlewareTest(TestCase): The remote address will be set even when there is no actor """ remote_addr = "123.213.145.99" + remote_port = 12345 actor = None - with set_actor(actor=actor, remote_addr=remote_addr): + with set_actor(actor=actor, remote_addr=remote_addr, remote_port=remote_port): obj = SimpleModel.objects.create(text="I am not difficult.") history = obj.history.get() @@ -585,6 +593,11 @@ class MiddlewareTest(TestCase): remote_addr, msg=f"Remote address is {remote_addr}", ) + self.assertEqual( + history.remote_port, + remote_port, + msg=f"Remote port is {remote_port}", + ) self.assertIsNone(history.actor, msg="Actor is `None` for anonymous user") def test_get_actor(self):