diff --git a/netbox/users/models.py b/netbox/users/models.py index b9ab6cbb5..17c5a3a65 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -12,6 +12,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone +from utilities.permissions import resolve_permission from utilities.utils import flatten_dict @@ -202,11 +203,9 @@ class ObjectPermissionManager(models.Manager): Compile all ObjectPermission attributes applicable to a specific combination of user, model, and action. Returns a dictionary that can be passed directly to .filter() on a QuerySet. """ - app_label, codename = perm.split('.') - action, model_name = codename.split('_') + content_type, action = resolve_permission(perm) assert action in ['view', 'add', 'change', 'delete'], f"Invalid action: {action}" - content_type = ContentType.objects.get(app_label=app_label, model=model_name) qs = self.get_queryset().filter( Q(users=user) | Q(groups__user=user), model=content_type, diff --git a/netbox/utilities/auth_backends.py b/netbox/utilities/auth_backends.py index bb705a6df..a490115bb 100644 --- a/netbox/utilities/auth_backends.py +++ b/netbox/utilities/auth_backends.py @@ -7,6 +7,7 @@ from django.contrib.contenttypes.models import ContentType from django.db.models import Q from users.models import ObjectPermission +from utilities.permissions import resolve_permission class ObjectPermissionBackend(ModelBackend): @@ -40,7 +41,6 @@ class ObjectPermissionBackend(ModelBackend): return user_obj._object_perm_cache def has_perm(self, user_obj, perm, obj=None): - # print(f'has_perm({perm})') app_label, codename = perm.split('.') action, model_name = codename.split('_') @@ -120,10 +120,9 @@ class RemoteUserBackend(_RemoteUserBackend): permissions_list = [] for permission_name in settings.REMOTE_AUTH_DEFAULT_PERMISSIONS: try: - app_label, codename = permission_name.split('.') - action, model_name = codename.split('_') + content_type, action = resolve_permission(permission_name) user.object_permissions.create(**{ - 'model': ContentType.objects.get(app_label=app_label, model=model_name), + 'model': content_type, f'can_{action}': True }) permissions_list.append(permission_name) diff --git a/netbox/utilities/permissions.py b/netbox/utilities/permissions.py index 516d6fe5b..80d564db4 100644 --- a/netbox/utilities/permissions.py +++ b/netbox/utilities/permissions.py @@ -1,3 +1,6 @@ +from django.contrib.contenttypes.models import ContentType + + def get_permission_for_model(model, action): """ Resolve the named permission for a given model (or instance) and action (e.g. view or add). @@ -13,3 +16,20 @@ def get_permission_for_model(model, action): action, model._meta.model_name ) + + +def resolve_permission(name): + """ + Given a permission name, return the relevant ContentType and action. For example, "dcim.view_site" returns + (Site, "view"). + + :param name: Permission name in the format ._ + """ + app_label, codename = name.split('.') + action, model_name = codename.split('_') + try: + content_type = ContentType.objects.get(app_label=app_label, model=model_name) + except ContentType.DoesNotExist: + raise ValueError(f"Unknown app/model for {name}") + + return content_type, action diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index 86f465364..a505e6e03 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -7,6 +7,7 @@ from django.urls import reverse, NoReverseMatch from rest_framework.test import APIClient from users.models import ObjectPermission, Token +from utilities.permissions import resolve_permission from .utils import disable_warnings, post_data @@ -32,11 +33,9 @@ class TestCase(_TestCase): Assign a set of permissions to the test user. Accepts permission names in the form ._. """ for name in names: - app_label, codename = name.split('.') - action, model_name = codename.split('_') - + ct, action = resolve_permission(name) self.user.object_permissions.create(**{ - 'model': ContentType.objects.get(app_label=app_label, model=model_name), + 'model': ct, f'can_{action}': True }) @@ -45,11 +44,9 @@ class TestCase(_TestCase): Remove a set of permissions from the test user, if assigned. """ for name in names: - app_label, codename = name.split('.') - action, model_name = codename.split('_') - + ct, action = resolve_permission(name) self.user.object_permissions.filter(**{ - 'model': ContentType.objects.get(app_label=app_label, model=model_name), + 'model': ct, f'can_{action}': True }).delete()