Skip to content

Commit

Permalink
Merge branch 'master' into oauth2-authentication
Browse files Browse the repository at this point in the history
Conflicts:
	rest_framework/tests/authentication.py
  • Loading branch information
dulacp committed Mar 1, 2013
2 parents d8f455b + 282af60 commit aed3c13
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 27 deletions.
4 changes: 2 additions & 2 deletions docs/api-guide/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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:

* 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.

Expand Down
1 change: 1 addition & 0 deletions docs/topics/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ You can determine your currently installed version using `pip freeze`:

### 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.
* Bugfixes for model field validation edge-cases.

Expand Down
43 changes: 22 additions & 21 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,27 @@ class ModelSerializer(Serializer):
"""
_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):
"""
Return all the fields that should be serialized for the model.
Expand Down Expand Up @@ -515,28 +536,8 @@ def get_field(self, model_field):
kwargs['choices'] = model_field.flatchoices
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:
return field_mapping[model_field.__class__](**kwargs)
return self.field_mapping[model_field.__class__](**kwargs)
except KeyError:
return ModelField(model_field=model_field, **kwargs)

Expand Down
39 changes: 36 additions & 3 deletions rest_framework/tests/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,43 @@
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, OAuth2Authentication
from rest_framework.authentication import (
BaseAuthentication,
TokenAuthentication,
BasicAuthentication,
SessionAuthentication,
OAuth2Authentication
)
from rest_framework.compat import patterns, url, include
from rest_framework.compat import oauth2
from rest_framework.compat import oauth2_provider
from rest_framework.tests.utils import RequestFactory
from rest_framework.views import APIView
import json
import base64
import datetime
import unittest


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})

def get(self, request):
return HttpResponse({'a': 1, 'b': 2, 'c': 3})

urlpatterns = patterns('',
(r'^session/$', MockView.as_view(authentication_classes=[SessionAuthentication])),
Expand Down Expand Up @@ -199,6 +211,27 @@ def test_token_login_form(self):
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):
"""OAuth 2.0 authentication"""
urls = 'rest_framework.tests.authentication'
Expand Down
13 changes: 12 additions & 1 deletion rest_framework/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_exempt
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.request import Request
from rest_framework.settings import api_settings
Expand Down Expand Up @@ -257,6 +257,16 @@ def perform_content_negotiation(self, request, force=False):
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.
Expand Down Expand Up @@ -305,6 +315,7 @@ def initial(self, request, *args, **kwargs):
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)

Expand Down

0 comments on commit aed3c13

Please sign in to comment.