from django.contrib.auth.mixins import AccessMixin from django.core.exceptions import ImproperlyConfigured from django.urls import reverse from django.urls.exceptions import NoReverseMatch from django.utils.http import is_safe_url 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')