Procházet zdrojové kódy

Refine queryset restriction logic

Jeremy Stretch před 5 roky
rodič
revize
3a9512f086

+ 16 - 2
netbox/utilities/permissions.py

@@ -19,12 +19,26 @@ def get_permission_for_model(model, action):
     )
 
 
+def get_permission_action(name):
+    """
+    Return the action component (e.g. view or add) from a permission name.
+
+    :param name: Permission name in the format <app_label>.<action>_<model>
+    """
+    try:
+        return name.split('.')[1].split('_')[0]
+    except ValueError:
+        raise ValueError(
+            f"Invalid permission name: {name}. Must be in the format <app_label>.<action>_<model>"
+        )
+
+
 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>.<action>_<model>
+    :param name: Permission name in the format <app_label>.<action>_<model>
     """
     app_label, codename = name.split('.')
     action, model_name = codename.split('_')
@@ -40,7 +54,7 @@ def permission_is_exempt(name):
     """
     Determine whether a specified permission is exempt from evaluation.
 
-    :param name: Permission name in the format <app>.<action>_<model>
+    :param name: Permission name in the format <app_label>.<action>_<model>
     """
     app_label, codename = name.split('.')
     action, model_name = codename.split('_')

+ 6 - 10
netbox/utilities/querysets.py

@@ -1,5 +1,7 @@
 from django.db.models import Q, QuerySet
 
+from utilities.permissions import permission_is_exempt
+
 
 class DummyQuerySet:
     """
@@ -19,7 +21,6 @@ class RestrictedQuerySet(QuerySet):
         Filter the QuerySet to return only objects on which the specified user has been granted the specified
         permission.
 
-        :param queryset: Base QuerySet to be restricted
         :param user: User instance
         :param action: The action which must be permitted (e.g. "view" for "dcim.view_site")
         """
@@ -28,17 +29,12 @@ class RestrictedQuerySet(QuerySet):
         model_name = self.model._meta.model_name
         permission_required = f'{app_label}.{action}_{model_name}'
 
-        # TODO: Handle anonymous users
-        if not user.is_authenticated:
+        # Bypass restriction for superusers and exempt views
+        if user.is_superuser or permission_is_exempt(permission_required):
             return self
 
-        # Determine what constraints (if any) have been placed on this user for this action and model
-        # TODO: Find a better way to ensure permissions are cached
-        if not hasattr(user, '_object_perm_cache'):
-            user.get_all_permissions()
-
-        # User has not been granted any permission
-        if permission_required not in user._object_perm_cache:
+        # User is anonymous or has not been granted the requisite permission
+        if not user.is_authenticated or permission_required not in user.get_all_permissions():
             return self.none()
 
         # Filter the queryset to include only objects with allowed attributes

+ 8 - 8
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_for_model
+from utilities.permissions import get_permission_action, get_permission_for_model
 from utilities.utils import csv_format, prepare_cloned_fields
 from .error_handlers import handle_protectederror
 from .forms import ConfirmationForm, ImportForm
@@ -60,16 +60,16 @@ class ObjectPermissionRequiredMixin(AccessMixin):
         user = self.request.user
         permission_required = self.get_required_permission()
 
-        # First, check that the user is granted the required permission(s) at either the model or object level.
-        if not user.has_perms((permission_required, *self.additional_permissions)):
-            return False
+        # Check that the user has been granted the required permission(s).
+        if user.has_perms((permission_required, *self.additional_permissions)):
 
-        # Update the view's QuerySet to filter only the permitted objects
-        if user.is_authenticated and not user.is_superuser:
-            action = permission_required.split('.')[1].split('_')[0]
+            # Update the view's QuerySet to filter only the permitted objects
+            action = get_permission_action(permission_required)
             self.queryset = self.queryset.restrict(user, action)
 
-        return True
+            return True
+
+        return False
 
     def dispatch(self, request, *args, **kwargs):