diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication/__init__.py similarity index 98% rename from netbox/netbox/authentication.py rename to netbox/netbox/authentication/__init__.py index 2b66639c8..55fd91d4d 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication/__init__.py @@ -14,6 +14,7 @@ from users.models import Group, ObjectPermission from utilities.permissions import ( permission_is_exempt, qs_filter_from_constraints, resolve_permission, resolve_permission_type, ) +from .misc import _mirror_groups UserModel = get_user_model() @@ -313,7 +314,7 @@ class RemoteUserBackend(_RemoteUserBackend): # Create a new instance of django-auth-ldap's LDAPBackend with our own ObjectPermissions try: - from django_auth_ldap.backend import LDAPBackend as LDAPBackend_ + from django_auth_ldap.backend import _LDAPUser, LDAPBackend as LDAPBackend_ class NBLDAPBackend(ObjectPermissionMixin, LDAPBackend_): def get_permission_filter(self, user_obj): @@ -323,6 +324,10 @@ try: hasattr(user_obj.ldap_user, "group_names")): permission_filter = permission_filter | Q(groups__name__in=user_obj.ldap_user.group_names) return permission_filter + + # Patch with our modified _mirror_groups() method to support our custom Group model + _LDAPUser._mirror_groups = _mirror_groups + except ModuleNotFoundError: pass diff --git a/netbox/netbox/authentication/misc.py b/netbox/netbox/authentication/misc.py new file mode 100644 index 000000000..fe89b8e39 --- /dev/null +++ b/netbox/netbox/authentication/misc.py @@ -0,0 +1,67 @@ +# Copyright (c) 2009, Peter Sagerson +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from users.models import Group + + +# Copied from django_auth_ldap.backend._LDAPUser and modified to support our +# custom Group model. +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()) + current_group_names = frozenset( + self._user.groups.values_list("name", flat=True).iterator() + ) + + # These were normalized to sets above. + MIRROR_GROUPS_EXCEPT = self.settings.MIRROR_GROUPS_EXCEPT + MIRROR_GROUPS = self.settings.MIRROR_GROUPS + + # If the settings are white- or black-listing groups, we'll update + # target_group_names such that we won't modify the membership of groups + # beyond our purview. + if isinstance(MIRROR_GROUPS_EXCEPT, (set, frozenset)): + target_group_names = (target_group_names - MIRROR_GROUPS_EXCEPT) | ( + current_group_names & MIRROR_GROUPS_EXCEPT + ) + elif isinstance(MIRROR_GROUPS, (set, frozenset)): + target_group_names = (target_group_names & MIRROR_GROUPS) | ( + current_group_names - MIRROR_GROUPS + ) + + if target_group_names != current_group_names: + existing_groups = list( + Group.objects.filter(name__in=target_group_names).iterator() + ) + existing_group_names = frozenset(group.name for group in existing_groups) + + new_groups = [ + Group.objects.get_or_create(name=name)[0] + for name in target_group_names + if name not in existing_group_names + ] + + self._user.groups.set(existing_groups + new_groups)