Merge pull request #249 from mjumbewu/restframework2

Implement simple token authentication
This commit is contained in:
Tom Christie 2012-09-07 13:48:39 -07:00
commit 9faca0aef0
8 changed files with 115 additions and 26 deletions

View File

@ -103,4 +103,38 @@ class SessionAuthentication(BaseAuthentication):
return (user, None)
# TODO: TokenAuthentication, DigestAuthentication, OAuthAuthentication
class TokenAuthentication(BaseAuthentication):
"""
Use a token model for authentication.
A custom token model may be used here, but must have the following minimum
properties:
* key -- The string identifying the token
* user -- The user to which the token belongs
* revoked -- The status of the token
The token key should be passed in as a string to the "Authorization" HTTP
header. For example:
Authorization: 0123456789abcdef0123456789abcdef
"""
model = None
def authenticate(self, request):
key = request.META.get('HTTP_AUTHORIZATION', '').strip()
if self.model is None:
from djangorestframework.tokenauth.models import BasicToken
self.model = BasicToken
try:
token = self.model.objects.get(key=key)
except self.model.DoesNotExist:
return None
if token.user.is_active and not token.revoked:
return (token.user, token)
# TODO: DigestAuthentication, OAuthAuthentication

View File

@ -90,6 +90,7 @@ INSTALLED_APPS = (
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'djangorestframework',
'djangorestframework.tokenauth',
)
STATIC_URL = '/static/'

View File

@ -8,6 +8,9 @@ from django.http import HttpResponse
from djangorestframework.views import APIView
from djangorestframework import permissions
from djangorestframework.tokenauth.models import BasicToken
from djangorestframework.authentication import TokenAuthentication
import base64
@ -20,6 +23,8 @@ class MockView(APIView):
def put(self, request):
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
MockView.authentication += (TokenAuthentication,)
urlpatterns = patterns('',
(r'^$', MockView.as_view()),
)
@ -104,3 +109,45 @@ class SessionAuthTests(TestCase):
"""
response = self.csrf_client.post('/', {'example': 'example'})
self.assertEqual(response.status_code, 403)
class TokenAuthTests(TestCase):
"""Token authentication"""
urls = 'djangorestframework.tests.authentication'
def setUp(self):
self.csrf_client = Client(enforce_csrf_checks=True)
self.username = 'john'
self.email = 'lennon@thebeatles.com'
self.password = 'password'
self.user = User.objects.create_user(self.username, self.email, self.password)
self.key = 'abcd1234'
self.token = BasicToken.objects.create(key=self.key, user=self.user)
def test_post_form_passing_token_auth(self):
"""Ensure POSTing json over token auth with correct credentials passes and does not require CSRF"""
auth = self.key
response = self.csrf_client.post('/', {'example': 'example'}, HTTP_AUTHORIZATION=auth)
self.assertEqual(response.status_code, 200)
def test_post_json_passing_token_auth(self):
"""Ensure POSTing form over token auth with correct credentials passes and does not require CSRF"""
auth = self.key
response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth)
self.assertEqual(response.status_code, 200)
def test_post_form_failing_token_auth(self):
"""Ensure POSTing form over token auth without correct credentials fails"""
response = self.csrf_client.post('/', {'example': 'example'})
self.assertEqual(response.status_code, 403)
def test_post_json_failing_token_auth(self):
"""Ensure POSTing json over token auth without correct credentials fails"""
response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json')
self.assertEqual(response.status_code, 403)
def test_token_has_auto_assigned_key_if_none_provided(self):
"""Ensure creating a token with no key will auto-assign a key"""
token = BasicToken.objects.create(user=self.user)
self.assertEqual(len(token.key), 32)

View File

@ -55,27 +55,27 @@ class MockView(APIView):
def get(self, request, **kwargs):
response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
return self.render(response)
return response
class MockGETView(APIView):
def get(self, request, **kwargs):
return {'foo': ['bar', 'baz']}
return Response({'foo': ['bar', 'baz']})
class HTMLView(APIView):
renderers = (DocumentingHTMLRenderer, )
def get(self, request, **kwargs):
return 'text'
return Response('text')
class HTMLView1(APIView):
renderers = (DocumentingHTMLRenderer, JSONRenderer)
def get(self, request, **kwargs):
return 'text'
return Response('text')
urlpatterns = patterns('',
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
@ -88,7 +88,7 @@ urlpatterns = patterns('',
)
class RendererIntegrationTests(TestCase):
class RendererEndToEndTests(TestCase):
"""
End-to-end testing of renderers using an RendererMixin on a generic view.
"""
@ -216,18 +216,6 @@ class JSONRendererTests(TestCase):
self.assertEquals(strip_trailing_whitespace(content), _indented_repr)
class MockGETView(APIView):
def get(self, request, *args, **kwargs):
return Response({'foo': ['bar', 'baz']})
urlpatterns = patterns('',
url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
)
class JSONPRendererTests(TestCase):
"""
Tests specific to the JSONP Renderer

View File

@ -0,0 +1,15 @@
import uuid
from django.db import models
class BasicToken(models.Model):
"""
The default authorization token model class.
"""
key = models.CharField(max_length=32, primary_key=True, blank=True)
user = models.ForeignKey('auth.User')
revoked = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if not self.key:
self.key = uuid.uuid4().hex
return super(BasicToken, self).save(*args, **kwargs)

View File

View File

@ -8,7 +8,7 @@ Authentication will run the first time either the `request.user` or `request.aut
The `request.user` property will typically be set to an instance of the `contrib.auth` package's `User` class.
The `request.auth` property is used for any additional authentication information, for example, it may be used to represent an authentication token that the request was signed with.
The `request.auth` property is used for any additional authentication information, for example, it may be used to represent an authentication token that the request was signed with.
## How authentication is determined
@ -36,7 +36,7 @@ You can also set the authentication policy on a per-view basis, using the `APIVi
def get(self, request, format=None):
content = {
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'auth': unicode(request.auth), # None
}
return Response(content)
@ -49,7 +49,7 @@ Or, if you're using the `@api_view` decorator with function based views.
)
def example_view(request, format=None):
content = {
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'auth': unicode(request.auth), # None
}
return Response(content)
@ -65,16 +65,20 @@ If successfully authenticated, `UserBasicAuthentication` provides the following
* `request.user` will be a `django.contrib.auth.models.User` instance.
* `request.auth` will be `None`.
## TokenBasicAuthentication
## TokenAuthentication
This policy uses [HTTP Basic Authentication][basicauth], signed against a token key and secret. Token basic authentication is appropriate for client-server setups, such as native desktop and mobile clients.
This policy uses [HTTP Authentication][basicauth] with no authentication scheme. Token basic authentication is appropriate for client-server setups, such as native desktop and mobile clients. The token key should be passed in as a string to the "Authorization" HTTP header. For example:
**Note:** If you run `TokenBasicAuthentication` in production your API must be `https` only, or it will be completely insecure.
curl http://my.api.org/ -X POST -H "Authorization: 0123456789abcdef0123456789abcdef"
If successfully authenticated, `TokenBasicAuthentication` provides the following credentials.
**Note:** If you run `TokenAuthentication` in production your API must be `https` only, or it will be completely insecure.
If successfully authenticated, `TokenAuthentication` provides the following credentials.
* `request.user` will be a `django.contrib.auth.models.User` instance.
* `request.auth` will be a `djangorestframework.models.BasicToken` instance.
* `request.auth` will be a `djangorestframework.tokenauth.models.BasicToken` instance.
To use the `TokenAuthentication` policy, you must have a token model. Django REST Framework comes with a minimal default token model. To use it, include `djangorestframework.tokenauth` in your installed applications and sync your database. To use your own token model, subclass the `djangorestframework.tokenauth.TokenAuthentication` class and specify a `model` attribute that references your custom token model. The token model must provide `user`, `key`, and `revoked` attributes. Refer to the `djangorestframework.tokenauth.models.BasicToken` model as an example.
## OAuthAuthentication