diff options
| author | Tom Christie | 2012-09-12 20:39:22 +0100 |
|---|---|---|
| committer | Tom Christie | 2012-09-12 20:39:22 +0100 |
| commit | 003a65f0e094e59b5462fcd0607bf290d80cc5aa (patch) | |
| tree | c514790099c155e18897012cc4599a60f191832e /djangorestframework | |
| parent | dac4cb9e8bf107f407ed8754bbef0ce97e79beb2 (diff) | |
| download | django-rest-framework-003a65f0e094e59b5462fcd0607bf290d80cc5aa.tar.bz2 | |
Tweaks to Token auth
Diffstat (limited to 'djangorestframework')
| -rw-r--r-- | djangorestframework/authentication.py | 50 | ||||
| -rw-r--r-- | djangorestframework/authtoken/__init__.py | 0 | ||||
| -rw-r--r-- | djangorestframework/authtoken/migrations/0001_initial.py | 72 | ||||
| -rw-r--r-- | djangorestframework/authtoken/migrations/__init__.py | 0 | ||||
| -rw-r--r-- | djangorestframework/authtoken/models.py | 23 | ||||
| -rw-r--r-- | djangorestframework/authtoken/views.py | 0 | ||||
| -rw-r--r-- | djangorestframework/runtests/settings.py | 2 | ||||
| -rw-r--r-- | djangorestframework/tests/authentication.py | 12 | ||||
| -rw-r--r-- | djangorestframework/tokenauth/models.py | 29 |
9 files changed, 147 insertions, 41 deletions
diff --git a/djangorestframework/authentication.py b/djangorestframework/authentication.py index 2446fbbd..a6634392 100644 --- a/djangorestframework/authentication.py +++ b/djangorestframework/authentication.py @@ -6,14 +6,9 @@ Authentication behavior is provided by mixing the :class:`mixins.RequestMixin` c from django.contrib.auth import authenticate from djangorestframework.compat import CsrfViewMiddleware +from djangorestframework.authtoken.models import Token import base64 -__all__ = ( - 'BaseAuthentication', - 'BasicAuthentication', - 'SessionAuthentication' -) - class BaseAuthentication(object): """ @@ -105,36 +100,33 @@ class SessionAuthentication(BaseAuthentication): class TokenAuthentication(BaseAuthentication): """ - Use a token model for authentication. + Simple token based authentication. - A custom token model may be used here, but must have the following minimum - properties: + Clients should authenticate by passing the token key in the "Authorization" + HTTP header, prepended with the string "Token ". For example: - * 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: Token 401f7ac837da42b97f613d789819ff93537bee6a + """ - Authorization: 0123456789abcdef0123456789abcdef + model = Token + """ + A custom token model may be used, but must have the following properties. + * key -- The string identifying the token + * user -- The user to which the token belongs """ - 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 + auth = request.META.get('HTTP_AUTHORIZATION', '').split() - try: - token = self.model.objects.get(key=key) - except self.model.DoesNotExist: - return None + if len(auth) == 2 and auth[0].lower() == "token": + key = auth[1] + 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) + if token.user.is_active and not getattr(token, 'revoked', False): + return (token.user, token) -# TODO: DigestAuthentication, OAuthAuthentication +# TODO: OAuthAuthentication diff --git a/djangorestframework/authtoken/__init__.py b/djangorestframework/authtoken/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/djangorestframework/authtoken/__init__.py diff --git a/djangorestframework/authtoken/migrations/0001_initial.py b/djangorestframework/authtoken/migrations/0001_initial.py new file mode 100644 index 00000000..a91006b0 --- /dev/null +++ b/djangorestframework/authtoken/migrations/0001_initial.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Token' + db.create_table('authtoken_token', ( + ('key', self.gf('django.db.models.fields.CharField')(max_length=40, primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('revoked', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + )) + db.send_create_signal('authtoken', ['Token']) + + + def backwards(self, orm): + # Deleting model 'Token' + db.delete_table('authtoken_token') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'authtoken.token': { + 'Meta': {'object_name': 'Token'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}), + 'revoked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['authtoken']
\ No newline at end of file diff --git a/djangorestframework/authtoken/migrations/__init__.py b/djangorestframework/authtoken/migrations/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/djangorestframework/authtoken/migrations/__init__.py diff --git a/djangorestframework/authtoken/models.py b/djangorestframework/authtoken/models.py new file mode 100644 index 00000000..fd47e6c7 --- /dev/null +++ b/djangorestframework/authtoken/models.py @@ -0,0 +1,23 @@ +import uuid +import hmac +from hashlib import sha1 +from django.db import models + + +class Token(models.Model): + """ + The default authorization token model. + """ + key = models.CharField(max_length=40, primary_key=True) + user = models.ForeignKey('auth.User') + revoked = models.BooleanField(default=False) + created = models.DateTimeField(auto_now_add=True) + + def save(self, *args, **kwargs): + if not self.key: + self.key = self.generate_key() + return super(Token, self).save(*args, **kwargs) + + def generate_key(self): + unique = str(uuid.uuid4()) + return hmac.new(unique, digestmod=sha1).hexdigest() diff --git a/djangorestframework/authtoken/views.py b/djangorestframework/authtoken/views.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/djangorestframework/authtoken/views.py diff --git a/djangorestframework/runtests/settings.py b/djangorestframework/runtests/settings.py index 1fc6b47b..da2ae5b8 100644 --- a/djangorestframework/runtests/settings.py +++ b/djangorestframework/runtests/settings.py @@ -90,7 +90,7 @@ INSTALLED_APPS = ( # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'djangorestframework', - 'djangorestframework.tokenauth', + 'djangorestframework.authtoken', ) STATIC_URL = '/static/' diff --git a/djangorestframework/tests/authentication.py b/djangorestframework/tests/authentication.py index dab06b97..ddbee4b6 100644 --- a/djangorestframework/tests/authentication.py +++ b/djangorestframework/tests/authentication.py @@ -8,7 +8,7 @@ from django.http import HttpResponse from djangorestframework.views import APIView from djangorestframework import permissions -from djangorestframework.tokenauth.models import BasicToken +from djangorestframework.authtoken.models import Token from djangorestframework.authentication import TokenAuthentication import base64 @@ -123,17 +123,17 @@ class TokenAuthTests(TestCase): 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) + self.token = Token.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 + auth = "Token " + 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 + auth = "Token " + self.key response = self.csrf_client.post('/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 200) @@ -149,5 +149,5 @@ class TokenAuthTests(TestCase): 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) + token = Token.objects.create(user=self.user) + self.assertTrue(bool(token.key)) diff --git a/djangorestframework/tokenauth/models.py b/djangorestframework/tokenauth/models.py index f289b0fd..e8886112 100644 --- a/djangorestframework/tokenauth/models.py +++ b/djangorestframework/tokenauth/models.py @@ -1,15 +1,34 @@ import uuid +import hmac +from hashlib import sha1 from django.db import models -class BasicToken(models.Model): + +class TokenManager(models.Manager): + """ + Manager class to provide `Token.objects.create_token(user=user)`. + """ + def create_token(self, user): + token = Token(user=user) + token.save() + return token + + +class Token(models.Model): """ - The default authorization token model class. + The default authorization token model. """ - key = models.CharField(max_length=32, primary_key=True, blank=True) + key = models.CharField(max_length=40, primary_key=True) user = models.ForeignKey('auth.User') revoked = models.BooleanField(default=False) + created = models.DateTimeField(auto_now_add=True) + objects = TokenManager() def save(self, *args, **kwargs): if not self.key: - self.key = uuid.uuid4().hex - return super(BasicToken, self).save(*args, **kwargs) + self.key = self.generate_key() + return super(Token, self).save(*args, **kwargs) + + def generate_key(self): + unique = str(uuid.uuid4()) + return hmac.new(unique, digestmod=sha1).hexdigest() |
