mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-06-17 03:53:33 +03:00
Merge pull request #249 from mjumbewu/restframework2
Implement simple token authentication
This commit is contained in:
commit
9faca0aef0
|
@ -103,4 +103,38 @@ class SessionAuthentication(BaseAuthentication):
|
||||||
return (user, None)
|
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
|
||||||
|
|
|
@ -90,6 +90,7 @@ INSTALLED_APPS = (
|
||||||
# Uncomment the next line to enable admin documentation:
|
# Uncomment the next line to enable admin documentation:
|
||||||
# 'django.contrib.admindocs',
|
# 'django.contrib.admindocs',
|
||||||
'djangorestframework',
|
'djangorestframework',
|
||||||
|
'djangorestframework.tokenauth',
|
||||||
)
|
)
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
|
@ -8,6 +8,9 @@ from django.http import HttpResponse
|
||||||
from djangorestframework.views import APIView
|
from djangorestframework.views import APIView
|
||||||
from djangorestframework import permissions
|
from djangorestframework import permissions
|
||||||
|
|
||||||
|
from djangorestframework.tokenauth.models import BasicToken
|
||||||
|
from djangorestframework.authentication import TokenAuthentication
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +23,8 @@ class MockView(APIView):
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
||||||
|
|
||||||
|
MockView.authentication += (TokenAuthentication,)
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
(r'^$', MockView.as_view()),
|
(r'^$', MockView.as_view()),
|
||||||
)
|
)
|
||||||
|
@ -104,3 +109,45 @@ class SessionAuthTests(TestCase):
|
||||||
"""
|
"""
|
||||||
response = self.csrf_client.post('/', {'example': 'example'})
|
response = self.csrf_client.post('/', {'example': 'example'})
|
||||||
self.assertEqual(response.status_code, 403)
|
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)
|
||||||
|
|
|
@ -55,27 +55,27 @@ class MockView(APIView):
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
|
||||||
return self.render(response)
|
return response
|
||||||
|
|
||||||
|
|
||||||
class MockGETView(APIView):
|
class MockGETView(APIView):
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
return {'foo': ['bar', 'baz']}
|
return Response({'foo': ['bar', 'baz']})
|
||||||
|
|
||||||
|
|
||||||
class HTMLView(APIView):
|
class HTMLView(APIView):
|
||||||
renderers = (DocumentingHTMLRenderer, )
|
renderers = (DocumentingHTMLRenderer, )
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
return 'text'
|
return Response('text')
|
||||||
|
|
||||||
|
|
||||||
class HTMLView1(APIView):
|
class HTMLView1(APIView):
|
||||||
renderers = (DocumentingHTMLRenderer, JSONRenderer)
|
renderers = (DocumentingHTMLRenderer, JSONRenderer)
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
return 'text'
|
return Response('text')
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
|
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.
|
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)
|
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):
|
class JSONPRendererTests(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests specific to the JSONP Renderer
|
Tests specific to the JSONP Renderer
|
||||||
|
|
0
djangorestframework/tokenauth/__init__.py
Normal file
0
djangorestframework/tokenauth/__init__.py
Normal file
15
djangorestframework/tokenauth/models.py
Normal file
15
djangorestframework/tokenauth/models.py
Normal 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)
|
0
djangorestframework/tokenauth/views.py
Normal file
0
djangorestframework/tokenauth/views.py
Normal 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.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
|
## 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):
|
def get(self, request, format=None):
|
||||||
content = {
|
content = {
|
||||||
'user': unicode(request.user), # `django.contrib.auth.User` instance.
|
'user': unicode(request.user), # `django.contrib.auth.User` instance.
|
||||||
'auth': unicode(request.auth), # None
|
'auth': unicode(request.auth), # None
|
||||||
}
|
}
|
||||||
return Response(content)
|
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):
|
def example_view(request, format=None):
|
||||||
content = {
|
content = {
|
||||||
'user': unicode(request.user), # `django.contrib.auth.User` instance.
|
'user': unicode(request.user), # `django.contrib.auth.User` instance.
|
||||||
'auth': unicode(request.auth), # None
|
'auth': unicode(request.auth), # None
|
||||||
}
|
}
|
||||||
return Response(content)
|
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.user` will be a `django.contrib.auth.models.User` instance.
|
||||||
* `request.auth` will be `None`.
|
* `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.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
|
## OAuthAuthentication
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user