Skip to content

Commit 94fbfcb

Browse files
FMCorzcarltongibson
authored andcommitted
Added lazy evaluation to composed permissions. (#6463)
Refs #6402.
1 parent 8a29c53 commit 94fbfcb

File tree

3 files changed

+101
-5
lines changed

3 files changed

+101
-5
lines changed

rest_framework/compat.py

Lines changed: 11 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
@@ -34,6 +36,11 @@
3436
except ImportError:
3537
ProhibitNullCharactersValidator = None
3638

39+
try:
40+
from unittest import mock
41+
except ImportError:
42+
mock = None
43+
3744

3845
def get_original_route(urlpattern):
3946
"""
@@ -314,3 +321,7 @@ class MinLengthValidator(CustomValidatorMessage, validators.MinLengthValidator):
314321

315322
class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator):
316323
pass
324+
325+
326+
# Version Constants.
327+
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: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import warnings
66

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy