Last active
November 28, 2019 09:19
-
-
Save bpereto/de33b6988866fe04c3a1b2f77c21534f to your computer and use it in GitHub Desktop.
Django Restframework Rules Permission integration with HTTPMethod Map
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Django REST Framework Permission Class for Django Rules | |
""" | |
from rest_framework import permissions, exceptions | |
from rest_framework.viewsets import ModelViewSet | |
from django.http import Http404 | |
class DjangoRulesMethodObjectPermissions(permissions.BasePermission): | |
""" | |
The request is authenticated using Django's object-level permissions. | |
It requires an object-permissions-enabled backend, such as django-rules | |
It ensures that the user is authenticated, and has the appropriate | |
`add`/`change`/`delete` permissions on the object using HTTP Methods | |
mapped to a django rule(s). | |
""" | |
# Map methods into required permission codes. | |
# Override this if you need to also provide 'view' permissions, | |
# or if you want to provide custom permission codes. | |
perms_map = { | |
'GET': [], | |
'OPTIONS': [], | |
'HEAD': [], | |
} | |
object_perms_map = {} | |
authenticated_users_only = True | |
def get_required_permissions(self, method): | |
""" | |
Given a HTTP method, return the list of permission | |
codes that the user is required to have. | |
""" | |
if method not in self.perms_map: | |
raise exceptions.MethodNotAllowed(method) | |
return [perm for perm in self.perms_map[method]] | |
def get_required_object_permissions(self, method): | |
""" | |
Given a HTTP method, return the list of permission | |
codes that the user is required to have for an object | |
inherit object permission from global permission if not defined | |
""" | |
if (method not in self.object_perms_map) and \ | |
(method not in self.perms_map): | |
raise exceptions.MethodNotAllowed(method) | |
# inherit global perm if not specified in object map | |
if method in self.object_perms_map: | |
object_map = self.object_perms_map[method] | |
else: | |
object_map = self.perms_map[method] | |
return [perm for perm in object_map] | |
def has_permission(self, request, view): | |
""" | |
check required perm codes with has_perms | |
""" | |
if not request.user or \ | |
(not request.user.is_authenticated and self.authenticated_users_only): | |
return False | |
perms = self.get_required_permissions(request.method) | |
return request.user.has_perms(perms) | |
def has_object_permission(self, request, view, obj): | |
""" | |
check required perm codes for a specific object with has_perms | |
""" | |
# authentication checks have already executed via has_permission | |
user = request.user | |
perms = self.get_required_object_permissions(request.method) | |
if not user.has_perms(perms, obj): | |
# If the user does not have permissions we need to determine if | |
# they have read permissions to see 403, or not, and simply see | |
# a 404 response. | |
if request.method in permissions.SAFE_METHODS: | |
# Read permissions already checked and failed, no need | |
# to make another lookup. | |
raise Http404 | |
read_perms = self.get_required_object_permissions('GET') | |
if not user.has_perms(read_perms, obj): | |
raise Http404 | |
# Has read permissions. | |
return False | |
return True | |
""" | |
Django Rules | |
https://github.com/dfunckt/django-rules | |
dynamic function mapped to permission codes | |
""" | |
import rules | |
from rules.predicates import is_staff, is_authenticated | |
# pylint: disable=invalid-name | |
is_foo_user = rules.is_group_member('foo') | |
@rules.predicate | |
def tree_is_big_enough(_, obj): | |
"""checks if a tree is big enough""" | |
return obj.height > 10 | |
rules.add_perm('tree.detail_rule_tree', is_foo_user) | |
rules.add_perm('tree.update_rule_tree', tree_is_big_enough) | |
rules.add_perm('tree.create_rule_tree', is_authenticated) | |
rules.add_perm('tree.delete_rule_tree', is_staff) | |
""" | |
REST API Permission Classes | |
""" | |
class TreePermission(DjangoRulesMethodObjectPermissions): | |
""" | |
example django rules object permission class | |
Hint: django checks global permission before object permission, | |
therefore for ex. PUT is an empty list in the perms_map, to allow | |
checking of the object permission in object_perms_map | |
""" | |
perms_map = { | |
'GET': [], | |
'OPTIONS': [], | |
'HEAD': [], | |
'POST': ['tree.create_rule_tree'], | |
'PATCH': [], | |
'PUT': [], | |
'DELETE': [] | |
} | |
object_perms_map = { | |
'GET': ['tree.detail_rule_tree'], | |
'PUT': ['tree.update_rule_tree'], | |
'PATCH': ['tree.update_rule_tree'], | |
'DELETE': ['tree.delete_rule_tree'], | |
} | |
class TreeViewSet(ModelViewSet): | |
''' | |
API endpoints for the Tree model. | |
''' | |
permission_classes = (TreePermission,) | |
""" | |
GNU General Public License v2 | |
""" |
I looked at your code and the issue leading up to it, and I must say, this is a very nice way to do things!
However, isn't this code basically just a reimplementation of Django Rest Framework's default 'DjangoObjectPermissions' class? I've tried using the default class instead of this one, and it seems to work perfectly well -- also the code is very similar. Can you point me in the direction of the important differences between these two implementations?
Thanks a lot!
This looks great, but the GPL is a problem. Django, Django Rest Framework and Django Rules all use BSD-like licenses.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For those who want to be consistent with DRF permission exception PermissionDenied in place of
Http404
.And thank you for this. I found this helpful over the drf-rules.