Added lazy evaluation to composed permissions. (#6463)

Refs #6402.
This commit is contained in:
Frédéric Massart 2019-02-25 20:47:02 +08:00 committed by Carlton Gibson
parent 8a29c53226
commit 94fbfcb6fd
3 changed files with 101 additions and 5 deletions

View File

@ -5,6 +5,8 @@ versions of Django/Python, and compatibility wrappers around optional packages.
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from django.conf import settings from django.conf import settings
from django.core import validators from django.core import validators
from django.utils import six from django.utils import six
@ -34,6 +36,11 @@ try:
except ImportError: except ImportError:
ProhibitNullCharactersValidator = None ProhibitNullCharactersValidator = None
try:
from unittest import mock
except ImportError:
mock = None
def get_original_route(urlpattern): def get_original_route(urlpattern):
""" """
@ -314,3 +321,7 @@ class MinLengthValidator(CustomValidatorMessage, validators.MinLengthValidator):
class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator): class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator):
pass pass
# Version Constants.
PY36 = sys.version_info >= (3, 6)

View File

@ -44,13 +44,13 @@ class AND:
def has_permission(self, request, view): def has_permission(self, request, view):
return ( return (
self.op1.has_permission(request, view) & self.op1.has_permission(request, view) and
self.op2.has_permission(request, view) self.op2.has_permission(request, view)
) )
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return ( return (
self.op1.has_object_permission(request, view, obj) & self.op1.has_object_permission(request, view, obj) and
self.op2.has_object_permission(request, view, obj) self.op2.has_object_permission(request, view, obj)
) )
@ -62,13 +62,13 @@ class OR:
def has_permission(self, request, view): def has_permission(self, request, view):
return ( return (
self.op1.has_permission(request, view) | self.op1.has_permission(request, view) or
self.op2.has_permission(request, view) self.op2.has_permission(request, view)
) )
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
return ( return (
self.op1.has_object_permission(request, view, obj) | self.op1.has_object_permission(request, view, obj) or
self.op2.has_object_permission(request, view, obj) self.op2.has_object_permission(request, view, obj)
) )

View File

@ -5,6 +5,7 @@ import unittest
import warnings import warnings
import django import django
import pytest
from django.contrib.auth.models import AnonymousUser, Group, Permission, User from django.contrib.auth.models import AnonymousUser, Group, Permission, User
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
@ -14,7 +15,7 @@ from rest_framework import (
HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers,
status, views status, views
) )
from rest_framework.compat import is_guardian_installed from rest_framework.compat import PY36, is_guardian_installed, mock
from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.filters import DjangoObjectPermissionsFilter
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
@ -600,3 +601,87 @@ class PermissionsCompositionTests(TestCase):
permissions.IsAuthenticated permissions.IsAuthenticated
) )
assert composed_perm().has_permission(request, None) is True assert composed_perm().has_permission(request, None) is True
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
def test_or_lazyness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
composed_perm = (permissions.AllowAny | permissions.IsAuthenticated)
hasperm = composed_perm().has_permission(request, None)
self.assertIs(hasperm, True)
mock_allow.assert_called_once()
mock_deny.assert_not_called()
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
composed_perm = (permissions.IsAuthenticated | permissions.AllowAny)
hasperm = composed_perm().has_permission(request, None)
self.assertIs(hasperm, True)
mock_deny.assert_called_once()
mock_allow.assert_called_once()
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
def test_object_or_lazyness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
composed_perm = (permissions.AllowAny | permissions.IsAuthenticated)
hasperm = composed_perm().has_object_permission(request, None, None)
self.assertIs(hasperm, True)
mock_allow.assert_called_once()
mock_deny.assert_not_called()
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
composed_perm = (permissions.IsAuthenticated | permissions.AllowAny)
hasperm = composed_perm().has_object_permission(request, None, None)
self.assertIs(hasperm, True)
mock_deny.assert_called_once()
mock_allow.assert_called_once()
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
def test_and_lazyness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
composed_perm = (permissions.AllowAny & permissions.IsAuthenticated)
hasperm = composed_perm().has_permission(request, None)
self.assertIs(hasperm, False)
mock_allow.assert_called_once()
mock_deny.assert_called_once()
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
composed_perm = (permissions.IsAuthenticated & permissions.AllowAny)
hasperm = composed_perm().has_permission(request, None)
self.assertIs(hasperm, False)
mock_allow.assert_not_called()
mock_deny.assert_called_once()
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
def test_object_and_lazyness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
composed_perm = (permissions.AllowAny & permissions.IsAuthenticated)
hasperm = composed_perm().has_object_permission(request, None, None)
self.assertIs(hasperm, False)
mock_allow.assert_called_once()
mock_deny.assert_called_once()
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
composed_perm = (permissions.IsAuthenticated & permissions.AllowAny)
hasperm = composed_perm().has_object_permission(request, None, None)
self.assertIs(hasperm, False)
mock_allow.assert_not_called()
mock_deny.assert_called_once()