Skip to content

Commit 32b44d9

Browse files
FMCorzcarltongibson
authored andcommitted
Added lazy evaluation to composed permissions.
Refs #6402.
1 parent 8a29c53 commit 32b44d9

File tree

3 files changed

+97
-5
lines changed

3 files changed

+97
-5
lines changed

rest_framework/compat.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from __future__ import unicode_literals
77

8+
import sys
9+
810
from django.conf import settings
911
from django.core import validators
1012
from django.utils import six
@@ -314,3 +316,7 @@ class MinLengthValidator(CustomValidatorMessage, validators.MinLengthValidator):
314316

315317
class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator):
316318
pass
319+
320+
321+
# Version Constants.
322+
PY36 = sys.version_info >= (3, 6)

rest_framework/permissions.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ def __init__(self, op1, op2):
4444

4545
def has_permission(self, request, view):
4646
return (
47-
self.op1.has_permission(request, view) &
47+
self.op1.has_permission(request, view) and
4848
self.op2.has_permission(request, view)
4949
)
5050

5151
def has_object_permission(self, request, view, obj):
5252
return (
53-
self.op1.has_object_permission(request, view, obj) &
53+
self.op1.has_object_permission(request, view, obj) and
5454
self.op2.has_object_permission(request, view, obj)
5555
)
5656

@@ -62,13 +62,13 @@ def __init__(self, op1, op2):
6262

6363
def has_permission(self, request, view):
6464
return (
65-
self.op1.has_permission(request, view) |
65+
self.op1.has_permission(request, view) or
6666
self.op2.has_permission(request, view)
6767
)
6868

6969
def has_object_permission(self, request, view, obj):
7070
return (
71-
self.op1.has_object_permission(request, view, obj) |
71+
self.op1.has_object_permission(request, view, obj) or
7272
self.op2.has_object_permission(request, view, obj)
7373
)
7474

tests/test_permissions.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import base64
44
import unittest
55
import warnings
6+
from unittest import mock
67

78
import django
9+
import pytest
810
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
911
from django.db import models
1012
from django.test import TestCase
@@ -14,7 +16,7 @@
1416
HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers,
1517
status, views
1618
)
17-
from rest_framework.compat import is_guardian_installed
19+
from rest_framework.compat import PY36, is_guardian_installed
1820
from rest_framework.filters import DjangoObjectPermissionsFilter
1921
from rest_framework.routers import DefaultRouter
2022
from rest_framework.test import APIRequestFactory
@@ -600,3 +602,87 @@ def test_several_levels_and_precedence(self):
600602
permissions.IsAuthenticated
601603
)
602604
assert composed_perm().has_permission(request, None) is True
605+
606+
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
607+
def test_or_lazyness(self):
608+
request = factory.get('/1', format='json')
609+
request.user = AnonymousUser()
610+
611+
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
612+
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
613+
composed_perm = (permissions.AllowAny | permissions.IsAuthenticated)
614+
hasperm = composed_perm().has_permission(request, None)
615+
self.assertIs(hasperm, True)
616+
mock_allow.assert_called_once()
617+
mock_deny.assert_not_called()
618+
619+
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
620+
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
621+
composed_perm = (permissions.IsAuthenticated | permissions.AllowAny)
622+
hasperm = composed_perm().has_permission(request, None)
623+
self.assertIs(hasperm, True)
624+
mock_deny.assert_called_once()
625+
mock_allow.assert_called_once()
626+
627+
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
628+
def test_object_or_lazyness(self):
629+
request = factory.get('/1', format='json')
630+
request.user = AnonymousUser()
631+
632+
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
633+
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
634+
composed_perm = (permissions.AllowAny | permissions.IsAuthenticated)
635+
hasperm = composed_perm().has_object_permission(request, None, None)
636+
self.assertIs(hasperm, True)
637+
mock_allow.assert_called_once()
638+
mock_deny.assert_not_called()
639+
640+
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
641+
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
642+
composed_perm = (permissions.IsAuthenticated | permissions.AllowAny)
643+
hasperm = composed_perm().has_object_permission(request, None, None)
644+
self.assertIs(hasperm, True)
645+
mock_deny.assert_called_once()
646+
mock_allow.assert_called_once()
647+
648+
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
649+
def test_and_lazyness(self):
650+
request = factory.get('/1', format='json')
651+
request.user = AnonymousUser()
652+
653+
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
654+
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
655+
composed_perm = (permissions.AllowAny & permissions.IsAuthenticated)
656+
hasperm = composed_perm().has_permission(request, None)
657+
self.assertIs(hasperm, False)
658+
mock_allow.assert_called_once()
659+
mock_deny.assert_called_once()
660+
661+
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
662+
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
663+
composed_perm = (permissions.IsAuthenticated & permissions.AllowAny)
664+
hasperm = composed_perm().has_permission(request, None)
665+
self.assertIs(hasperm, False)
666+
mock_allow.assert_not_called()
667+
mock_deny.assert_called_once()
668+
669+
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
670+
def test_object_and_lazyness(self):
671+
request = factory.get('/1', format='json')
672+
request.user = AnonymousUser()
673+
674+
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
675+
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
676+
composed_perm = (permissions.AllowAny & permissions.IsAuthenticated)
677+
hasperm = composed_perm().has_object_permission(request, None, None)
678+
self.assertIs(hasperm, False)
679+
mock_allow.assert_called_once()
680+
mock_deny.assert_called_once()
681+
682+
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
683+
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
684+
composed_perm = (permissions.IsAuthenticated & permissions.AllowAny)
685+
hasperm = composed_perm().has_object_permission(request, None, None)
686+
self.assertIs(hasperm, False)
687+
mock_allow.assert_not_called()
688+
mock_deny.assert_called_once()

0 commit comments

Comments
 (0)