From a5974ee2655a8a982de299cdfaba13374a996c30 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 22 Jun 2026 06:35:38 -0400 Subject: [PATCH] Fixes #21310: Fix IntegrityError exception when `AUTH_LDAP_MIRROR_GROUPS` is enabled (#22492) --- netbox/netbox/authentication/misc.py | 4 +- netbox/netbox/tests/test_authentication.py | 43 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/authentication/misc.py b/netbox/netbox/authentication/misc.py index fe89b8e39..c993967f1 100644 --- a/netbox/netbox/authentication/misc.py +++ b/netbox/netbox/authentication/misc.py @@ -31,7 +31,9 @@ def _mirror_groups(self): Mirrors the user's LDAP groups in the Django database and updates the user's membership. """ - target_group_names = frozenset(self._get_groups().get_group_names()) + target_group_names = frozenset( + filter(None, self._get_groups().get_group_names()) + ) current_group_names = frozenset( self._user.groups.values_list("name", flat=True).iterator() ) diff --git a/netbox/netbox/tests/test_authentication.py b/netbox/netbox/tests/test_authentication.py index 5221fe4b8..5fda6e088 100644 --- a/netbox/netbox/tests/test_authentication.py +++ b/netbox/netbox/tests/test_authentication.py @@ -1,4 +1,5 @@ import datetime +from unittest.mock import MagicMock from django.conf import settings from django.contrib.messages.storage.fallback import FallbackStorage @@ -10,6 +11,7 @@ from social_core.exceptions import AuthFailed from core.models import ObjectType from dcim.models import Rack, Site +from netbox.authentication.misc import _mirror_groups from netbox.middleware import SocialAuthExceptionMiddleware from users.constants import TOKEN_PREFIX from users.models import Group, ObjectPermission, Token, User @@ -517,6 +519,47 @@ class ExternalAuthenticationTestCase(TestCase): ) +class LDAPMirrorGroupsTestCase(TestCase): + """ + Test the custom _mirror_groups() patched onto django-auth-ldap's _LDAPUser. + """ + + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create(username='ldapuser') + Group.objects.create(name='Group 1') + + def _build_ldap_user(self, group_names): + """ + Construct a mock _LDAPUser whose LDAP group search returns the given names. + """ + ldap_user = MagicMock() + ldap_user._user = self.user + ldap_user._get_groups.return_value.get_group_names.return_value = group_names + # Disable allow/deny list handling + ldap_user.settings.MIRROR_GROUPS_EXCEPT = None + ldap_user.settings.MIRROR_GROUPS = None + return ldap_user + + def test_mirror_groups_ignores_empty_names(self): + """ + An LDAP group search returning an empty or null name must not attempt to + create a Group with a null name (see #21310). + """ + ldap_user = self._build_ldap_user({'Group 1', '', None}) + + _mirror_groups(ldap_user) + + # No group with a null or empty name should have been created + self.assertFalse(Group.objects.filter(name__isnull=True).exists()) + self.assertFalse(Group.objects.filter(name='').exists()) + # The user should be mirrored into the one valid, matching group + self.assertListEqual( + ['Group 1'], + list(self.user.groups.values_list('name', flat=True)) + ) + + class ObjectPermissionAPIViewTestCase(TestCase): client_class = APIClient