From 82300990ec79f8ce6b2fca953d5548028a14aaea 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 76407401130734e6d224ebbcba19182560eba794 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 5bf4234ad3f2d0a02a1eda37d4a196d66970a7fb 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 4abfa6231c4a5006e4baa9b3306c9307c4c75092 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 a3d40e35212841bef7366953a39ff4c04e826bdd 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 b814123ede55e2052462648cb8e409f2c7df43fa 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)