Source code for bridgekeeper.mixins

from django.core.exceptions import (
    ImproperlyConfigured,
    PermissionDenied,
    SuspiciousOperation,
)

from . import perms as global_permission_map


class BasePermissionMixin:
    def __init__(self, permission_map=None, *args, **kwargs):
        if permission_map is None:
            permission_map = global_permission_map
        try:
            self.rule = permission_map[self.permission_name]
        except AttributeError:
            raise ImproperlyConfigured("permission_name is not set")
        except KeyError:
            raise ImproperlyConfigured(
                f"permission {self.permission_name} " "does not exist"
            )


[docs]class QuerySetPermissionMixin(BasePermissionMixin): """View mixin that filters QuerySets according to a permission. Use this mixin with any class-based view that expects a ``get_queryset`` method (e.g. :class:`~django.views.generic.list.ListView`, :class:`~django.views.generic.detail.DetailView`, :class:`~django.views.generic.edit.UpdateView`, or any other views that subclass from :class:`~django.views.generic.list.MultipleObjectMixin` or :class:`~django.views.generic.detail.SingleObjectMixin`), and supply a :attr:`permission_name` attribute with the name of a Bridgekeeper permission. The view's queryset will then be automatically filtered to objects that the user requesting the page has the supplied permission for. For multiple-object views like :class:`~django.views.generic.list.ListView`, objects the user doesn't have the permission for just won't be in the list. For single-object views like :class:`~django.views.generic.edit.UpdateView`, attempts to access objects the user doesn't have the permission for will just 404. .. attribute:: permission_name The name of the Bridgekeeper permission to check against, e.g. ``'shrubberies.change_shrubbery'``. """ def get_queryset(self, *args, **kwargs): qs = super().get_queryset(*args, **kwargs) return self.rule.filter(self.request.user, qs)
[docs]class CreatePermissionGuardMixin(BasePermissionMixin): """A view that checks permissions before creating model instances. Use this mixin with :class:`~django.views.generic.edit.CreateView`, and supply the :attr:`permission_name` of a Bridgekeeper permission. Your view will then do two things: - Check that it's possible for a user to create any new instances at all (i.e. that :meth:`~bridgekeeper.rules.Rule.is_possible_for` returns ``True`` on the supplied permission). If not, the mixin raises :class:`~django.core.exceptions.PermissionDenied`. - Just before the form is saved, checks the unsaved model instance against the supplied permission; if it fails, the mixin raises :class:`~django.core.exceptions.SuspiciousOperation`. Note that unlike :class:`QuerySetPermissionMixin`, this mixin won't automatically apply permissions for you. Ideally, your view (or the form class your view uses) should make it impossible for users to create instances they're not allowed to create; fields that must be set to a certain value should be set automatically and not displayed in the form, choice fields should have their ``choices`` limited to only values the user is allowed to set, and so on. Bridgekeeper can't (and arguably shouldn't) reach into your form and modify it for you. Instead, this mixin provides a last line of defence; if your view has a bug where a user can create something they're not allowed to, the mixin will prevent the object from actually being created, and crash loudly in a way that your error reporting systems can pick up, allowing you to fix the bug. .. attribute:: permission_name The name of the Bridgekeeper permission to check against, e.g. ``'shrubberies.change_shrubbery'``. """ def dispatch(self, request, *args, **kwargs): if not self.rule.is_possible_for(self.request.user): raise PermissionDenied return super().dispatch(request, *args, **kwargs) def form_valid(self, form): if not self.rule.check(self.request.user, form.instance): raise SuspiciousOperation( "Tried to create an instance which " "permissions do not allow" ) return super().form_valid(form)