From dcc28d89b399645c52318e38da6279aa363235c9 Mon Sep 17 00:00:00 2001 From: Cleiton Lima Date: Tue, 1 Oct 2024 11:03:49 -0300 Subject: [PATCH] Added remote port --- .gitignore | 1 + CHANGELOG.md | 3 +++ auditlog/context.py | 4 +++- auditlog/middleware.py | 16 +++++++++++++++- .../migrations/0016_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/0016_logentry_remote_port.py diff --git a/.gitignore b/.gitignore index 155da2b3..96288a16 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ venv.bak/ ### JetBrains .idea/ +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b6e5233b..5063f3c3 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 a problem when setting `Value(None)` in `JSONField` ([#646](https://github.com/jazzband/django-auditlog/pull/646)) diff --git a/auditlog/context.py b/auditlog/context.py index 644c6ce5..ecb034c7 100644 --- a/auditlog/context.py +++ b/auditlog/context.py @@ -13,12 +13,13 @@ @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 c47666bb..bd01da39 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 @@ def _get_remote_addr(request): 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 @@ def _get_actor(request): 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/0016_logentry_remote_port.py b/auditlog/migrations/0016_logentry_remote_port.py new file mode 100644 index 00000000..805d15aa --- /dev/null +++ b/auditlog/migrations/0016_logentry_remote_port.py @@ -0,0 +1,17 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("auditlog", "0015_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 1ae8dbcc..a0b402cf 100644 --- a/auditlog/models.py +++ b/auditlog/models.py @@ -373,6 +373,9 @@ class Action: 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 0ff960b4..8f8b825f 100644 --- a/auditlog_tests/tests.py +++ b/auditlog_tests/tests.py @@ -585,6 +585,13 @@ def test_get_remote_addr(self): 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("-", "_") @@ -622,9 +629,10 @@ def test_set_actor_anonymous_request(self): 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() @@ -633,6 +641,11 @@ def test_set_actor_anonymous_request(self): 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):