mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 05:04:31 +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)
|
||||
|
||||
|
||||
# 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:
|
||||
# 'django.contrib.admindocs',
|
||||
'djangorestframework',
|
||||
'djangorestframework.tokenauth',
|
||||
)
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
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.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
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user