diff --git a/rules/contrib/rest_framework.py b/rules/contrib/rest_framework.py index 8ecaa96..b5ed166 100644 --- a/rules/contrib/rest_framework.py +++ b/rules/contrib/rest_framework.py @@ -1,7 +1,9 @@ from django.core.exceptions import ImproperlyConfigured, PermissionDenied +from ..viewsets import BaseAutoPermissionMixin -class AutoPermissionViewSetMixin: + +class AutoPermissionViewSetMixin(BaseAutoPermissionMixin): """ Enforces object-level permissions in ``rest_framework.viewsets.ViewSet``, deriving the permission type from the particular action to be performed.. @@ -59,6 +61,10 @@ def initial(self, *args, **kwargs): # Skip permission checking for this action return + if self.action == "create": + # Will be checked before perform_create + return + # Determine whether we've to check object permissions (for detail actions) obj = None extra_actions = self.get_extra_actions() @@ -66,10 +72,18 @@ def initial(self, *args, **kwargs): if handler.__func__ in extra_actions: if handler.detail: obj = self.get_object() - elif self.action not in ("create", "list"): + elif self.action not in ("list"): obj = self.get_object() # Finally, check permission - perm = self.get_queryset().model.get_perm(perm_type) + model = self.get_queryset().model + perm = self.get_permission_for_model(model, perm_type) if not self.request.user.has_perm(perm, obj): raise PermissionDenied + + def perform_create(self, serializer): + model = self.get_queryset().model + perm = self.get_permission_for_model(model, "add") + if not self.request.user.has_perm(perm, serializer.validated_data): + raise PermissionDenied + return super().perform_create(serializer) diff --git a/rules/contrib/views.py b/rules/contrib/views.py index 7e7cf07..376d720 100644 --- a/rules/contrib/views.py +++ b/rules/contrib/views.py @@ -12,6 +12,8 @@ # versions before 1.9. For usage help see Django's docs for 1.9 or later. from django.views.generic.edit import BaseCreateView +from ..viewsets import BaseAutoPermissionMixin + LoginRequiredMixin = mixins.LoginRequiredMixin UserPassesTestMixin = mixins.UserPassesTestMixin @@ -47,7 +49,7 @@ def has_permission(self): return self.request.user.has_perms(perms, obj) -class AutoPermissionRequiredMixin(PermissionRequiredMixin): +class AutoPermissionRequiredMixin(PermissionRequiredMixin, BaseAutoPermissionMixin): """ An extended variant of PermissionRequiredMixin which automatically determines the permission to check based on the type of view it's used with. @@ -119,7 +121,7 @@ def get_permission_required(self): model = getattr(self, "model", None) if model is None: model = self.get_queryset().model - perms.append(model.get_perm(perm_type)) + perms.append(self.get_permission_for_model(model, perm_type)) # If additional permissions have been defined, consider them as well if self.permission_required is not None: diff --git a/rules/viewsets.py b/rules/viewsets.py new file mode 100644 index 0000000..5e02c76 --- /dev/null +++ b/rules/viewsets.py @@ -0,0 +1,21 @@ +# All auto permission mixins (both for DRF and Django views) +# inherit from this mixin. +class BaseAutoPermissionMixin: + def get_perm(self, model, perm_type): + return "%s.%s_%s" % (model._meta.app_label, perm_type, model._meta.model_name) + + def get_permission_for_model(self, model, perm_type): + + # Attempt to use model mixin. If model mixin is not available, + # default to standard convetion of this project (or allow + # overriding `get_perm` through a custom Mixin) + try: + use_model_mixin_method = callable(model.get_perm) + except AttributeError: + use_model_mixin_method = False + + if use_model_mixin_method: + perm = model.get_perm(perm_type) + else: + perm = self.get_perm(model, perm_type) + return perm