From 0403e7f59b8ae09fec87e5b5f6a9fc8da1fd4091 Mon Sep 17 00:00:00 2001 From: Lorin Werthen Date: Thu, 14 Jul 2016 16:10:54 +0200 Subject: [PATCH 1/2] add logical or permission operator --- rest_framework/permissions.py | 44 +++++++++++++++++++++++++++++++++++ tests/test_permissions.py | 22 ++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 8f5de0256..563a74eda 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -4,10 +4,54 @@ Provides a set of pluggable permission policies. from __future__ import unicode_literals from django.http import Http404 +from django.utils import six SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') +class BasePermissionMetaClass(type): + """ + Metaclass for the base permission class. + """ + + def __init__(cls, name, bases, dct): + super(BasePermissionMetaClass, cls).__init__(name, bases, dct) + + def __or__(klass, perm_class): + """ + Returns a new permission class performing a logical OR between two + different permission classes. + """ + assert issubclass(perm_class, BasePermission), ( + "%s is not a subclass of BasePermission" % perm_class + ) + + def has_permission(self, request, view): + perm = klass.has_permission(self, request, view) + otherwise = perm_class().has_permission(request, view) + + return perm or otherwise + + def has_object_permission(self, request, view, obj): + perm = klass.has_object_permission(self, request, view, obj) + otherwise = perm_class().has_object_permission(request, view, obj) + + return perm or otherwise + + t = type( + str('{0}Or{1}').format(klass.__name__, perm_class.__name__), + (klass,), + {} + ) + + t.has_permission = has_permission + t.has_object_permission = has_object_permission + + return t + + +# Needed for performing logical operations between permission classes. +@six.add_metaclass(BasePermissionMetaClass) class BasePermission(object): """ A base class from which all permission classes should inherit. diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 5cef22628..0c3f1cf81 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -470,3 +470,25 @@ class CustomPermissionsTests(TestCase): detail = response.data.get('detail') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(detail, self.custom_message) + + +class AllowPermission(permissions.BasePermission): + def has_permission(self, request, view): + return True + + +class DenyPermission(permissions.BasePermission): + def has_permission(self, request, view): + return False + + +class PermissionOperatorTests(TestCase): + def test_permission_logical_or(self): + # We evaluate all possible combinations. + + # We pass None to the has_permission function, request and view aren't + # relevant to these classes. + self.assertFalse((DenyPermission | DenyPermission)().has_permission(None, None)) + self.assertTrue((AllowPermission | DenyPermission)().has_permission(None, None)) + self.assertTrue((DenyPermission | AllowPermission)().has_permission(None, None)) + self.assertTrue((AllowPermission | AllowPermission)().has_permission(None, None)) From 2d762fe3f43bfd6da939207c6acb39f5deee70ec Mon Sep 17 00:00:00 2001 From: Lorin Werthen Date: Fri, 15 Jul 2016 13:47:36 +0200 Subject: [PATCH 2/2] improve coverage by adding testcases for has_object_permission as well --- tests/test_permissions.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 0c3f1cf81..f58bbe4f1 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -476,11 +476,17 @@ class AllowPermission(permissions.BasePermission): def has_permission(self, request, view): return True + def has_object_permission(self, request, view, obj): + return True + class DenyPermission(permissions.BasePermission): def has_permission(self, request, view): return False + def has_object_permission(self, request, view, obj): + return False + class PermissionOperatorTests(TestCase): def test_permission_logical_or(self): @@ -492,3 +498,8 @@ class PermissionOperatorTests(TestCase): self.assertTrue((AllowPermission | DenyPermission)().has_permission(None, None)) self.assertTrue((DenyPermission | AllowPermission)().has_permission(None, None)) self.assertTrue((AllowPermission | AllowPermission)().has_permission(None, None)) + + self.assertFalse((DenyPermission | DenyPermission)().has_object_permission(None, None, None)) + self.assertTrue((AllowPermission | DenyPermission)().has_object_permission(None, None, None)) + self.assertTrue((DenyPermission | AllowPermission)().has_object_permission(None, None, None)) + self.assertTrue((AllowPermission | AllowPermission)().has_object_permission(None, None, None))