From 19324c61a34c09a193038f7d40db3cdc30382468 Mon Sep 17 00:00:00 2001 From: "Jens L." Date: Mon, 23 Jun 2025 15:17:48 +0200 Subject: [PATCH] root: add system check for database encoding (#15186) * root: add system check for database encoding Signed-off-by: Jens Langhammer * oops Signed-off-by: Jens Langhammer --------- Signed-off-by: Jens Langhammer --- authentik/events/tests/test_enrich_geoip.py | 14 ++++++-- authentik/root/db/base.py | 36 +++++++++++++++++++++ authentik/tenants/checks.py | 1 + lifecycle/migrate.py | 12 ++++--- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/authentik/events/tests/test_enrich_geoip.py b/authentik/events/tests/test_enrich_geoip.py index 83a8391558..4a01cbaa6e 100644 --- a/authentik/events/tests/test_enrich_geoip.py +++ b/authentik/events/tests/test_enrich_geoip.py @@ -2,7 +2,9 @@ from django.test import TestCase +from authentik.events.context_processors.base import get_context_processors from authentik.events.context_processors.geoip import GeoIPContextProcessor +from authentik.events.models import Event, EventAction class TestGeoIP(TestCase): @@ -13,8 +15,7 @@ class TestGeoIP(TestCase): def test_simple(self): """Test simple city wrapper""" - # IPs from - # https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json + # IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json self.assertEqual( self.reader.city_dict("2.125.160.216"), { @@ -25,3 +26,12 @@ class TestGeoIP(TestCase): "long": -1.25, }, ) + + def test_special_chars(self): + """Test city name with special characters""" + # IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json + event = Event.new(EventAction.LOGIN) + event.client_ip = "89.160.20.112" + for processor in get_context_processors(): + processor.enrich_event(event) + event.save() diff --git a/authentik/root/db/base.py b/authentik/root/db/base.py index 2385e1b972..48688206a7 100644 --- a/authentik/root/db/base.py +++ b/authentik/root/db/base.py @@ -1,13 +1,49 @@ """authentik database backend""" +from django.core.checks import Warning +from django.db.backends.base.validation import BaseDatabaseValidation from django_tenants.postgresql_backend.base import DatabaseWrapper as BaseDatabaseWrapper from authentik.lib.config import CONFIG +class DatabaseValidation(BaseDatabaseValidation): + + def check(self, **kwargs): + return self._check_encoding() + + def _check_encoding(self): + """Throw a warning when the server_encoding is not UTF-8 or + server_encoding and client_encoding are mismatched""" + messages = [] + with self.connection.cursor() as cursor: + cursor.execute("SHOW server_encoding;") + server_encoding = cursor.fetchone()[0] + cursor.execute("SHOW client_encoding;") + client_encoding = cursor.fetchone()[0] + if server_encoding != client_encoding: + messages.append( + Warning( + "PostgreSQL Server and Client encoding are mismatched: Server: " + f"{server_encoding}, Client: {client_encoding}", + id="ak.db.W001", + ) + ) + if server_encoding != "UTF8": + messages.append( + Warning( + f"PostgreSQL Server encoding is not UTF8: {server_encoding}", + id="ak.db.W002", + ) + ) + return messages + + class DatabaseWrapper(BaseDatabaseWrapper): """database backend which supports rotating credentials""" + validation_class = DatabaseValidation + def get_connection_params(self): """Refresh DB credentials before getting connection params""" conn_params = super().get_connection_params() diff --git a/authentik/tenants/checks.py b/authentik/tenants/checks.py index 72c7228f51..a1c6ffdcd8 100644 --- a/authentik/tenants/checks.py +++ b/authentik/tenants/checks.py @@ -16,6 +16,7 @@ def check_embedded_outpost_disabled(app_configs, **kwargs): "Embedded outpost must be disabled when tenants API is enabled.", hint="Disable embedded outpost by setting outposts.disable_embedded_outpost to " "True, or disable the tenants API by setting tenants.enabled to False", + id="ak.tenants.E001", ) ] return [] diff --git a/lifecycle/migrate.py b/lifecycle/migrate.py index f5787a9021..6b77edde90 100755 --- a/lifecycle/migrate.py +++ b/lifecycle/migrate.py @@ -10,7 +10,7 @@ from typing import Any from psycopg import Connection, Cursor, connect from structlog.stdlib import get_logger -from authentik.lib.config import CONFIG +from authentik.lib.config import CONFIG, django_db_config LOGGER = get_logger() ADV_LOCK_UID = 1000 @@ -115,9 +115,13 @@ def run_migrations(): execute_from_command_line(["", "migrate_schemas"]) if CONFIG.get_bool("tenants.enabled", False): execute_from_command_line(["", "migrate_schemas", "--schema", "template", "--tenant"]) - execute_from_command_line( - ["", "check"] + ([] if CONFIG.get_bool("debug") else ["--deploy"]) - ) + # Run django system checks for all databases + check_args = ["", "check"] + for label in django_db_config(CONFIG).keys(): + check_args.append(f"--database={label}") + if not CONFIG.get_bool("DEBUG"): + check_args.append("--deploy") + execute_from_command_line(check_args) finally: release_lock(curr) curr.close()