From 4108dd94be1d0f91cdf41984ef6fd80620f66831 Mon Sep 17 00:00:00 2001 From: Najam Ahmed Ansari Date: Fri, 9 Mar 2018 18:34:24 +0500 Subject: [PATCH] Adding support for permissions based access control Rationale: Having to modify code (by writing methods for every new role) is not ideal in cases where it is required to create roles on the fly (e.g. through an admin panel). For these cases, it is helpful to have permission based methods so that response depends on whatever permissions a user has. --- drf_roles/mixins.py | 36 +++++++++++++++++++++++++++++++++++- setup.py | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/drf_roles/mixins.py b/drf_roles/mixins.py index 2c87a6c..03334f1 100644 --- a/drf_roles/mixins.py +++ b/drf_roles/mixins.py @@ -1,8 +1,12 @@ from django.conf import settings -from django.contrib.auth.models import Group +from django.contrib.auth.models import Group, Permission # Default settings DEFAULT_GROUPS = [group.name.lower() for group in Group.objects.all()] +DEFAULT_PERMISSIONS = [ + permission.codename.lower() for permission in Permission.objects.all() +] + DEFAULT_REGISTRY = ( "get_queryset", "get_serializer_class", @@ -42,6 +46,36 @@ def _get_role(self, user): else: return user_role.pop() + +class PermissionViewSetMixin(object): + """A ViewSet mixin that parameterizes DRF methods over permissions.""" + _viewset_method_registry = set(getattr(settings, "VIEWSET_METHOD_REGISTRY", DEFAULT_REGISTRY)) + _permissions = DEFAULT_PERMISSIONS + + def __init__(self, *args, **kwargs): + for permission in self._permissions: + for fn in self._viewset_method_registry: + register_permission_fn(permission, fn) + + def _call_permission_fn(self, fn, permission, *args, **kwargs): + """Attempts to call a permission-scoped method""" + try: + if not self.request.user.has_perm(permission): + raise RoleError("The user does not have the required permission") + permission_fn = "{}_for_{}".format(fn, permission) + return getattr(self, permission_fn)(*args, **kwargs) + except (AttributeError, RoleError): + return getattr(super(PermissionViewSetMixin, self), fn)(*args, **kwargs) + + +def register_permission_fn(permission, fn): + """Dynamically adds fn to PermissionViewSetMixin. + """ + def inner(self, *args, **kwargs): + return self._call_permission_fn(fn, permission, *args, **kwargs) + setattr(PermissionViewSetMixin, fn, inner) + + def register_fn(fn): """Dynamically adds fn to RoleViewSetMixin""" def inner(self, *args, **kwargs): diff --git a/setup.py b/setup.py index 3e24528..3868ef6 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='django-rest-framework-roles', - version='0.5', + version='0.6', packages=['drf_roles'], include_package_data=True, license='BSD License',