Skip to content

Commit 6e0567c

Browse files
request.user should be still be accessible in renderer context if authentication fails
1 parent 11cbf8d commit 6e0567c

File tree

4 files changed

+61
-15
lines changed

4 files changed

+61
-15
lines changed

docs/api-guide/authentication.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ The following example will authenticate any incoming request as the user given b
333333
try:
334334
user = User.objects.get(username=username)
335335
except User.DoesNotExist:
336-
raise authenticate.AuthenticationFailed('No such user')
336+
raise exceptions.AuthenticationFailed('No such user')
337337
338338
return (user, None)
339339

optionals.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ defusedxml>=0.3
44
django-filter>=0.5.4
55
django-oauth-plus>=2.0
66
oauth2>=1.5.211
7-
django-oauth2-provider>=0.2.3
7+
django-oauth2-provider>=0.2.4

rest_framework/request.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def user(self):
173173
by the authentication classes provided to the request.
174174
"""
175175
if not hasattr(self, '_user'):
176-
self._authenticator, self._user, self._auth = self._authenticate()
176+
self._authenticate()
177177
return self._user
178178

179179
@user.setter
@@ -192,7 +192,7 @@ def auth(self):
192192
request, such as an authentication token.
193193
"""
194194
if not hasattr(self, '_auth'):
195-
self._authenticator, self._user, self._auth = self._authenticate()
195+
self._authenticate()
196196
return self._auth
197197

198198
@auth.setter
@@ -210,7 +210,7 @@ def successful_authenticator(self):
210210
to authenticate the request, or `None`.
211211
"""
212212
if not hasattr(self, '_authenticator'):
213-
self._authenticator, self._user, self._auth = self._authenticate()
213+
self._authenticate()
214214
return self._authenticator
215215

216216
def _load_data_and_files(self):
@@ -330,11 +330,18 @@ def _authenticate(self):
330330
Returns a three-tuple of (authenticator, user, authtoken).
331331
"""
332332
for authenticator in self.authenticators:
333-
user_auth_tuple = authenticator.authenticate(self)
333+
try:
334+
user_auth_tuple = authenticator.authenticate(self)
335+
except exceptions.APIException:
336+
self._not_authenticated()
337+
raise
338+
334339
if not user_auth_tuple is None:
335-
user, auth = user_auth_tuple
336-
return (authenticator, user, auth)
337-
return self._not_authenticated()
340+
self._authenticator = authenticator
341+
self._user, self._auth = user_auth_tuple
342+
return
343+
344+
self._not_authenticated()
338345

339346
def _not_authenticated(self):
340347
"""
@@ -343,17 +350,17 @@ def _not_authenticated(self):
343350
344351
By default this will be (None, AnonymousUser, None).
345352
"""
353+
self._authenticator = None
354+
346355
if api_settings.UNAUTHENTICATED_USER:
347-
user = api_settings.UNAUTHENTICATED_USER()
356+
self._user = api_settings.UNAUTHENTICATED_USER()
348357
else:
349-
user = None
358+
self._user = None
350359

351360
if api_settings.UNAUTHENTICATED_TOKEN:
352-
auth = api_settings.UNAUTHENTICATED_TOKEN()
361+
self._auth = api_settings.UNAUTHENTICATED_TOKEN()
353362
else:
354-
auth = None
355-
356-
return (None, user, auth)
363+
self._auth = None
357364

358365
def __getattr__(self, attr):
359366
"""

rest_framework/tests/test_authentication.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from rest_framework import HTTP_HEADER_ENCODING
77
from rest_framework import exceptions
88
from rest_framework import permissions
9+
from rest_framework import renderers
10+
from rest_framework.response import Response
911
from rest_framework import status
1012
from rest_framework.authentication import (
1113
BaseAuthentication,
@@ -553,3 +555,40 @@ def test_post_form_with_valid_scope_passing_auth(self):
553555
auth = self._create_authorization_header(token=read_write_access_token.token)
554556
response = self.csrf_client.post('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth)
555557
self.assertEqual(response.status_code, 200)
558+
559+
560+
class FailingAuthAccessedInRenderer(TestCase):
561+
def setUp(self):
562+
class AuthAccessingRenderer(renderers.BaseRenderer):
563+
media_type = 'text/plain'
564+
format = 'txt'
565+
566+
def render(self, data, media_type=None, renderer_context=None):
567+
request = renderer_context['request']
568+
if request.user.is_authenticated():
569+
return b'authenticated'
570+
return b'not authenticated'
571+
572+
class FailingAuth(BaseAuthentication):
573+
def authenticate(self, request):
574+
raise exceptions.AuthenticationFailed('authentication failed')
575+
576+
class ExampleView(APIView):
577+
authentication_classes = (FailingAuth,)
578+
renderer_classes = (AuthAccessingRenderer,)
579+
580+
def get(self, request):
581+
return Response({'foo': 'bar'})
582+
583+
self.view = ExampleView.as_view()
584+
585+
def test_failing_auth_accessed_in_renderer(self):
586+
"""
587+
When authentication fails the renderer should still be able to access
588+
`request.user` without raising an exception. Particularly relevant
589+
to HTML responses that might reasonably access `request.user`.
590+
"""
591+
request = factory.get('/')
592+
response = self.view(request)
593+
content = response.render().content
594+
self.assertEqual(content, b'not authenticated')

0 commit comments

Comments
 (0)