mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-02 19:40:13 +03:00
permissions: Allow permissions to be composed
Implement a system to compose permissions with and / or. This is performed by returning an `OperationHolder` instance that keeps the permission classes and type of composition (and / or). When called it will return a AND/OR instance that will then delegate the permission check to the operands.
This commit is contained in:
parent
d1514d1f9c
commit
daea006433
|
@ -4,12 +4,76 @@ Provides a set of pluggable permission policies.
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
from django.utils import six
|
||||||
|
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
|
|
||||||
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
|
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
|
||||||
|
|
||||||
|
|
||||||
|
class OperandHolder:
|
||||||
|
def __init__(self, operator_class, op1_class, op2_class):
|
||||||
|
self.operator_class = operator_class
|
||||||
|
self.op1_class = op1_class
|
||||||
|
self.op2_class = op2_class
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
op1 = self.op1_class(*args, **kwargs)
|
||||||
|
op2 = self.op2_class(*args, **kwargs)
|
||||||
|
return self.operator_class(op1, op2)
|
||||||
|
|
||||||
|
|
||||||
|
class AND:
|
||||||
|
def __init__(self, op1, op2):
|
||||||
|
self.op1 = op1
|
||||||
|
self.op2 = op2
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return (
|
||||||
|
self.op1.has_permission(request, view) &
|
||||||
|
self.op2.has_permission(request, view)
|
||||||
|
)
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
return (
|
||||||
|
self.op1.has_object_permission(request, view, obj) &
|
||||||
|
self.op2.has_object_permission(request, view, obj)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OR:
|
||||||
|
def __init__(self, op1, op2):
|
||||||
|
self.op1 = op1
|
||||||
|
self.op2 = op2
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return (
|
||||||
|
self.op1.has_permission(request, view) |
|
||||||
|
self.op2.has_permission(request, view)
|
||||||
|
)
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
return (
|
||||||
|
self.op1.has_object_permission(request, view, obj) |
|
||||||
|
self.op2.has_object_permission(request, view, obj)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BasePermissionMetaclass(type):
|
||||||
|
def __and__(cls, other):
|
||||||
|
return OperandHolder(AND, cls, other)
|
||||||
|
|
||||||
|
def __or__(cls, other):
|
||||||
|
return OperandHolder(OR, cls, other)
|
||||||
|
|
||||||
|
def __rand__(cls, other):
|
||||||
|
return OperandHolder(AND, other, cls)
|
||||||
|
|
||||||
|
def __ror__(cls, other):
|
||||||
|
return OperandHolder(OR, other, cls)
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(BasePermissionMetaclass)
|
||||||
class BasePermission(object):
|
class BasePermission(object):
|
||||||
"""
|
"""
|
||||||
A base class from which all permission classes should inherit.
|
A base class from which all permission classes should inherit.
|
||||||
|
|
|
@ -522,3 +522,45 @@ class CustomPermissionsTests(TestCase):
|
||||||
detail = response.data.get('detail')
|
detail = response.data.get('detail')
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertEqual(detail, self.custom_message)
|
self.assertEqual(detail, self.custom_message)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeUser:
|
||||||
|
def __init__(self, auth=False):
|
||||||
|
self.is_authenticated = auth
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionsCompositionTests(TestCase):
|
||||||
|
def test_and_false(self):
|
||||||
|
request = factory.get('/1', format='json')
|
||||||
|
request.user = FakeUser(auth=False)
|
||||||
|
composed_perm = permissions.IsAuthenticated & permissions.AllowAny
|
||||||
|
assert composed_perm().has_permission(request, None) is False
|
||||||
|
|
||||||
|
def test_and_true(self):
|
||||||
|
request = factory.get('/1', format='json')
|
||||||
|
request.user = FakeUser(auth=True)
|
||||||
|
composed_perm = permissions.IsAuthenticated & permissions.AllowAny
|
||||||
|
assert composed_perm().has_permission(request, None) is True
|
||||||
|
|
||||||
|
def test_or_false(self):
|
||||||
|
request = factory.get('/1', format='json')
|
||||||
|
request.user = FakeUser(auth=False)
|
||||||
|
composed_perm = permissions.IsAuthenticated | permissions.AllowAny
|
||||||
|
assert composed_perm().has_permission(request, None) is True
|
||||||
|
|
||||||
|
def test_or_true(self):
|
||||||
|
request = factory.get('/1', format='json')
|
||||||
|
request.user = FakeUser(auth=True)
|
||||||
|
composed_perm = permissions.IsAuthenticated | permissions.AllowAny
|
||||||
|
assert composed_perm().has_permission(request, None) is True
|
||||||
|
|
||||||
|
def test_several_levels(self):
|
||||||
|
request = factory.get('/1', format='json')
|
||||||
|
request.user = FakeUser(auth=True)
|
||||||
|
composed_perm = (
|
||||||
|
permissions.IsAuthenticated &
|
||||||
|
permissions.IsAuthenticated &
|
||||||
|
permissions.IsAuthenticated &
|
||||||
|
permissions.IsAuthenticated
|
||||||
|
)
|
||||||
|
assert composed_perm().has_permission(request, None) is True
|
||||||
|
|
Loading…
Reference in New Issue
Block a user