mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Auth is no longer lazy. Closes #667.
More consistent auth failure behavior.
This commit is contained in:
parent
4e14b26fa9
commit
13b3af0d22
|
@ -10,7 +10,7 @@ Authentication is the mechanism of associating an incoming request with a set of
|
||||||
|
|
||||||
REST framework provides a number of authentication schemes out of the box, and also allows you to implement custom schemes.
|
REST framework provides a number of authentication schemes out of the box, and also allows you to implement custom schemes.
|
||||||
|
|
||||||
Authentication will run the first time either the `request.user` or `request.auth` properties are accessed, and determines how those properties are initialized.
|
Authentication is always run at the very start of the view, before the permission and throttling checks occur, and before any other code is allowed to proceed.
|
||||||
|
|
||||||
The `request.user` property will typically be set to an instance of the `contrib.auth` package's `User` class.
|
The `request.user` property will typically be set to an instance of the `contrib.auth` package's `User` class.
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ In some circumstances instead of returning `None`, you may want to raise an `Aut
|
||||||
Typically the approach you should take is:
|
Typically the approach you should take is:
|
||||||
|
|
||||||
* If authentication is not attempted, return `None`. Any other authentication schemes also in use will still be checked.
|
* If authentication is not attempted, return `None`. Any other authentication schemes also in use will still be checked.
|
||||||
* If authentication is attempted but fails, raise a `AuthenticationFailed` exception. An error response will be returned immediately, without checking any other authentication schemes.
|
* If authentication is attempted but fails, raise a `AuthenticationFailed` exception. An error response will be returned immediately, regardless of any permissions checks, and without checking any other authentication schemes.
|
||||||
|
|
||||||
You *may* also override the `.authenticate_header(self, request)` method. If implemented, it should return a string that will be used as the value of the `WWW-Authenticate` header in a `HTTP 401 Unauthorized` response.
|
You *may* also override the `.authenticate_header(self, request)` method. If implemented, it should return a string that will be used as the value of the `WWW-Authenticate` header in a `HTTP 401 Unauthorized` response.
|
||||||
|
|
||||||
|
|
|
@ -3,25 +3,39 @@ from django.contrib.auth.models import User
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from rest_framework import HTTP_HEADER_ENCODING
|
from rest_framework import HTTP_HEADER_ENCODING
|
||||||
|
from rest_framework import exceptions
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.authentication import TokenAuthentication, BasicAuthentication, SessionAuthentication
|
from rest_framework.authentication import (
|
||||||
|
BaseAuthentication,
|
||||||
|
TokenAuthentication,
|
||||||
|
BasicAuthentication,
|
||||||
|
SessionAuthentication
|
||||||
|
)
|
||||||
from rest_framework.compat import patterns
|
from rest_framework.compat import patterns
|
||||||
|
from rest_framework.tests.utils import RequestFactory
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
factory = RequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class MockView(APIView):
|
class MockView(APIView):
|
||||||
permission_classes = (permissions.IsAuthenticated,)
|
permission_classes = (permissions.IsAuthenticated,)
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
||||||
|
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
(r'^session/$', MockView.as_view(authentication_classes=[SessionAuthentication])),
|
(r'^session/$', MockView.as_view(authentication_classes=[SessionAuthentication])),
|
||||||
(r'^basic/$', MockView.as_view(authentication_classes=[BasicAuthentication])),
|
(r'^basic/$', MockView.as_view(authentication_classes=[BasicAuthentication])),
|
||||||
|
@ -187,3 +201,24 @@ class TokenAuthTests(TestCase):
|
||||||
{'username': self.username, 'password': self.password})
|
{'username': self.username, 'password': self.password})
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(json.loads(response.content.decode('ascii'))['token'], self.key)
|
self.assertEqual(json.loads(response.content.decode('ascii'))['token'], self.key)
|
||||||
|
|
||||||
|
|
||||||
|
class IncorrectCredentialsTests(TestCase):
|
||||||
|
def test_incorrect_credentials(self):
|
||||||
|
"""
|
||||||
|
If a request contains bad authentication credentials, then
|
||||||
|
authentication should run and error, even if no permissions
|
||||||
|
are set on the view.
|
||||||
|
"""
|
||||||
|
class IncorrectCredentialsAuth(BaseAuthentication):
|
||||||
|
def authenticate(self, request):
|
||||||
|
raise exceptions.AuthenticationFailed('Bad credentials')
|
||||||
|
|
||||||
|
request = factory.get('/')
|
||||||
|
view = MockView.as_view(
|
||||||
|
authentication_classes=(IncorrectCredentialsAuth,),
|
||||||
|
permission_classes=()
|
||||||
|
)
|
||||||
|
response = view(request)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(response.data, {'detail': 'Bad credentials'})
|
||||||
|
|
|
@ -257,6 +257,16 @@ class APIView(View):
|
||||||
return (renderers[0], renderers[0].media_type)
|
return (renderers[0], renderers[0].media_type)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def perform_authentication(self, request):
|
||||||
|
"""
|
||||||
|
Perform authentication on the incoming request.
|
||||||
|
|
||||||
|
Note that if you override this and simply 'pass', then authentication
|
||||||
|
will instead be performed lazily, the first time either
|
||||||
|
`request.user` or `request.auth` is accessed.
|
||||||
|
"""
|
||||||
|
request.user
|
||||||
|
|
||||||
def check_permissions(self, request):
|
def check_permissions(self, request):
|
||||||
"""
|
"""
|
||||||
Check if the request should be permitted.
|
Check if the request should be permitted.
|
||||||
|
@ -305,6 +315,7 @@ class APIView(View):
|
||||||
self.format_kwarg = self.get_format_suffix(**kwargs)
|
self.format_kwarg = self.get_format_suffix(**kwargs)
|
||||||
|
|
||||||
# Ensure that the incoming request is permitted
|
# Ensure that the incoming request is permitted
|
||||||
|
self.perform_authentication(request)
|
||||||
self.check_permissions(request)
|
self.check_permissions(request)
|
||||||
self.check_throttles(request)
|
self.check_throttles(request)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user