Skip to content

Commit 37688e5

Browse files
FMCorzcarltongibson
authored andcommitted
Added lazy evaluation to composed permissions.
Refs #6402.
1 parent d110454 commit 37688e5

File tree

3 files changed

+98
-5
lines changed

3 files changed

+98
-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: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@
33
import base64
44
import unittest
55
import warnings
6+
from unittest import mock
67

78
import django
89
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
910
from django.db import models
1011
from django.test import TestCase
1112
from django.urls import ResolverMatch
1213

14+
import pytest
15+
1316
from rest_framework import (
1417
HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers,
1518
status, views
1619
)
17-
from rest_framework.compat import is_guardian_installed
20+
from rest_framework.compat import PY36, is_guardian_installed
1821
from rest_framework.filters import DjangoObjectPermissionsFilter
1922
from rest_framework.routers import DefaultRouter
2023
from rest_framework.test import APIRequestFactory
@@ -600,3 +603,87 @@ def test_several_levels_and_precedence(self):
600603
permissions.IsAuthenticated
601604
)
602605
assert composed_perm().has_permission(request, None) is True
606+
607+
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
608+
def test_or_lazyness(self):
609+
request = factory.get('/1', format='json')
610+
request.user = AnonymousUser()
611+
612+
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
613+
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
614+
composed_perm = (permissions.AllowAny | permissions.IsAuthenticated)
615+
hasperm = composed_perm().has_permission(request, None)
616+
self.assertIs(hasperm, True)
617+
mock_allow.assert_called_once()
618+
mock_deny.assert_not_called()
619+
620+
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
621+
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
622+
composed_perm = (permissions.IsAuthenticated | permissions.AllowAny)
623+
hasperm = composed_perm().has_permission(request, None)
624+
self.assertIs(hasperm, True)
625+
mock_deny.assert_called_once()
626+
mock_allow.assert_called_once()
627+
628+
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
629+
def test_object_or_lazyness(self):
630+
request = factory.get('/1', format='json')
631+
request.user = AnonymousUser()
632+
633+
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
634+
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
635+
composed_perm = (permissions.AllowAny | permissions.IsAuthenticated)
636+
hasperm = composed_perm().has_object_permission(request, None, None)
637+
self.assertIs(hasperm, True)
638+
mock_allow.assert_called_once()
639+
mock_deny.assert_not_called()
640+
641+
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
642+
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
643+
composed_perm = (permissions.IsAuthenticated | permissions.AllowAny)
644+
hasperm = composed_perm().has_object_permission(request, None, None)
645+
self.assertIs(hasperm, True)
646+
mock_deny.assert_called_once()
647+
mock_allow.assert_called_once()
648+
649+
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
650+
def test_and_lazyness(self):
651+
request = factory.get('/1', format='json')
652+
request.user = AnonymousUser()
653+
654+
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
655+
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
656+
composed_perm = (permissions.AllowAny & permissions.IsAuthenticated)
657+
hasperm = composed_perm().has_permission(request, None)
658+
self.assertIs(hasperm, False)
659+
mock_allow.assert_called_once()
660+
mock_deny.assert_called_once()
661+
662+
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
663+
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
664+
composed_perm = (permissions.IsAuthenticated & permissions.AllowAny)
665+
hasperm = composed_perm().has_permission(request, None)
666+
self.assertIs(hasperm, False)
667+
mock_allow.assert_not_called()
668+
mock_deny.assert_called_once()
669+
670+
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
671+
def test_object_and_lazyness(self):
672+
request = factory.get('/1', format='json')
673+
request.user = AnonymousUser()
674+
675+
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
676+
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
677+
composed_perm = (permissions.AllowAny & permissions.IsAuthenticated)
678+
hasperm = composed_perm().has_object_permission(request, None, None)
679+
self.assertIs(hasperm, False)
680+
mock_allow.assert_called_once()
681+
mock_deny.assert_called_once()
682+
683+
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
684+
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
685+
composed_perm = (permissions.IsAuthenticated & permissions.AllowAny)
686+
hasperm = composed_perm().has_object_permission(request, None, None)
687+
self.assertIs(hasperm, False)
688+
mock_allow.assert_not_called()
689+
mock_deny.assert_called_once()

0 commit comments

Comments
 (0)