mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
139 lines
5.1 KiB
Python
139 lines
5.1 KiB
Python
from django.contrib.auth.mixins import AccessMixin
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.shortcuts import get_object_or_404, redirect
|
|
from django.urls import reverse
|
|
from django.urls.exceptions import NoReverseMatch
|
|
from django.utils.http import is_safe_url
|
|
from django.views.generic import View
|
|
|
|
from .permissions import resolve_permission
|
|
|
|
|
|
#
|
|
# View Mixins
|
|
#
|
|
|
|
class ContentTypePermissionRequiredMixin(AccessMixin):
|
|
"""
|
|
Similar to Django's built-in PermissionRequiredMixin, but extended to check model-level permission assignments.
|
|
This is related to ObjectPermissionRequiredMixin, except that is does not enforce object-level permissions,
|
|
and fits within NetBox's custom permission enforcement system.
|
|
|
|
additional_permissions: An optional iterable of statically declared permissions to evaluate in addition to those
|
|
derived from the object type
|
|
"""
|
|
additional_permissions = list()
|
|
|
|
def get_required_permission(self):
|
|
"""
|
|
Return the specific permission necessary to perform the requested action on an object.
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()")
|
|
|
|
def has_permission(self):
|
|
user = self.request.user
|
|
permission_required = self.get_required_permission()
|
|
|
|
# Check that the user has been granted the required permission(s).
|
|
if user.has_perms((permission_required, *self.additional_permissions)):
|
|
return True
|
|
|
|
return False
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not self.has_permission():
|
|
return self.handle_no_permission()
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
|
|
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.
|
|
|
|
additional_permissions: An optional iterable of statically declared permissions to evaluate in addition to those
|
|
derived from the object type
|
|
"""
|
|
additional_permissions = list()
|
|
|
|
def get_required_permission(self):
|
|
"""
|
|
Return the specific permission necessary to perform the requested action on an object.
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()")
|
|
|
|
def has_permission(self):
|
|
user = self.request.user
|
|
permission_required = self.get_required_permission()
|
|
|
|
# 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
|
|
action = resolve_permission(permission_required)[1]
|
|
self.queryset = self.queryset.restrict(user, action)
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
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:
|
|
"""
|
|
Provides logic for determining where a user should be redirected after processing a form.
|
|
"""
|
|
default_return_url = None
|
|
|
|
def get_return_url(self, request, obj=None):
|
|
|
|
# First, see if `return_url` was specified as a query parameter or form data. Use this URL only if it's
|
|
# considered safe.
|
|
query_param = request.GET.get('return_url') or request.POST.get('return_url')
|
|
if query_param and is_safe_url(url=query_param, allowed_hosts=request.get_host()):
|
|
return query_param
|
|
|
|
# Next, check if the object being modified (if any) has an absolute URL.
|
|
if obj is not None and obj.pk and hasattr(obj, 'get_absolute_url'):
|
|
return obj.get_absolute_url()
|
|
|
|
# Fall back to the default URL (if specified) for the view.
|
|
if self.default_return_url is not None:
|
|
return reverse(self.default_return_url)
|
|
|
|
# Attempt to dynamically resolve the list view for the object
|
|
if hasattr(self, 'queryset'):
|
|
model_opts = self.queryset.model._meta
|
|
try:
|
|
return reverse(f'{model_opts.app_label}:{model_opts.model_name}_list')
|
|
except NoReverseMatch:
|
|
pass
|
|
|
|
# If all else fails, return home. Ideally this should never happen.
|
|
return reverse('home')
|
|
|
|
|
|
#
|
|
# Views
|
|
#
|
|
|
|
class SlugRedirectView(View):
|
|
|
|
def get(self, request, model, slug):
|
|
obj = get_object_or_404(model.objects.restrict(request.user, 'view'), slug=slug)
|
|
return redirect(obj.get_absolute_url())
|