diff --git a/netbox/netbox/authentication.py b/netbox/netbox/authentication.py index 4e9078a9a..bf1f96edb 100644 --- a/netbox/netbox/authentication.py +++ b/netbox/netbox/authentication.py @@ -6,7 +6,7 @@ from django.contrib.auth.models import Group from django.db.models import Q from users.models import ObjectPermission -from utilities.permissions import permission_is_exempt, resolve_permission +from utilities.permissions import permission_is_exempt, resolve_permission, resolve_permission_ct class ObjectPermissionBackend(ModelBackend): @@ -42,8 +42,7 @@ class ObjectPermissionBackend(ModelBackend): return perms def has_perm(self, user_obj, perm, obj=None): - app_label, codename = perm.split('.') - action, model_name = codename.split('_') + app_label, action, model_name = resolve_permission(perm) # Superusers implicitly have all permissions if user_obj.is_active and user_obj.is_superuser: @@ -114,7 +113,7 @@ class RemoteUserBackend(_RemoteUserBackend): permissions_list = [] for permission_name, attrs in settings.REMOTE_AUTH_DEFAULT_PERMISSIONS.items(): try: - content_type, action = resolve_permission(permission_name) + content_type, action = resolve_permission_ct(permission_name) # TODO: Merge multiple actions into a single ObjectPermission per content type obj_perm = ObjectPermission(actions=[action], attrs=attrs) obj_perm.save() diff --git a/netbox/utilities/permissions.py b/netbox/utilities/permissions.py index 38064b689..44c34942f 100644 --- a/netbox/utilities/permissions.py +++ b/netbox/utilities/permissions.py @@ -19,33 +19,36 @@ def get_permission_for_model(model, action): ) -def get_permission_action(name): +def resolve_permission(name): """ - Return the action component (e.g. view or add) from a permission name. + Given a permission name, return the app_label, action, and model_name components. For example, "dcim.view_site" + returns ("dcim", "view", "site"). :param name: Permission name in the format ._ """ try: - return name.split('.')[1].split('_')[0] + app_label, codename = name.split('.') + action, model_name = codename.rsplit('_', 1) except ValueError: raise ValueError( f"Invalid permission name: {name}. Must be in the format ._" ) + return app_label, action, model_name -def resolve_permission(name): + +def resolve_permission_ct(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('_') + app_label, action, model_name = resolve_permission(name) try: content_type = ContentType.objects.get(app_label=app_label, model=model_name) except ContentType.DoesNotExist: - raise ValueError(f"Unknown app/model for {name}") + raise ValueError(f"Unknown app_label/model_name for {name}") return content_type, action @@ -56,8 +59,7 @@ def permission_is_exempt(name): :param name: Permission name in the format ._ """ - app_label, codename = name.split('.') - action, model_name = codename.split('_') + app_label, action, model_name = resolve_permission(name) if action == 'view': if ( diff --git a/netbox/utilities/testing/testcases.py b/netbox/utilities/testing/testcases.py index 3514f9060..2ef5a19fe 100644 --- a/netbox/utilities/testing/testcases.py +++ b/netbox/utilities/testing/testcases.py @@ -7,7 +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 utilities.permissions import resolve_permission_ct from .utils import disable_warnings, post_data @@ -33,7 +33,7 @@ class TestCase(_TestCase): Assign a set of permissions to the test user. Accepts permission names in the form ._. """ for name in names: - ct, action = resolve_permission(name) + ct, action = resolve_permission_ct(name) obj_perm = ObjectPermission(actions=[action]) obj_perm.save() obj_perm.users.add(self.user) diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 0304780f3..e4161077c 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -28,7 +28,7 @@ from extras.models import CustomField, CustomFieldValue, ExportTemplate from extras.querysets import CustomFieldQueryset from utilities.exceptions import AbortTransaction from utilities.forms import BootstrapMixin, CSVDataField, TableConfigForm -from utilities.permissions import get_permission_action, get_permission_for_model +from utilities.permissions import get_permission_for_model, resolve_permission from utilities.utils import csv_format, prepare_cloned_fields from .error_handlers import handle_protectederror from .forms import ConfirmationForm, ImportForm @@ -64,7 +64,7 @@ class ObjectPermissionRequiredMixin(AccessMixin): if user.has_perms((permission_required, *self.additional_permissions)): # Update the view's QuerySet to filter only the permitted objects - action = get_permission_action(permission_required) + action = resolve_permission(permission_required)[1] self.queryset = self.queryset.restrict(user, action) return True @@ -233,7 +233,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View): # Compile a dictionary indicating which permissions are available to the current user for this model permissions = {} for action in ('add', 'change', 'delete', 'view'): - perm_name = '{}.{}_{}'.format(model._meta.app_label, action, model._meta.model_name) + perm_name = get_permission_for_model(model, action) permissions[action] = request.user.has_perm(perm_name) # Construct the table based on the user's permissions