Skip to content

Commit 233498f

Browse files
author
Jens Timmerman
committed
added tests, fixed an error the tests revealed
1 parent 9c86cfa commit 233498f

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

oauth2_provider/ext/rest_framework/permissions.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.core.exceptions import ImproperlyConfigured
44

55
from rest_framework.permissions import BasePermission, IsAuthenticated
6+
from .authentication import OAuth2Authentication
67

78
from ...settings import oauth2_settings
89

@@ -89,11 +90,18 @@ def get_scopes(self, request, view):
8990
class IsAuthenticatedOrTokenHasScope(BasePermission):
9091
"""
9192
The user is authenticated using some backend or the token has the right scope
93+
This only returns True if the user is authenticated, but not using a token
94+
or using a token, and the token has the correct scope.
95+
9296
This is usefull when combined with the DjangoModelPermissions to allow people browse the browsable api's
9397
if they log in using the a non token bassed middleware,
9498
and let them access the api's using a rest client with a token
9599
"""
96100
def has_permission(self, request, view):
97-
is_authenticated = IsAuthenticated()
101+
is_authenticated = IsAuthenticated().has_permission(request, view)
102+
oauth2authenticated = False
103+
if is_authenticated:
104+
oauth2authenticated = isinstance(request.successful_authenticator, OAuth2Authentication)
105+
98106
token_has_scope = TokenHasScope()
99-
return is_authenticated.has_permission(request, view) or token_has_scope.has_permission(request, view)
107+
return (is_authenticated and not oauth2authenticated) or token_has_scope.has_permission(request, view)

oauth2_provider/tests/test_rest_framework.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
try:
2020
from rest_framework import permissions
2121
from rest_framework.views import APIView
22+
from rest_framework.test import force_authenticate, APIRequestFactory
2223
from ..ext.rest_framework import OAuth2Authentication, TokenHasScope, TokenHasReadWriteScope, TokenHasResourceScope
24+
from ..ext.rest_framework import IsAuthenticatedOrTokenHasScope
2325

2426
class MockView(APIView):
2527
permission_classes = (permissions.IsAuthenticated,)
@@ -37,6 +39,10 @@ class ScopedView(OAuth2View):
3739
permission_classes = [permissions.IsAuthenticated, TokenHasScope]
3840
required_scopes = ['scope1']
3941

42+
class AuthenticatedOrScopedView(OAuth2View):
43+
permission_classes = [IsAuthenticatedOrTokenHasScope]
44+
required_scopes = ['scope1']
45+
4046
class ReadWriteScopedView(OAuth2View):
4147
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
4248

@@ -51,6 +57,7 @@ class ResourceScopedView(OAuth2View):
5157
url(r'^oauth2-scoped-test/$', ScopedView.as_view()),
5258
url(r'^oauth2-read-write-test/$', ReadWriteScopedView.as_view()),
5359
url(r'^oauth2-resource-scoped-test/$', ResourceScopedView.as_view()),
60+
url(r'^oauth2-authenticated-or-scoped-test/$', AuthenticatedOrScopedView.as_view()),
5461
)
5562

5663
rest_framework_installed = True
@@ -105,6 +112,24 @@ def test_authentication_denied(self):
105112
response = self.client.get("/oauth2-test/", HTTP_AUTHORIZATION=auth)
106113
self.assertEqual(response.status_code, 401)
107114

115+
@unittest.skipUnless(rest_framework_installed, 'djangorestframework not installed')
116+
def test_authentication_or_scope_denied(self):
117+
# user is not authenticated
118+
# not a correct token
119+
auth = self._create_authorization_header("fake-token")
120+
response = self.client.get("/oauth2-authenticated-or-scoped-test/", HTTP_AUTHORIZATION=auth)
121+
self.assertEqual(response.status_code, 401)
122+
# token doesn't have correct scope
123+
auth = self._create_authorization_header(self.access_token.token)
124+
125+
factory = APIRequestFactory()
126+
request = factory.get("/oauth2-authenticated-or-scoped-test/")
127+
request.auth = auth
128+
force_authenticate(request, token=self.access_token)
129+
response = AuthenticatedOrScopedView.as_view()(request)
130+
# authenticated but wrong scope, this is 403, not 401
131+
self.assertEqual(response.status_code, 403)
132+
108133
@unittest.skipUnless(rest_framework_installed, 'djangorestframework not installed')
109134
def test_scoped_permission_allow(self):
110135
self.access_token.scope = 'scope1'
@@ -114,6 +139,33 @@ def test_scoped_permission_allow(self):
114139
response = self.client.get("/oauth2-scoped-test/", HTTP_AUTHORIZATION=auth)
115140
self.assertEqual(response.status_code, 200)
116141

142+
@unittest.skipUnless(rest_framework_installed, 'djangorestframework not installed')
143+
def test_authenticated_or_scoped_permission_allow(self):
144+
self.access_token.scope = 'scope1'
145+
self.access_token.save()
146+
# correct token and correct scope
147+
auth = self._create_authorization_header(self.access_token.token)
148+
response = self.client.get("/oauth2-authenticated-or-scoped-test/", HTTP_AUTHORIZATION=auth)
149+
self.assertEqual(response.status_code, 200)
150+
151+
auth = self._create_authorization_header("fake-token")
152+
# incorrect token but authenticated
153+
factory = APIRequestFactory()
154+
request = factory.get("/oauth2-authenticated-or-scoped-test/")
155+
request.auth = auth
156+
force_authenticate(request, self.test_user)
157+
response = AuthenticatedOrScopedView.as_view()(request)
158+
self.assertEqual(response.status_code, 200)
159+
160+
# correct token but not authenticated
161+
request = factory.get("/oauth2-authenticated-or-scoped-test/")
162+
request.auth = auth
163+
self.access_token.scope = 'scope1'
164+
self.access_token.save()
165+
force_authenticate(request, token=self.access_token)
166+
response = AuthenticatedOrScopedView.as_view()(request)
167+
self.assertEqual(response.status_code, 200)
168+
117169
@unittest.skipUnless(rest_framework_installed, 'djangorestframework not installed')
118170
def test_scoped_permission_deny(self):
119171
self.access_token.scope = 'scope2'

0 commit comments

Comments
 (0)