aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2012-11-19 13:30:49 -0800
committerTom Christie2012-11-19 13:30:49 -0800
commitb9e5c9484ae34b6675ea940854d58e2561bcc7f9 (patch)
tree8de7b921b7bc8b7cf9275cf07c8f26c3bf276d14
parent25f024575bc36ea6ed386160a31b70abb5ac3e6e (diff)
parentf5f1ac49ec9d9cb180a00c791f2701c3f5a2d65b (diff)
downloaddjango-rest-framework-b9e5c9484ae34b6675ea940854d58e2561bcc7f9.tar.bz2
Merge pull request #399 from robromano/master
Added login view for users of TokenAuthentication
-rw-r--r--docs/api-guide/authentication.md12
-rw-r--r--docs/topics/credits.md3
-rw-r--r--docs/topics/release-notes.md2
-rw-r--r--rest_framework/authtoken/serializers.py24
-rw-r--r--rest_framework/authtoken/views.py24
-rw-r--r--rest_framework/tests/authentication.py33
6 files changed, 97 insertions, 1 deletions
diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
index cb1e2645..a30bd22c 100644
--- a/docs/api-guide/authentication.md
+++ b/docs/api-guide/authentication.md
@@ -112,6 +112,18 @@ If you've already created some User`'s, you can run a script like this.
for user in User.objects.all():
Token.objects.get_or_create(user=user)
+When using TokenAuthentication, it may be useful to add a login view for clients to retrieve the token.
+
+REST framework provides a built-in login view for clients to retrieve the token called `rest_framework.authtoken.obtain_auth_token`. To use it, add a pattern to include the token login view for clients as follows:
+
+ urlpatterns += patterns('',
+ url(r'^api-token-auth/', 'rest_framework.authtoken.obtain_auth_token')
+ )
+
+The `r'^api-token-auth/'` part of pattern can actually be whatever URL you want to use. The authtoken login view will render a JSON response when a valid `username` and `password` fields are POST'ed to the view using forms or JSON:
+
+ { 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }
+
## OAuthAuthentication
This policy uses the [OAuth 2.0][oauth] protocol to authenticate requests. OAuth is appropriate for server-server setups, such as when you want to allow a third-party service to access your API on a user's behalf.
diff --git a/docs/topics/credits.md b/docs/topics/credits.md
index f037e816..34590109 100644
--- a/docs/topics/credits.md
+++ b/docs/topics/credits.md
@@ -60,6 +60,7 @@ The following people have helped make REST framework great.
* Ben Konrath - [benkonrath]
* Marc Aymerich - [glic3rinu]
* Ludwig Kraatz - [ludwigkraatz]
+* Rob Romano - [robromano]
* Eugene Mechanism - [mechanism]
Many thanks to everyone who's contributed to the project.
@@ -156,4 +157,6 @@ To contact the author directly:
[benkonrath]: https://github.com/benkonrath
[glic3rinu]: https://github.com/glic3rinu
[ludwigkraatz]: https://github.com/ludwigkraatz
+[robromano]: https://github.com/robromano
[mechanism]: https://github.com/mechanism
+
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 2a6fbe83..d43f892f 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -4,6 +4,8 @@
>
> — Eric S. Raymond, [The Cathedral and the Bazaar][cite].
+* Add convenience login view to get tokens when using `TokenAuthentication`
+
## 2.1.3
**Date**: 16th Nov 2012
diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py
new file mode 100644
index 00000000..a5ed6e6d
--- /dev/null
+++ b/rest_framework/authtoken/serializers.py
@@ -0,0 +1,24 @@
+from django.contrib.auth import authenticate
+from rest_framework import serializers
+
+class AuthTokenSerializer(serializers.Serializer):
+ username = serializers.CharField()
+ password = serializers.CharField()
+
+ def validate(self, attrs):
+ username = attrs.get('username')
+ password = attrs.get('password')
+
+ if username and password:
+ user = authenticate(username=username, password=password)
+
+ if user:
+ if not user.is_active:
+ raise serializers.ValidationError('User account is disabled.')
+ attrs['user'] = user
+ return attrs
+ else:
+ raise serializers.ValidationError('Unable to login with provided credentials.')
+ else:
+ raise serializers.ValidationError('Must include "username" and "password"')
+
diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py
index e69de29b..3ac674e2 100644
--- a/rest_framework/authtoken/views.py
+++ b/rest_framework/authtoken/views.py
@@ -0,0 +1,24 @@
+from rest_framework.views import APIView
+from rest_framework import status
+from rest_framework import parsers
+from rest_framework import renderers
+from rest_framework.response import Response
+from rest_framework.authtoken.models import Token
+from rest_framework.authtoken.serializers import AuthTokenSerializer
+
+class ObtainAuthToken(APIView):
+ throttle_classes = ()
+ permission_classes = ()
+ parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
+ renderer_classes = (renderers.JSONRenderer,)
+ model = Token
+
+ def post(self, request):
+ serializer = AuthTokenSerializer(data=request.DATA)
+ if serializer.is_valid():
+ token, created = Token.objects.get_or_create(user=serializer.object['user'])
+ return Response({'token': token.key})
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+obtain_auth_token = ObtainAuthToken.as_view()
diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py
index 8ab4c4e4..96ca9f52 100644
--- a/rest_framework/tests/authentication.py
+++ b/rest_framework/tests/authentication.py
@@ -1,4 +1,4 @@
-from django.conf.urls.defaults import patterns
+from django.conf.urls.defaults import patterns, include
from django.contrib.auth.models import User
from django.test import Client, TestCase
@@ -27,6 +27,7 @@ MockView.authentication_classes += (TokenAuthentication,)
urlpatterns = patterns('',
(r'^$', MockView.as_view()),
+ (r'^auth-token/', 'rest_framework.authtoken.views.obtain_auth_token'),
)
@@ -152,3 +153,33 @@ class TokenAuthTests(TestCase):
self.token.delete()
token = Token.objects.create(user=self.user)
self.assertTrue(bool(token.key))
+
+ def test_token_login_json(self):
+ """Ensure token login view using JSON POST works."""
+ client = Client(enforce_csrf_checks=True)
+ response = client.post('/auth-token/login/',
+ json.dumps({'username': self.username, 'password': self.password}), 'application/json')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(json.loads(response.content)['token'], self.key)
+
+ def test_token_login_json_bad_creds(self):
+ """Ensure token login view using JSON POST fails if bad credentials are used."""
+ client = Client(enforce_csrf_checks=True)
+ response = client.post('/auth-token/login/',
+ json.dumps({'username': self.username, 'password': "badpass"}), 'application/json')
+ self.assertEqual(response.status_code, 400)
+
+ def test_token_login_json_missing_fields(self):
+ """Ensure token login view using JSON POST fails if missing fields."""
+ client = Client(enforce_csrf_checks=True)
+ response = client.post('/auth-token/login/',
+ json.dumps({'username': self.username}), 'application/json')
+ self.assertEqual(response.status_code, 400)
+
+ def test_token_login_form(self):
+ """Ensure token login view using form POST works."""
+ client = Client(enforce_csrf_checks=True)
+ response = client.post('/auth-token/login/',
+ {'username': self.username, 'password': self.password})
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(json.loads(response.content)['token'], self.key)