1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Move ObjectPermissionRequiredMixin to utilities.views

This commit is contained in:
Jeremy Stretch
2020-05-21 13:12:15 -04:00
parent 40c590f445
commit cc6e74dfd5
5 changed files with 78 additions and 59 deletions

View File

@ -21,13 +21,12 @@ from extras.models import Graph
from extras.views import ObjectConfigContextView
from ipam.models import Prefix, VLAN
from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
from netbox.authentication import ObjectPermissionRequiredMixin
from utilities.forms import ConfirmationForm
from utilities.paginator import EnhancedPaginator
from utilities.utils import csv_format
from utilities.views import (
BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, GetReturnURLMixin,
ObjectImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
ObjectImportView, ObjectDeleteView, ObjectEditView, ObjectListView, ObjectPermissionRequiredMixin,
)
from virtualization.models import VirtualMachine
from . import filters, forms, tables

View File

@ -8,10 +8,10 @@ from django.views.generic import View
from django_tables2 import RequestConfig
from dcim.models import Device, Interface
from netbox.authentication import ObjectPermissionRequiredMixin
from utilities.paginator import EnhancedPaginator
from utilities.views import (
BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
ObjectPermissionRequiredMixin,
)
from virtualization.models import VirtualMachine
from . import filters, forms, tables

View File

@ -1,55 +0,0 @@
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import ImproperlyConfigured
from users.models import ObjectPermission
class ObjectPermissionRequiredMixin(AccessMixin):
"""
Similar to Django's built-in PermissionRequiredMixin, but extended to check for both model-level and object-level
permission assignments. If the user has only object-level permissions assigned, the view's queryset is filtered
to return only those objects on which the user is permitted to perform the specified action.
"""
permission_required = None
def has_permission(self):
user = self.request.user
# First, check that the user is granted the required permission at either the model or object level.
if not user.has_perm(self.permission_required):
return False
# Superusers implicitly have all permissions
if user.is_superuser:
return True
# Determine whether the permission is model-level or object-level. Model-level permissions grant the
# specified action to *all* objects, so no further action is needed.
if self.permission_required in {*user._user_perm_cache, *user._group_perm_cache}:
return True
# If the permission is granted only at the object level, filter the view's queryset to return only objects
# on which the user is permitted to perform the specified action.
attrs = ObjectPermission.objects.get_attr_constraints(user, self.permission_required)
if attrs:
# Update the view's QuerySet to filter only the permitted objects
self.queryset = self.queryset.filter(attrs)
return True
def dispatch(self, request, *args, **kwargs):
if self.permission_required is None:
raise ImproperlyConfigured(
'{0} is missing the permission_required attribute. Define {0}.permission_required, or override '
'{0}.get_permission_required().'.format(self.__class__.__name__)
)
if not hasattr(self, 'queryset'):
raise ImproperlyConfigured(
'{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define '
'a base queryset'.format(self.__class__.__name__)
)
if not self.has_permission():
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)

View File

@ -0,0 +1,15 @@
def get_permission_for_model(model, action):
"""
Resolve the named permission for a given model (or instance) and action (e.g. view or add).
:param model: A model or instance
:param action: View, add, change, or delete (string)
"""
if action not in ('view', 'add', 'change', 'delete'):
raise ValueError(f"Unsupported action: {action}")
return '{}.{}_{}'.format(
model._meta.app_label,
action,
model._meta.model_name
)

View File

@ -4,7 +4,8 @@ from copy import deepcopy
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured, ObjectDoesNotExist, ValidationError
from django.db import transaction, IntegrityError
from django.db.models import ManyToManyField, ProtectedError
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
@ -32,6 +33,61 @@ from .forms import ConfirmationForm, ImportForm
from .paginator import EnhancedPaginator, get_paginate_count
#
# Mixins
#
class ObjectPermissionRequiredMixin(AccessMixin):
"""
Similar to Django's built-in PermissionRequiredMixin, but extended to check for both model-level and object-level
permission assignments. If the user has only object-level permissions assigned, the view's queryset is filtered
to return only those objects on which the user is permitted to perform the specified action.
"""
permission_required = None
def has_permission(self):
user = self.request.user
# First, check that the user is granted the required permission at either the model or object level.
if not user.has_perm(self.permission_required):
return False
# Superusers implicitly have all permissions
if user.is_superuser:
return True
# Determine whether the permission is model-level or object-level. Model-level permissions grant the
# specified action to *all* objects, so no further action is needed.
if self.permission_required in {*user._user_perm_cache, *user._group_perm_cache}:
return True
# If the permission is granted only at the object level, filter the view's queryset to return only objects
# on which the user is permitted to perform the specified action.
attrs = ObjectPermission.objects.get_attr_constraints(user, self.permission_required)
if attrs:
# Update the view's QuerySet to filter only the permitted objects
self.queryset = self.queryset.filter(attrs)
return True
def dispatch(self, request, *args, **kwargs):
if self.permission_required is None:
raise ImproperlyConfigured(
'{0} is missing the permission_required attribute. Define {0}.permission_required, or override '
'{0}.get_permission_required().'.format(self.__class__.__name__)
)
if not hasattr(self, 'queryset'):
raise ImproperlyConfigured(
'{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define '
'a base queryset'.format(self.__class__.__name__)
)
if not self.has_permission():
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class GetReturnURLMixin(object):
"""
Provides logic for determining where a user should be redirected after processing a form.
@ -58,6 +114,10 @@ class GetReturnURLMixin(object):
return reverse('home')
#
# Generic views
#
class ObjectListView(View):
"""
List a series of objects.