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):
 def resolve_permission(name):
     """
     """
     Given a permission name, return the relevant ContentType and action. For example, "dcim.view_site" returns
     Given a permission name, return the relevant ContentType and action. For example, "dcim.view_site" returns
     (Site, "view").
     (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('.')
     app_label, codename = name.split('.')
     action, model_name = codename.split('_')
     action, model_name = codename.split('_')
@@ -40,7 +54,7 @@ def permission_is_exempt(name):
     """
     """
     Determine whether a specified permission is exempt from evaluation.
     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('.')
     app_label, codename = name.split('.')
     action, model_name = codename.split('_')
     action, model_name = codename.split('_')

+ 6 - 10
netbox/utilities/querysets.py

@@ -1,5 +1,7 @@
 from django.db.models import Q, QuerySet
 from django.db.models import Q, QuerySet
 
 
+from utilities.permissions import permission_is_exempt
+
 
 
 class DummyQuerySet:
 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
         Filter the QuerySet to return only objects on which the specified user has been granted the specified
         permission.
         permission.
 
 
-        :param queryset: Base QuerySet to be restricted
         :param user: User instance
         :param user: User instance
         :param action: The action which must be permitted (e.g. "view" for "dcim.view_site")
         :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
         model_name = self.model._meta.model_name
         permission_required = f'{app_label}.{action}_{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
             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()
             return self.none()
 
 
         # Filter the queryset to include only objects with allowed attributes
         # 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 extras.querysets import CustomFieldQueryset
 from utilities.exceptions import AbortTransaction
 from utilities.exceptions import AbortTransaction
 from utilities.forms import BootstrapMixin, CSVDataField, TableConfigForm
 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 utilities.utils import csv_format, prepare_cloned_fields
 from .error_handlers import handle_protectederror
 from .error_handlers import handle_protectederror
 from .forms import ConfirmationForm, ImportForm
 from .forms import ConfirmationForm, ImportForm
@@ -60,16 +60,16 @@ class ObjectPermissionRequiredMixin(AccessMixin):
         user = self.request.user
         user = self.request.user
         permission_required = self.get_required_permission()
         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)
             self.queryset = self.queryset.restrict(user, action)
 
 
-        return True
+            return True
+
+        return False
 
 
     def dispatch(self, request, *args, **kwargs):
     def dispatch(self, request, *args, **kwargs):