From b75c2bd61f4a7c37412fe831598fd7d1c924fb31 Mon Sep 17 00:00:00 2001 From: James Meakin <12661555+jmsmkn@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:27:45 +0200 Subject: [PATCH] Ensure old tokens continue to work --- .gitignore | 1 + knox/auth.py | 17 +++++++++--- .../0010_alter_authtoken_token_key.py | 18 +++++++++++++ knox/models.py | 5 +--- tests/tests.py | 27 +++++++++++++++++++ tox.ini | 2 +- 6 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 knox/migrations/0010_alter_authtoken_token_key.py diff --git a/.gitignore b/.gitignore index 7791a82c..eeecd427 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Distribution / packaging .Python env/ +.venv/ build/ develop-eggs/ dist/ diff --git a/knox/auth.py b/knox/auth.py index 858ee504..39815f6d 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -56,15 +56,24 @@ def authenticate_credentials(self, token): ''' msg = _('Invalid token.') token = token.decode("utf-8") + + try: + digest = hash_token(token) + except (TypeError, binascii.Error): + raise exceptions.AuthenticationFailed(msg) + + for auth_token in get_token_model().objects.filter(token_key=token[:8]): + # Migrate tokens that were created prior to 3a1bc58 + # TODO: This will have terrible performance if TOKEN_PREFIX is used + if compare_digest(digest, auth_token.digest): + auth_token.token_key = token[:CONSTANTS.TOKEN_KEY_LENGTH] + auth_token.save() + for auth_token in get_token_model().objects.filter( token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH]): if self._cleanup_token(auth_token): continue - try: - digest = hash_token(token) - except (TypeError, binascii.Error): - raise exceptions.AuthenticationFailed(msg) if compare_digest(digest, auth_token.digest): if knox_settings.AUTO_REFRESH and auth_token.expiry: self.renew_token(auth_token) diff --git a/knox/migrations/0010_alter_authtoken_token_key.py b/knox/migrations/0010_alter_authtoken_token_key.py new file mode 100644 index 00000000..c07b5ad8 --- /dev/null +++ b/knox/migrations/0010_alter_authtoken_token_key.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.7 on 2024-08-02 08:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('knox', '0009_extend_authtoken_field'), + ] + + operations = [ + migrations.AlterField( + model_name='authtoken', + name='token_key', + field=models.CharField(db_index=True, max_length=15), + ), + ] diff --git a/knox/models.py b/knox/models.py index 8b0a179d..a9b24eae 100644 --- a/knox/models.py +++ b/knox/models.py @@ -7,8 +7,6 @@ from knox import crypto from knox.settings import CONSTANTS, knox_settings -sha = knox_settings.SECURE_HASH_ALGORITHM - User = settings.AUTH_USER_MODEL @@ -37,8 +35,7 @@ class AbstractAuthToken(models.Model): digest = models.CharField( max_length=CONSTANTS.DIGEST_LENGTH, primary_key=True) token_key = models.CharField( - max_length=CONSTANTS.MAXIMUM_TOKEN_PREFIX_LENGTH + - CONSTANTS.TOKEN_KEY_LENGTH, + max_length=CONSTANTS.TOKEN_KEY_LENGTH, db_index=True ) user = models.ForeignKey(User, null=False, blank=False, diff --git a/tests/tests.py b/tests/tests.py index 9494db0b..ab744595 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -496,3 +496,30 @@ def test_tokens_created_before_prefix_still_work(self): response = self.client.get(root_url, {}, format='json') self.assertEqual(response.status_code, 200) reload(views) + + def test_old_tokens_still_work(self): + self.assertEqual(AuthToken.objects.count(), 0) + + old_token = "02d233c901e7bd38df1dbc486b7e22c5c81b089c40cbb31d35d7b032615f5778" + # Hash generated using crypto.hash_token on 4.2.0 with + # SECURE_HASH_ALGORITHM = 'cryptography.hazmat.primitives.hashes.SHA512' + old_hash = ( + "c7f9f2904decf77e0fa0341bc3eb96daa1437649825f4bfdd38cdad64d69c4be55938d71f17" + "34131c656f9bbbfc5d991bef295accd268921b23d9cdd0d9d60d0" + ) + + AuthToken( + token_key=old_token[: 8], # 8 was the key length prior to 3a1bc58 + digest=old_hash, + user=self.user, + ).save() + + rf = APIRequestFactory() + request = rf.get('/') + request.META = {'HTTP_AUTHORIZATION': f'Token {old_token}'} + user, auth_token = TokenAuthentication().authenticate(request) + self.assertEqual(self.user, user) + self.assertEqual( + old_token[:CONSTANTS.TOKEN_KEY_LENGTH], + auth_token.token_key, + ) diff --git a/tox.ini b/tox.ini index 5b17cb80..c49f2e81 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = [testenv] commands = python manage.py migrate - coverage run manage.py test + coverage run manage.py test {posargs} coverage report setenv = DJANGO_SETTINGS_MODULE = knox_project.settings