Refine queryset restriction logic

This commit is contained in:
Jeremy Stretch 2020-06-01 13:09:34 -04:00
parent 9679557747
commit 3a9512f086
3 changed files with 30 additions and 20 deletions

View File

@ -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('_')

View File

@ -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

View File

@ -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):