Merge branch 'master' into oauth2-authentication

Conflicts:
	rest_framework/tests/authentication.py
This commit is contained in:
Pierre Dulac 2013-03-01 11:50:11 +01:00
commit aed3c13471
5 changed files with 73 additions and 27 deletions

View File

@ -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.
@ -259,7 +259,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.

View File

@ -42,6 +42,7 @@ You can determine your currently installed version using `pip freeze`:
### Master ### Master
* Request authentication is no longer lazily evaluated, instead authentication is always run, which results in more consistent, obvious behavior. Eg. Supplying bad auth credentials will now always return an error response, even if no permissions are set on the view.
* Bugfix for serializer data being uncacheable with pickle protocol 0. * Bugfix for serializer data being uncacheable with pickle protocol 0.
* Bugfixes for model field validation edge-cases. * Bugfixes for model field validation edge-cases.

View File

@ -418,6 +418,27 @@ class ModelSerializer(Serializer):
""" """
_options_class = ModelSerializerOptions _options_class = ModelSerializerOptions
field_mapping = {
models.AutoField: IntegerField,
models.FloatField: FloatField,
models.IntegerField: IntegerField,
models.PositiveIntegerField: IntegerField,
models.SmallIntegerField: IntegerField,
models.PositiveSmallIntegerField: IntegerField,
models.DateTimeField: DateTimeField,
models.DateField: DateField,
models.TimeField: TimeField,
models.EmailField: EmailField,
models.CharField: CharField,
models.URLField: URLField,
models.SlugField: SlugField,
models.TextField: CharField,
models.CommaSeparatedIntegerField: CharField,
models.BooleanField: BooleanField,
models.FileField: FileField,
models.ImageField: ImageField,
}
def get_default_fields(self): def get_default_fields(self):
""" """
Return all the fields that should be serialized for the model. Return all the fields that should be serialized for the model.
@ -515,28 +536,8 @@ class ModelSerializer(Serializer):
kwargs['choices'] = model_field.flatchoices kwargs['choices'] = model_field.flatchoices
return ChoiceField(**kwargs) return ChoiceField(**kwargs)
field_mapping = {
models.AutoField: IntegerField,
models.FloatField: FloatField,
models.IntegerField: IntegerField,
models.PositiveIntegerField: IntegerField,
models.SmallIntegerField: IntegerField,
models.PositiveSmallIntegerField: IntegerField,
models.DateTimeField: DateTimeField,
models.DateField: DateField,
models.TimeField: TimeField,
models.EmailField: EmailField,
models.CharField: CharField,
models.URLField: URLField,
models.SlugField: SlugField,
models.TextField: CharField,
models.CommaSeparatedIntegerField: CharField,
models.BooleanField: BooleanField,
models.FileField: FileField,
models.ImageField: ImageField,
}
try: try:
return field_mapping[model_field.__class__](**kwargs) return self.field_mapping[model_field.__class__](**kwargs)
except KeyError: except KeyError:
return ModelField(model_field=model_field, **kwargs) return ModelField(model_field=model_field, **kwargs)

View File

@ -4,13 +4,21 @@ 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, OAuth2Authentication from rest_framework.authentication import (
BaseAuthentication,
TokenAuthentication,
BasicAuthentication,
SessionAuthentication,
OAuth2Authentication
)
from rest_framework.compat import patterns, url, include from rest_framework.compat import patterns, url, include
from rest_framework.compat import oauth2 from rest_framework.compat import oauth2
from rest_framework.compat import oauth2_provider from rest_framework.compat import oauth2_provider
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
@ -18,17 +26,21 @@ import datetime
import unittest import unittest
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})
def get(self, request):
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])),
@ -199,6 +211,27 @@ class TokenAuthTests(TestCase):
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'})
class OAuth2Tests(TestCase): class OAuth2Tests(TestCase):
"""OAuth 2.0 authentication""" """OAuth 2.0 authentication"""
urls = 'rest_framework.tests.authentication' urls = 'rest_framework.tests.authentication'

View File

@ -8,7 +8,7 @@ from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from rest_framework import status, exceptions from rest_framework import status, exceptions
from rest_framework.compat import View, apply_markdown, smart_text from rest_framework.compat import View, apply_markdown
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
@ -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)