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))