aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2013-02-28 17:58:58 +0000
committerTom Christie2013-02-28 17:58:58 +0000
commit13b3af0d22bdbae5be0eb39ea50219c1fb83e28f (patch)
tree26faec0481b8bb82e726ac4fc109b5f65d292a82
parent4e14b26fa9727a79f8ae7c7ef25d1339500fa26c (diff)
downloaddjango-rest-framework-13b3af0d22bdbae5be0eb39ea50219c1fb83e28f.tar.bz2
Auth is no longer lazy. Closes #667.
More consistent auth failure behavior.
-rw-r--r--docs/api-guide/authentication.md4
-rw-r--r--rest_framework/tests/authentication.py37
-rw-r--r--rest_framework/views.py11
3 files changed, 49 insertions, 3 deletions
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index 342fabe7..fae86386 100644
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
@@ -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.
-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.
@@ -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:
* 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.
diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py
index 2a2bfba9..7b754af5 100644
--- a/rest_framework/tests/authentication.py
+++ b/rest_framework/tests/authentication.py
@@ -3,25 +3,39 @@ from django.contrib.auth.models import User
from django.http import HttpResponse
from django.test import Client, TestCase
from rest_framework import HTTP_HEADER_ENCODING
+from rest_framework import exceptions
from rest_framework import permissions
from rest_framework import status
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.tests.utils import RequestFactory
from rest_framework.views import APIView
import json
import base64
+factory = RequestFactory()
+
+
class MockView(APIView):
permission_classes = (permissions.IsAuthenticated,)
+ def get(self, request):
+ return HttpResponse({'a': 1, 'b': 2, 'c': 3})
+
def post(self, request):
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
def put(self, request):
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
+
urlpatterns = patterns('',
(r'^session/$', MockView.as_view(authentication_classes=[SessionAuthentication])),
(r'^basic/$', MockView.as_view(authentication_classes=[BasicAuthentication])),
@@ -187,3 +201,24 @@ class TokenAuthTests(TestCase):
{'username': self.username, 'password': self.password})
self.assertEqual(response.status_code, status.HTTP_200_OK)
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'})
diff --git a/rest_framework/views.py b/rest_framework/views.py
index fa742582..81cbdcbb 100644
--- a/rest_framework/views.py
+++ b/rest_framework/views.py
@@ -257,6 +257,16 @@ class APIView(View):
return (renderers[0], renderers[0].media_type)
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):
"""
Check if the request should be permitted.
@@ -305,6 +315,7 @@ class APIView(View):
self.format_kwarg = self.get_format_suffix(**kwargs)
# Ensure that the incoming request is permitted
+ self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)