mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Refine queryset restriction logic
This commit is contained in:
@ -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('_')
|
||||||
|
@ -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
|
# Bypass restriction for superusers and exempt views
|
||||||
if not user.is_authenticated:
|
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
|
# User is anonymous or has not been granted the requisite permission
|
||||||
# TODO: Find a better way to ensure permissions are cached
|
if not user.is_authenticated or permission_required not in user.get_all_permissions():
|
||||||
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:
|
|
||||||
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
|
||||||
|
@ -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.
|
# Check that the user has been granted the required permission(s).
|
||||||
if not user.has_perms((permission_required, *self.additional_permissions)):
|
if user.has_perms((permission_required, *self.additional_permissions)):
|
||||||
return False
|
|
||||||
|
|
||||||
# Update the view's QuerySet to filter only the permitted objects
|
# Update the view's QuerySet to filter only the permitted objects
|
||||||
if user.is_authenticated and not user.is_superuser:
|
action = get_permission_action(permission_required)
|
||||||
action = permission_required.split('.')[1].split('_')[0]
|
|
||||||
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):
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user