From f76becfd862b27441bd186ae66d58475a63f045c Mon Sep 17 00:00:00 2001 From: "Jens L." Date: Sat, 21 Jun 2025 00:21:49 +0200 Subject: [PATCH] stages/user_login: fix session binding logging (#15175) * add tests Signed-off-by: Jens Langhammer * fix logging Signed-off-by: Jens Langhammer * update test db? Signed-off-by: Jens Langhammer * ah there we go; fix mmdb not being reloaded with test settings Signed-off-by: Jens Langhammer --------- Signed-off-by: Jens Langhammer --- Makefile | 4 ++ authentik/events/context_processors/mmdb.py | 6 +-- authentik/root/test_runner.py | 5 ++ authentik/stages/user_login/middleware.py | 22 ++++---- authentik/stages/user_login/tests.py | 57 +++++++++++++++++++- tests/GeoLite2-ASN-Test.mmdb | Bin 12653 -> 12653 bytes tests/GeoLite2-City-Test.mmdb | Bin 20809 -> 21088 bytes 7 files changed, 80 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index a8ef77e592..c1d2812223 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,10 @@ dev-create-db: dev-reset: dev-drop-db dev-create-db migrate ## Drop and restore the Authentik PostgreSQL instance to a "fresh install" state. +update-test-mmdb: ## Update test GeoIP and ASN Databases + curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb -o ${PWD}/tests/GeoLite2-ASN-Test.mmdb + curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb -o ${PWD}/tests/GeoLite2-City-Test.mmdb + ######################### ## API Schema ######################### diff --git a/authentik/events/context_processors/mmdb.py b/authentik/events/context_processors/mmdb.py index 4ba762fe84..707b294895 100644 --- a/authentik/events/context_processors/mmdb.py +++ b/authentik/events/context_processors/mmdb.py @@ -15,13 +15,13 @@ class MMDBContextProcessor(EventContextProcessor): self.reader: Reader | None = None self._last_mtime: float = 0.0 self.logger = get_logger() - self.open() + self.load() def path(self) -> str | None: """Get the path to the MMDB file to load""" raise NotImplementedError - def open(self): + def load(self): """Get GeoIP Reader, if configured, otherwise none""" path = self.path() if path == "" or not path: @@ -44,7 +44,7 @@ class MMDBContextProcessor(EventContextProcessor): diff = self._last_mtime < mtime if diff > 0: self.logger.info("Found new MMDB Database, reopening", diff=diff, path=path) - self.open() + self.load() except OSError as exc: self.logger.warning("Failed to check MMDB age", exc=exc) diff --git a/authentik/root/test_runner.py b/authentik/root/test_runner.py index 04b70a8bd9..75a5924158 100644 --- a/authentik/root/test_runner.py +++ b/authentik/root/test_runner.py @@ -11,6 +11,8 @@ from django.contrib.contenttypes.models import ContentType from django.test.runner import DiscoverRunner from structlog.stdlib import get_logger +from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR +from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR from authentik.lib.config import CONFIG from authentik.lib.sentry import sentry_init from authentik.root.signals import post_startup, pre_startup, startup @@ -76,6 +78,9 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover for key, value in test_config.items(): CONFIG.set(key, value) + ASN_CONTEXT_PROCESSOR.load() + GEOIP_CONTEXT_PROCESSOR.load() + sentry_init() self.logger.debug("Test environment configured") diff --git a/authentik/stages/user_login/middleware.py b/authentik/stages/user_login/middleware.py index 8d50ac376f..ae23433b40 100644 --- a/authentik/stages/user_login/middleware.py +++ b/authentik/stages/user_login/middleware.py @@ -101,9 +101,9 @@ class BoundSessionMiddleware(SessionMiddleware): SESSION_KEY_BINDING_GEO, GeoIPBinding.NO_BINDING ) if configured_binding_net != NetworkBinding.NO_BINDING: - self.recheck_session_net(configured_binding_net, last_ip, new_ip) + BoundSessionMiddleware.recheck_session_net(configured_binding_net, last_ip, new_ip) if configured_binding_geo != GeoIPBinding.NO_BINDING: - self.recheck_session_geo(configured_binding_geo, last_ip, new_ip) + BoundSessionMiddleware.recheck_session_geo(configured_binding_geo, last_ip, new_ip) # If we got to this point without any error being raised, we need to # update the last saved IP to the current one if SESSION_KEY_BINDING_NET in request.session or SESSION_KEY_BINDING_GEO in request.session: @@ -111,7 +111,8 @@ class BoundSessionMiddleware(SessionMiddleware): # (== basically requires the user to be logged in) request.session[request.session.model.Keys.LAST_IP] = new_ip - def recheck_session_net(self, binding: NetworkBinding, last_ip: str, new_ip: str): + @staticmethod + def recheck_session_net(binding: NetworkBinding, last_ip: str, new_ip: str): """Check network/ASN binding""" last_asn = ASN_CONTEXT_PROCESSOR.asn(last_ip) new_asn = ASN_CONTEXT_PROCESSOR.asn(new_ip) @@ -158,7 +159,8 @@ class BoundSessionMiddleware(SessionMiddleware): new_ip, ) - def recheck_session_geo(self, binding: GeoIPBinding, last_ip: str, new_ip: str): + @staticmethod + def recheck_session_geo(binding: GeoIPBinding, last_ip: str, new_ip: str): """Check GeoIP binding""" last_geo = GEOIP_CONTEXT_PROCESSOR.city(last_ip) new_geo = GEOIP_CONTEXT_PROCESSOR.city(new_ip) @@ -179,8 +181,8 @@ class BoundSessionMiddleware(SessionMiddleware): if last_geo.continent != new_geo.continent: raise SessionBindingBroken( "geoip.continent", - last_geo.continent, - new_geo.continent, + last_geo.continent.to_dict(), + new_geo.continent.to_dict(), last_ip, new_ip, ) @@ -192,8 +194,8 @@ class BoundSessionMiddleware(SessionMiddleware): if last_geo.country != new_geo.country: raise SessionBindingBroken( "geoip.country", - last_geo.country, - new_geo.country, + last_geo.country.to_dict(), + new_geo.country.to_dict(), last_ip, new_ip, ) @@ -202,8 +204,8 @@ class BoundSessionMiddleware(SessionMiddleware): if last_geo.city != new_geo.city: raise SessionBindingBroken( "geoip.city", - last_geo.city, - new_geo.city, + last_geo.city.to_dict(), + new_geo.city.to_dict(), last_ip, new_ip, ) diff --git a/authentik/stages/user_login/tests.py b/authentik/stages/user_login/tests.py index 01570c4b50..ecc342059d 100644 --- a/authentik/stages/user_login/tests.py +++ b/authentik/stages/user_login/tests.py @@ -3,6 +3,7 @@ from time import sleep from unittest.mock import patch +from django.http import HttpRequest from django.urls import reverse from django.utils.timezone import now @@ -17,7 +18,12 @@ from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.lib.generators import generate_id from authentik.lib.utils.time import timedelta_from_string from authentik.root.middleware import ClientIPMiddleware -from authentik.stages.user_login.models import UserLoginStage +from authentik.stages.user_login.middleware import ( + BoundSessionMiddleware, + SessionBindingBroken, + logout_extra, +) +from authentik.stages.user_login.models import GeoIPBinding, NetworkBinding, UserLoginStage class TestUserLoginStage(FlowTestCase): @@ -192,3 +198,52 @@ class TestUserLoginStage(FlowTestCase): self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) response = self.client.get(reverse("authentik_api:application-list")) self.assertEqual(response.status_code, 403) + + def test_binding_net_break_log(self): + """Test logout_extra with exception""" + # IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-ASN-Test.json + for args, expect in [ + [[NetworkBinding.BIND_ASN, "8.8.8.8", "8.8.8.8"], ["network.missing"]], + [[NetworkBinding.BIND_ASN, "1.0.0.1", "1.128.0.1"], ["network.asn"]], + [ + [NetworkBinding.BIND_ASN_NETWORK, "12.81.96.1", "12.81.128.1"], + ["network.asn_network"], + ], + [[NetworkBinding.BIND_ASN_NETWORK_IP, "1.0.0.1", "1.0.0.2"], ["network.ip"]], + ]: + with self.subTest(args[0]): + with self.assertRaises(SessionBindingBroken) as cm: + BoundSessionMiddleware.recheck_session_net(*args) + self.assertEqual(cm.exception.reason, expect[0]) + # Ensure the request can be logged without throwing errors + self.client.force_login(self.user) + request = HttpRequest() + request.session = self.client.session + request.user = self.user + logout_extra(request, cm.exception) + + def test_binding_geo_break_log(self): + """Test logout_extra with exception""" + # IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json + for args, expect in [ + [[GeoIPBinding.BIND_CONTINENT, "8.8.8.8", "8.8.8.8"], ["geoip.missing"]], + [[GeoIPBinding.BIND_CONTINENT, "2.125.160.216", "67.43.156.1"], ["geoip.continent"]], + [ + [GeoIPBinding.BIND_CONTINENT_COUNTRY, "81.2.69.142", "89.160.20.112"], + ["geoip.country"], + ], + [ + [GeoIPBinding.BIND_CONTINENT_COUNTRY_CITY, "2.125.160.216", "81.2.69.142"], + ["geoip.city"], + ], + ]: + with self.subTest(args[0]): + with self.assertRaises(SessionBindingBroken) as cm: + BoundSessionMiddleware.recheck_session_geo(*args) + self.assertEqual(cm.exception.reason, expect[0]) + # Ensure the request can be logged without throwing errors + self.client.force_login(self.user) + request = HttpRequest() + request.session = self.client.session + request.user = self.user + logout_extra(request, cm.exception) diff --git a/tests/GeoLite2-ASN-Test.mmdb b/tests/GeoLite2-ASN-Test.mmdb index 3e5033144f505227ef872af685c3664a6d45d03b..28441ad472665eb45f7558b501c6c63c30649165 100644 GIT binary patch delta 16 YcmaEx^fqb3UIUg4?lsJl4;rii07ftek^lez delta 16 YcmaEx^fqb3UIUiYG~ch24;rii0814JeE- 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