From 911cbf2b183ab12f7fc1d72d5b6bc37fa4fa873a Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Thu, 10 Jun 2021 08:02:13 +0200 Subject: [PATCH 1/6] Fixes #5442: Use LDAP groups to find permissions When AUTH_LDAP_FIND_GROUP_PERMS is set to true the filter to find the users permissions is extended to search for all permissions assigned to groups in which the LDAP user is. --- netbox/netbox/authentication.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index 0eee2c13e..e696333ab 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -11,7 +11,7 @@ from users.models import ObjectPermission from utilities.permissions import permission_is_exempt, resolve_permission, resolve_permission_ct -class ObjectPermissionBackend(ModelBackend): +class ObjectPermissionMixin(): def get_all_permissions(self, user_obj, obj=None): if not user_obj.is_active or user_obj.is_anonymous: @@ -20,13 +20,16 @@ class ObjectPermissionBackend(ModelBackend): user_obj._object_perm_cache = self.get_object_permissions(user_obj) return user_obj._object_perm_cache + def get_permission_filter(self, user_obj): + return Q(users=user_obj) | Q(groups__user=user_obj) + def get_object_permissions(self, user_obj): """ Return all permissions granted to the user by an ObjectPermission. """ # Retrieve all assigned and enabled ObjectPermissions object_permissions = ObjectPermission.objects.filter( - Q(users=user_obj) | Q(groups__user=user_obj), + self.get_permission_filter(user_obj), enabled=True ).prefetch_related('object_types') @@ -86,6 +89,10 @@ class ObjectPermissionBackend(ModelBackend): return model.objects.filter(constraints, pk=obj.pk).exists() +class ObjectPermissionBackend(ObjectPermissionMixin, ModelBackend): + pass + + class RemoteUserBackend(_RemoteUserBackend): """ Custom implementation of Django's RemoteUserBackend which provides configuration hooks for basic customization. @@ -163,8 +170,15 @@ class LDAPBackend: "Required parameter AUTH_LDAP_SERVER_URI is missing from ldap_config.py." ) - # Create a new instance of django-auth-ldap's LDAPBackend - obj = LDAPBackend_() + # Create a new instance of django-auth-ldap's LDAPBackend with our own ObjectPermissions + class NBLDAPBackend(ObjectPermissionMixin, LDAPBackend_): + def get_permission_filter(self, user_obj): + permission_filter = Q(users=user_obj) | Q(groups__user=user_obj) + if self.settings.FIND_GROUP_PERMS: + permission_filter = permission_filter | Q(groups__name__in=user_obj.ldap_user.group_names) + return permission_filter + + obj = NBLDAPBackend() # Read LDAP configuration parameters from ldap_config.py instead of settings.py settings = LDAPSettings() From f55cf993dda9860d387e819779e079374a43297b Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Thu, 10 Jun 2021 16:13:43 +0200 Subject: [PATCH 2/6] Use method from parent class Co-authored-by: Jeremy Stretch --- netbox/netbox/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index e696333ab..b20091d53 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -173,7 +173,7 @@ class LDAPBackend: # Create a new instance of django-auth-ldap's LDAPBackend with our own ObjectPermissions class NBLDAPBackend(ObjectPermissionMixin, LDAPBackend_): def get_permission_filter(self, user_obj): - permission_filter = Q(users=user_obj) | Q(groups__user=user_obj) + permission_filter = super().get_permission_filter(user_obj) if self.settings.FIND_GROUP_PERMS: permission_filter = permission_filter | Q(groups__name__in=user_obj.ldap_user.group_names) return permission_filter From fa94b80a13bb70a5dd56129aa8894d9c37938b15 Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Tue, 15 Jun 2021 08:49:41 +0200 Subject: [PATCH 3/6] Fix error when running scripts This fixes the error Can't pickle local object 'LDAPBackend.__new__..NBLDAPBackend' --- netbox/netbox/authentication.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index b20091d53..03241e522 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -140,11 +140,25 @@ class RemoteUserBackend(_RemoteUserBackend): return False +# Create a new instance of django-auth-ldap's LDAPBackend with our own ObjectPermissions +try: + from django_auth_ldap.backend import LDAPBackend as LDAPBackend_ + + class NBLDAPBackend(ObjectPermissionMixin, LDAPBackend_): + def get_permission_filter(self, user_obj): + permission_filter = super().get_permission_filter(user_obj) + if self.settings.FIND_GROUP_PERMS: + permission_filter = permission_filter | Q(groups__name__in=user_obj.ldap_user.group_names) + return permission_filter +except ModuleNotFoundError: + pass + + class LDAPBackend: def __new__(cls, *args, **kwargs): try: - from django_auth_ldap.backend import LDAPBackend as LDAPBackend_, LDAPSettings + from django_auth_ldap.backend import LDAPSettings import ldap except ModuleNotFoundError as e: if getattr(e, 'name') == 'django_auth_ldap': @@ -170,14 +184,6 @@ class LDAPBackend: "Required parameter AUTH_LDAP_SERVER_URI is missing from ldap_config.py." ) - # Create a new instance of django-auth-ldap's LDAPBackend with our own ObjectPermissions - class NBLDAPBackend(ObjectPermissionMixin, LDAPBackend_): - def get_permission_filter(self, user_obj): - permission_filter = super().get_permission_filter(user_obj) - if self.settings.FIND_GROUP_PERMS: - permission_filter = permission_filter | Q(groups__name__in=user_obj.ldap_user.group_names) - return permission_filter - obj = NBLDAPBackend() # Read LDAP configuration parameters from ldap_config.py instead of settings.py From c0ddb2092c141952efabd9fe2d1e7ffcd66da695 Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Fri, 2 Jul 2021 07:55:13 +0200 Subject: [PATCH 4/6] Fixed bug for users authenticated with API token This prevents a crash when the current user has authenticated himself with an API token. In this case the user will not have the permissions given to his LDAP groups. --- netbox/netbox/authentication.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index 03241e522..2c843f076 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -147,7 +147,9 @@ try: class NBLDAPBackend(ObjectPermissionMixin, LDAPBackend_): def get_permission_filter(self, user_obj): permission_filter = super().get_permission_filter(user_obj) - if self.settings.FIND_GROUP_PERMS: + if (self.settings.FIND_GROUP_PERMS and + hasattr(user_obj, "ldap_user") and + hasattr(user_obj.ldap_user, "group_names")): permission_filter = permission_filter | Q(groups__name__in=user_obj.ldap_user.group_names) return permission_filter except ModuleNotFoundError: From 3b28ef7680a002742fc2c5223702360f62f82500 Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Mon, 5 Jul 2021 12:31:52 +0200 Subject: [PATCH 5/6] Load LDAP groups for API token authenticated users When users are authenticated with an API token not all permissions where assigned to the session because the LDAP group memberships where not available. Now the information is loaded from the directory if the user is found. If not the local group memberships are used. --- netbox/netbox/api/authentication.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/netbox/netbox/api/authentication.py b/netbox/netbox/api/authentication.py index 1cb32c1e4..76bb0f983 100644 --- a/netbox/netbox/api/authentication.py +++ b/netbox/netbox/api/authentication.py @@ -25,6 +25,16 @@ class TokenAuthentication(authentication.TokenAuthentication): if not token.user.is_active: raise exceptions.AuthenticationFailed("User inactive") + # When LDAP authentication is active try to load user data from LDAP directory + if (settings.REMOTE_AUTH_ENABLED and + settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend'): + from netbox.authentication import LDAPBackend + ldap_backend = LDAPBackend() + user = ldap_backend.populate_user(token.user.username) + # If the user is found in the LDAP directory use it, if not fallback to the local user + if user: + return user, token + return token.user, token From 15bc6c74b205facef35a2801a7993c16ee26eed6 Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Fri, 9 Jul 2021 08:13:02 +0200 Subject: [PATCH 6/6] Only check REMOTE_AUTH_BACKEND in API token auth --- netbox/netbox/api/authentication.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/netbox/netbox/api/authentication.py b/netbox/netbox/api/authentication.py index 76bb0f983..7f8bee318 100644 --- a/netbox/netbox/api/authentication.py +++ b/netbox/netbox/api/authentication.py @@ -26,8 +26,7 @@ class TokenAuthentication(authentication.TokenAuthentication): raise exceptions.AuthenticationFailed("User inactive") # When LDAP authentication is active try to load user data from LDAP directory - if (settings.REMOTE_AUTH_ENABLED and - settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend'): + if settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend': from netbox.authentication import LDAPBackend ldap_backend = LDAPBackend() user = ldap_backend.populate_user(token.user.username)