From 4e090cada913cf4cb7219ff23f133426aa2f5f68 Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Mon, 31 Jan 2022 16:28:11 +0100 Subject: [PATCH 01/41] Fix: library also compatible with Django 4.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96a31be7..b6392694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## 4.2.0 -- compatibility with Python up to 3.10 and Django up to 3.2 +- compatibility with Python up to 3.10 and Django up to 4.0 - integration with github CI instead of travis - Migration: "salt" field of model "AuthToken" is removed From 11c10964193392b4c8e565aeae7ba32614c0bde2 Mon Sep 17 00:00:00 2001 From: Ilya LUBASKIN / 14 years old <100635212+lubaskinc0de@users.noreply.github.com> Date: Mon, 18 Jul 2022 11:31:03 +0300 Subject: [PATCH 02/41] fix migrate zero fix django.db.migrations.exceptions.IrreversibleError: Operation > in knox.0006_auto_20160818_0932 is not reversible error when python manage,py migrate knox zero on django 4.0.4 --- knox/migrations/0006_auto_20160818_0932.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knox/migrations/0006_auto_20160818_0932.py b/knox/migrations/0006_auto_20160818_0932.py index b8540905..8c1c7412 100644 --- a/knox/migrations/0006_auto_20160818_0932.py +++ b/knox/migrations/0006_auto_20160818_0932.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(cleanup_tokens), + migrations.RunPython(cleanup_tokens, reverse_code=migrations.RunPython.noop), migrations.AlterField( model_name='authtoken', name='token_key', From 04c15fdc4da0c46510e37d4ba790c02e84c1e733 Mon Sep 17 00:00:00 2001 From: Taiwo ADEGITE Date: Tue, 26 Jul 2022 02:27:34 +0100 Subject: [PATCH 03/41] Updated README.md Changes made were done to make the README.md easy to read. --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 203db1f1..5303435e 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ django-rest-knox Authentication Module for django rest auth -Knox provides easy to use authentication for [Django REST +Knox provides easy-to-use authentication for [Django REST Framework](https://www.django-rest-framework.org/) The aim is to allow -for common patterns in applications that are REST based, with little +for common patterns in applications that are REST-based, with little extra effort; and to ensure that connections remain secure. -Knox authentication is token based, similar to the `TokenAuthentication` -built in to DRF. However, it overcomes some problems present in the +Knox authentication is token-based, similar to the `TokenAuthentication` +built into DRF. However, it overcomes some problems present in the default implementation: - DRF tokens are limited to one per user. This does not facilitate @@ -23,13 +23,14 @@ default implementation: client to have its own token which is deleted on the server side when the client logs out. - Knox also provides an option for a logged in client to remove *all* + Knox also provides an option for a logged-in client to remove *all* tokens that the server has - forcing all clients to re-authenticate. - DRF tokens are stored unencrypted in the database. This would allow - an attacker unrestricted access to an account with a token if the + an attacker unrestricted access to an account with a token if the database were compromised. + Knox tokens are only stored in a secure hash form (like a password). Even if the database were somehow stolen, an attacker would not be able to log in with the stolen credentials. @@ -55,7 +56,7 @@ Python / Django versions a bit more tricky. Our documentation is generated by [Mkdocs](https://www.mkdocs.org). -You can refer to their documentation on how to install it locally. +You can refer to their [documentation](https://www.mkdocs.org/user-guide/installation/) on how to install it locally. Another option is to use `mkdocs.sh` in this repository. It will run mkdocs in a [docker](https://www.docker.com/) container. From 1796ad834969785a1a315e44bdbd2fd0e81bd355 Mon Sep 17 00:00:00 2001 From: Taiwo ADEGITE Date: Tue, 26 Jul 2022 03:02:00 +0100 Subject: [PATCH 04/41] Undated index.md --- docs/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 1619797b..4a674fed 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,10 +1,10 @@ # Django-Rest-Knox -Knox provides easy to use authentication for [Django REST Framework](https://www.django-rest-framework.org/) +Knox provides easy-to-use authentication for [Django REST Framework](https://www.django-rest-framework.org/) The aim is to allow for common patterns in applications that are REST based, with little extra effort; and to ensure that connections remain secure. Knox authentication is token based, similar to the `TokenAuthentication` built -in to DRF. However, it overcomes some problems present in the default implementation: +into DRF. However, it overcomes some problems present in the default implementation: - DRF tokens are limited to one per user. This does not facilitate securely signing in from multiple devices, as the token is shared. It also requires From 1f611444aacbe4c41c205a1fe5c42d47e1442cbb Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Mon, 15 Aug 2022 12:04:42 +0200 Subject: [PATCH 05/41] docu: clearify behavior of TOKEN_LIMIT_PER_USER (#228) --- docs/settings.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/settings.md b/docs/settings.md index e44e322b..0de1ea87 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -58,7 +58,8 @@ Warning: setting a 0 or negative timedelta will create tokens that instantly exp the system will not prevent you setting this. ## TOKEN_LIMIT_PER_USER -This allows you to control how many tokens can be issued per user. +This allows you to control how many valid tokens can be issued per user. +If the limit for valid tokens is reached, an error is returned at login. By default this option is disabled and set to `None` -- thus no limit. ## USER_SERIALIZER From 78fe0c6cdfe6c9eb4c2fddddd215e99be90b3407 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Mon, 15 Aug 2022 12:06:03 +0200 Subject: [PATCH 06/41] use hashlib instead of cryptography (#230) --- docs/installation.md | 25 +++---------------------- docs/settings.md | 9 ++++----- knox/crypto.py | 21 ++++++++------------- knox/settings.py | 2 +- setup.py | 1 - tox.ini | 1 - 6 files changed, 16 insertions(+), 43 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 43945b44..afaeb559 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,27 +2,8 @@ ## Requirements -Knox depends on `cryptography` to provide bindings to `OpenSSL` for token generation -This requires the OpenSSL build libraries to be available. - -### Windows -Cryptography is a statically linked build, no extra steps are needed. - -### Linux -`cryptography` should build very easily on Linux provided you have a C compiler, -headers for Python (if you’re not using `pypy`), and headers for the OpenSSL and -`libffi` libraries available on your system. - -Debian and Ubuntu: -```bash -sudo apt-get install build-essential libssl-dev libffi-dev python3-dev python-dev -``` - -Fedora and RHEL-derivatives: -```bash -sudo yum install gcc libffi-devel python-devel openssl-devel -``` -For other systems or problems, see the [cryptography installation docs](https://cryptography.io/en/latest/installation/) +Knox depends on pythons internal library `hashlib` to provide bindings to `OpenSSL` or uses +an internal implementation of hashing algorithms for token generation. ## Installing Knox Knox should be installed with pip @@ -59,7 +40,7 @@ REST_FRAMEWORK = { - If you set TokenAuthentication as the only default authentication class on the second step, [override knox's LoginView](auth.md#global-usage-on-all-views) to accept another authentication method and use it instead of knox's default login view. -- Apply the migrations for the models +- Apply the migrations for the models. ```bash python manage.py migrate diff --git a/docs/settings.md b/docs/settings.md index 0de1ea87..fd33f16f 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -11,7 +11,7 @@ Example `settings.py` from datetime import timedelta from rest_framework.settings import api_settings REST_KNOX = { - 'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512', + 'SECURE_HASH_ALGORITHM': 'hashlib.sha512', 'AUTH_TOKEN_CHARACTER_LENGTH': 64, 'TOKEN_TTL': timedelta(hours=10), 'USER_SERIALIZER': 'knox.serializers.UserSerializer', @@ -30,14 +30,13 @@ token storage. By default, Knox uses SHA-512 to hash tokens in the database. -`cryptography.hazmat.primitives.hashes.Whirlpool` is an acceptable alternative setting -for production use. +`hashlib.sha3_512` is an acceptable alternative setting for production use. ### Tests -SHA-512 and Whirlpool are secure, however, they are slow. This should not be a +SHA-512 and SHA3-512 are secure, however, they are slow. This should not be a problem for your users, but when testing it may be noticeable (as test cases tend to use many more requests much more quickly than real users). In testing scenarios -it is acceptable to use `MD5` hashing.(`cryptography.hazmat.primitives.hashes.MD5`) +it is acceptable to use `MD5` hashing (`hashlib.md5`). MD5 is **not secure** and must *never* be used in production sites. diff --git a/knox/crypto.py b/knox/crypto.py index dba4f754..3ce8811e 100644 --- a/knox/crypto.py +++ b/knox/crypto.py @@ -1,12 +1,9 @@ import binascii from os import urandom as generate_bytes -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes - from knox.settings import knox_settings -sha = knox_settings.SECURE_HASH_ALGORITHM +hash_func = knox_settings.SECURE_HASH_ALGORITHM def create_token_string(): @@ -15,14 +12,12 @@ def create_token_string(): ).decode() -def hash_token(token): - ''' +def hash_token(token: str) -> str: + """ Calculates the hash of a token. - input is unhexlified - - token must contain an even number of hex digits or a binascii.Error - exception will be raised - ''' - digest = hashes.Hash(sha(), backend=default_backend()) + Token must contain an even number of hex digits or + a binascii.Error exception will be raised. + """ + digest = hash_func() digest.update(binascii.unhexlify(token)) - return binascii.hexlify(digest.finalize()).decode() + return digest.hexdigest() diff --git a/knox/settings.py b/knox/settings.py index 5bb14483..5197d029 100644 --- a/knox/settings.py +++ b/knox/settings.py @@ -7,7 +7,7 @@ USER_SETTINGS = getattr(settings, 'REST_KNOX', None) DEFAULTS = { - 'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512', + 'SECURE_HASH_ALGORITHM': 'hashlib.sha512', 'AUTH_TOKEN_CHARACTER_LENGTH': 64, 'TOKEN_TTL': timedelta(hours=10), 'USER_SERIALIZER': None, diff --git a/setup.py b/setup.py index 30d63bdb..a75d3689 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,6 @@ install_requires=[ 'django>=3.2', 'djangorestframework', - 'cryptography', ], # List additional groups of dependencies here (e.g. development diff --git a/tox.ini b/tox.ini index c6610d86..6ee441da 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,6 @@ deps = djangorestframework freezegun mkdocs - cryptography pytest-django setuptools twine From dc870df9ef5a5a41e796371e85fe920622b9cf56 Mon Sep 17 00:00:00 2001 From: Khalid Muhammad <62033170+Khalidm98@users.noreply.github.com> Date: Tue, 16 Aug 2022 19:14:39 +0200 Subject: [PATCH 07/41] Allowed usage of custom AuthToken based on knox.AbstractAuthToken (#275) * Allowed usage of custom AuthToken based on knox.AbstractAuthToken * moved get_token_model() to models, updated the docs Co-authored-by: Khalidm98 --- .gitignore | 3 +++ docs/settings.md | 14 ++++++++++++++ docs/views.md | 1 + knox/auth.py | 5 ++--- knox/migrations/0001_initial.py | 1 + knox/models.py | 34 ++++++++++++++++++++++++++++++--- knox/settings.py | 1 + knox/views.py | 10 +++++++--- knox_project/settings.py | 2 ++ 9 files changed, 62 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 6e5bf840..7791a82c 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ docs/_build/ target/ db.sqlite3 site/ + +# PyCharm Project +.idea diff --git a/docs/settings.md b/docs/settings.md index fd33f16f..5f930900 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -10,6 +10,9 @@ Example `settings.py` # These are the default values if none are set from datetime import timedelta from rest_framework.settings import api_settings + +KNOX_TOKEN_MODEL = 'knox.AuthToken' + REST_KNOX = { 'SECURE_HASH_ALGORITHM': 'hashlib.sha512', 'AUTH_TOKEN_CHARACTER_LENGTH': 64, @@ -17,11 +20,17 @@ REST_KNOX = { 'USER_SERIALIZER': 'knox.serializers.UserSerializer', 'TOKEN_LIMIT_PER_USER': None, 'AUTO_REFRESH': False, + 'MIN_REFRESH_INTERVAL': 60, + 'AUTH_HEADER_PREFIX': 'Token', 'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT, + 'TOKEN_MODEL': 'knox.AuthToken', } #...snip... ``` +## KNOX_TOKEN_MODEL +This is the variable used in the swappable dependency of the `AuthToken` model + ## SECURE_HASH_ALGORITHM This is a reference to the class used to provide the hashing algorithm for token storage. @@ -81,6 +90,11 @@ This is the expiry datetime format returned in the login view. The default is th [DATETIME_FORMAT][DATETIME_FORMAT] of Django REST framework. May be any of `None`, `iso-8601` or a Python [strftime format][strftime format] string. +## TOKEN_MODEL +This is the reference to the model used as `AuthToken`. We can define a custom `AuthToken` +model in our project that extends `knox.AbstractAuthToken` and add our business logic to it. +The default is `knox.AuthToken` + [DATETIME_FORMAT]: https://www.django-rest-framework.org/api-guide/settings/#date-and-time-formatting [strftime format]: https://docs.python.org/3/library/time.html#time.strftime diff --git a/docs/views.md b/docs/views.md index c8ed762b..074b16a7 100644 --- a/docs/views.md +++ b/docs/views.md @@ -21,6 +21,7 @@ helper methods: - `get_user_serializer_class(self)`, to change the class used for serializing the user - `get_expiry_datetime_format(self)`, to change the datetime format used for expiry - `format_expiry_datetime(self, expiry)`, to format the expiry `datetime` object at your convenience +- `create_token(self)`, to create the `AuthToken` instance at your convenience Finally, if none of these helper methods are sufficient, you can also override `get_post_response_data` to return a fully customized payload. diff --git a/knox/auth.py b/knox/auth.py index c013c4dd..a7b5f873 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -14,7 +14,7 @@ def compare_digest(a, b): ) from knox.crypto import hash_token -from knox.models import AuthToken +from knox.models import get_token_model from knox.settings import CONSTANTS, knox_settings from knox.signals import token_expired @@ -31,7 +31,6 @@ class TokenAuthentication(BaseAuthentication): - `request.user` will be a django `User` instance - `request.auth` will be an `AuthToken` instance ''' - model = AuthToken def authenticate(self, request): auth = get_authorization_header(request).split() @@ -62,7 +61,7 @@ def authenticate_credentials(self, token): ''' msg = _('Invalid token.') token = token.decode("utf-8") - for auth_token in AuthToken.objects.filter( + for auth_token in get_token_model().objects.filter( token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH]): if self._cleanup_token(auth_token): continue diff --git a/knox/migrations/0001_initial.py b/knox/migrations/0001_initial.py index e2b157ec..822176ea 100644 --- a/knox/migrations/0001_initial.py +++ b/knox/migrations/0001_initial.py @@ -9,6 +9,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), + migrations.swappable_dependency(settings.KNOX_TOKEN_MODEL), ] operations = [ diff --git a/knox/models.py b/knox/models.py index 4dbee1ae..4d316650 100644 --- a/knox/models.py +++ b/knox/models.py @@ -1,4 +1,6 @@ +from django.apps import apps from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.db import models from django.utils import timezone @@ -9,7 +11,7 @@ class AuthTokenManager(models.Manager): - def create(self, user, expiry=knox_settings.TOKEN_TTL): + def create(self, user, expiry=knox_settings.TOKEN_TTL, **kwargs): token = crypto.create_token_string() digest = crypto.hash_token(token) @@ -18,11 +20,11 @@ def create(self, user, expiry=knox_settings.TOKEN_TTL): instance = super(AuthTokenManager, self).create( token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH], digest=digest, - user=user, expiry=expiry) + user=user, expiry=expiry, **kwargs) return instance, token -class AuthToken(models.Model): +class AbstractAuthToken(models.Model): objects = AuthTokenManager() @@ -35,5 +37,31 @@ class AuthToken(models.Model): created = models.DateTimeField(auto_now_add=True) expiry = models.DateTimeField(null=True, blank=True) + class Meta: + abstract = True + def __str__(self): return '%s : %s' % (self.digest, self.user) + + +class AuthToken(AbstractAuthToken): + class Meta: + swappable = 'KNOX_TOKEN_MODEL' + + +def get_token_model(): + """ + Return the AuthToken model that is active in this project. + """ + + try: + return apps.get_model(knox_settings.TOKEN_MODEL, require_ready=False) + except ValueError: + raise ImproperlyConfigured( + "TOKEN_MODEL must be of the form 'app_label.model_name'" + ) + except LookupError: + raise ImproperlyConfigured( + "TOKEN_MODEL refers to model '%s' that has not been installed" + % knox_settings.TOKEN_MODEL + ) diff --git a/knox/settings.py b/knox/settings.py index 5197d029..b86e95bb 100644 --- a/knox/settings.py +++ b/knox/settings.py @@ -16,6 +16,7 @@ 'MIN_REFRESH_INTERVAL': 60, 'AUTH_HEADER_PREFIX': 'Token', 'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT, + 'TOKEN_MODEL': getattr(settings, 'KNOX_TOKEN_MODEL', 'knox.AuthToken'), } IMPORT_STRINGS = { diff --git a/knox/views.py b/knox/views.py index 7975bbeb..7b3d4920 100644 --- a/knox/views.py +++ b/knox/views.py @@ -8,7 +8,7 @@ from rest_framework.views import APIView from knox.auth import TokenAuthentication -from knox.models import AuthToken +from knox.models import get_token_model from knox.settings import knox_settings @@ -35,6 +35,11 @@ def format_expiry_datetime(self, expiry): datetime_format = self.get_expiry_datetime_format() return DateTimeField(format=datetime_format).to_representation(expiry) + def create_token(self): + return get_token_model().objects.create( + user=self.request.user, expiry=self.get_token_ttl() + ) + def get_post_response_data(self, request, token, instance): UserSerializer = self.get_user_serializer_class() @@ -59,8 +64,7 @@ def post(self, request, format=None): {"error": "Maximum amount of tokens allowed per user exceeded."}, status=status.HTTP_403_FORBIDDEN ) - token_ttl = self.get_token_ttl() - instance, token = AuthToken.objects.create(request.user, token_ttl) + instance, token = self.create_token() user_logged_in.send(sender=request.user.__class__, request=request, user=request.user) data = self.get_post_response_data(request, token, instance) diff --git a/knox_project/settings.py b/knox_project/settings.py index 85dcc48d..caa79f08 100644 --- a/knox_project/settings.py +++ b/knox_project/settings.py @@ -53,3 +53,5 @@ USE_TZ = True STATIC_URL = '/static/' + +KNOX_TOKEN_MODEL = 'knox.AuthToken' From 3a1bc584f9691f4bc19d8a04a98c68c293be9ca6 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Wed, 24 Aug 2022 15:21:38 +0200 Subject: [PATCH 08/41] feat: add token prefix option (#272) Fixes https://github.com/James1345/django-rest-knox/issues/271 --- docs/settings.md | 7 ++ knox/crypto.py | 10 ++- .../migrations/0009_extend_authtoken_field.py | 18 +++++ knox/models.py | 20 ++++-- knox/settings.py | 6 +- knox/views.py | 6 +- tests/tests.py | 71 ++++++++++++++++++- 7 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 knox/migrations/0009_extend_authtoken_field.py diff --git a/docs/settings.md b/docs/settings.md index 5f930900..0ba2317d 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -98,6 +98,10 @@ The default is `knox.AuthToken` [DATETIME_FORMAT]: https://www.django-rest-framework.org/api-guide/settings/#date-and-time-formatting [strftime format]: https://docs.python.org/3/library/time.html#time.strftime +## TOKEN_PREFIX +This is the prefix for the generated token that is used in the Authorization header. The default is just an empty string. +It can be up to `CONSTANTS.MAXIMUM_TOKEN_PREFIX_LENGTH` long. + # Constants `knox.settings` Knox also provides some constants for information. These must not be changed in external code; they are used in the model definitions in knox and an error will @@ -111,3 +115,6 @@ print(CONSTANTS.DIGEST_LENGTH) #=> 128 ## DIGEST_LENGTH This is the length of the digest that will be stored in the database for each token. + +## MAXIMUM_TOKEN_PREFIX_LENGTH +This is the maximum length of the token prefix. diff --git a/knox/crypto.py b/knox/crypto.py index 3ce8811e..02b53697 100644 --- a/knox/crypto.py +++ b/knox/crypto.py @@ -12,6 +12,14 @@ def create_token_string(): ).decode() +def make_hex_compatible(token: str) -> str: + """ + We need to make sure that the token, that is send is hex-compatible. + When a token prefix is used, we cannot garantee that. + """ + return binascii.unhexlify(binascii.hexlify(bytes(token, 'utf-8'))) + + def hash_token(token: str) -> str: """ Calculates the hash of a token. @@ -19,5 +27,5 @@ def hash_token(token: str) -> str: a binascii.Error exception will be raised. """ digest = hash_func() - digest.update(binascii.unhexlify(token)) + digest.update(make_hex_compatible(token)) return digest.hexdigest() diff --git a/knox/migrations/0009_extend_authtoken_field.py b/knox/migrations/0009_extend_authtoken_field.py new file mode 100644 index 00000000..18a33836 --- /dev/null +++ b/knox/migrations/0009_extend_authtoken_field.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1rc1 on 2022-07-20 17:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("knox", "0008_remove_authtoken_salt"), + ] + + operations = [ + migrations.AlterField( + model_name="authtoken", + name="token_key", + field=models.CharField(db_index=True, max_length=25), + ), + ] diff --git a/knox/models.py b/knox/models.py index 4d316650..b054f289 100644 --- a/knox/models.py +++ b/knox/models.py @@ -7,20 +7,25 @@ from knox import crypto from knox.settings import CONSTANTS, knox_settings +sha = knox_settings.SECURE_HASH_ALGORITHM + User = settings.AUTH_USER_MODEL class AuthTokenManager(models.Manager): - def create(self, user, expiry=knox_settings.TOKEN_TTL, **kwargs): - token = crypto.create_token_string() + def create( + self, + user, + expiry=knox_settings.TOKEN_TTL, + prefix=knox_settings.TOKEN_PREFIX + ): + token = prefix + crypto.create_token_string() digest = crypto.hash_token(token) - if expiry is not None: expiry = timezone.now() + expiry - instance = super(AuthTokenManager, self).create( token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH], digest=digest, - user=user, expiry=expiry, **kwargs) + user=user, expiry=expiry) return instance, token @@ -31,7 +36,10 @@ class AbstractAuthToken(models.Model): digest = models.CharField( max_length=CONSTANTS.DIGEST_LENGTH, primary_key=True) token_key = models.CharField( - max_length=CONSTANTS.TOKEN_KEY_LENGTH, db_index=True) + max_length=CONSTANTS.MAXIMUM_TOKEN_PREFIX_LENGTH + + CONSTANTS.TOKEN_KEY_LENGTH, + db_index=True + ) user = models.ForeignKey(User, null=False, blank=False, related_name='auth_token_set', on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) diff --git a/knox/settings.py b/knox/settings.py index b86e95bb..a5def499 100644 --- a/knox/settings.py +++ b/knox/settings.py @@ -17,6 +17,7 @@ 'AUTH_HEADER_PREFIX': 'Token', 'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT, 'TOKEN_MODEL': getattr(settings, 'KNOX_TOKEN_MODEL', 'knox.AuthToken'), + 'TOKEN_PREFIX': '', } IMPORT_STRINGS = { @@ -32,6 +33,8 @@ def reload_api_settings(*args, **kwargs): setting, value = kwargs['setting'], kwargs['value'] if setting == 'REST_KNOX': knox_settings = APISettings(value, DEFAULTS, IMPORT_STRINGS) + if len(knox_settings.TOKEN_PREFIX) > CONSTANTS.MAXIMUM_TOKEN_PREFIX_LENGTH: + raise ValueError("Illegal TOKEN_PREFIX length") setting_changed.connect(reload_api_settings) @@ -41,8 +44,9 @@ class CONSTANTS: ''' Constants cannot be changed at runtime ''' - TOKEN_KEY_LENGTH = 8 + TOKEN_KEY_LENGTH = 15 DIGEST_LENGTH = 128 + MAXIMUM_TOKEN_PREFIX_LENGTH = 10 def __setattr__(self, *args, **kwargs): raise Exception(''' diff --git a/knox/views.py b/knox/views.py index 7b3d4920..7a6b5719 100644 --- a/knox/views.py +++ b/knox/views.py @@ -22,6 +22,9 @@ def get_context(self): def get_token_ttl(self): return knox_settings.TOKEN_TTL + def get_token_prefix(self): + return knox_settings.TOKEN_PREFIX + def get_token_limit_per_user(self): return knox_settings.TOKEN_LIMIT_PER_USER @@ -36,8 +39,9 @@ def format_expiry_datetime(self, expiry): return DateTimeField(format=datetime_format).to_representation(expiry) def create_token(self): + token_prefix = self.get_token_prefix() return get_token_model().objects.create( - user=self.request.user, expiry=self.get_token_ttl() + user=self.request.user, expiry=self.get_token_ttl(), prefix=token_prefix ) def get_post_response_data(self, request, token, instance): diff --git a/tests/tests.py b/tests/tests.py index 914ce204..574305c2 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -10,7 +10,7 @@ from rest_framework.test import APIRequestFactory, APITestCase as TestCase from six.moves import reload_module -from knox import auth, views +from knox import auth, crypto, views from knox.auth import TokenAuthentication from knox.models import AuthToken from knox.serializers import UserSerializer @@ -45,6 +45,13 @@ def get_basic_auth_header(username, password): expiry_datetime_format_knox = knox_settings.defaults.copy() expiry_datetime_format_knox["EXPIRY_DATETIME_FORMAT"] = EXPIRY_DATETIME_FORMAT +token_prefix = "TEST_" +token_prefix_knox = knox_settings.defaults.copy() +token_prefix_knox["TOKEN_PREFIX"] = token_prefix + +token_prefix_too_long = "a" * CONSTANTS.MAXIMUM_TOKEN_PREFIX_LENGTH + "a" +token_prefix_too_long_knox = knox_settings.defaults.copy() +token_prefix_too_long_knox["TOKEN_PREFIX"] = token_prefix_too_long class AuthTestCase(TestCase): @@ -419,3 +426,65 @@ def test_expiry_is_present(self): response.data['expiry'], DateTimeField().to_representation(AuthToken.objects.first().expiry) ) + + def test_login_returns_serialized_token_with_prefix_when_prefix_set(self): + with override_settings(REST_KNOX=token_prefix_knox): + reload_module(views) + reload_module(crypto) + self.assertEqual(AuthToken.objects.count(), 0) + url = reverse('knox_login') + self.client.credentials( + HTTP_AUTHORIZATION=get_basic_auth_header(self.username, self.password) + ) + response = self.client.post( + url, + {}, + format='json' + ) + self.assertEqual(response.status_code, 200) + self.assertTrue(response.data['token'].startswith(token_prefix)) + reload_module(views) + reload_module(crypto) + + def test_token_with_prefix_returns_200(self): + with override_settings(REST_KNOX=token_prefix_knox): + reload_module(views) + self.assertEqual(AuthToken.objects.count(), 0) + url = reverse('knox_login') + self.client.credentials( + HTTP_AUTHORIZATION=get_basic_auth_header(self.username, self.password) + ) + response = self.client.post( + url, + {}, + format='json' + ) + self.assertEqual(response.status_code, 200) + self.assertTrue(response.data['token'].startswith(token_prefix)) + self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % response.data['token'])) + response = self.client.get(root_url, {}, format='json') + self.assertEqual(response.status_code, 200) + reload_module(views) + + def test_prefix_set_longer_than_max_length_raises_valueerror(self): + with self.assertRaises(ValueError): + with override_settings(REST_KNOX=token_prefix_too_long_knox): + pass + + def test_tokens_created_before_prefix_still_work(self): + self.client.credentials( + HTTP_AUTHORIZATION=get_basic_auth_header(self.username, self.password) + ) + url = reverse('knox_login') + response = self.client.post( + url, + {}, + format='json' + ) + self.assertFalse(response.data['token'].startswith(token_prefix)) + with override_settings(REST_KNOX=token_prefix_knox): + reload_module(views) + self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % response.data['token'])) + response = self.client.get(root_url, {}, format='json') + self.assertEqual(response.status_code, 200) + reload_module(views) From dff944e572df2edc2933e900b0faad8bb4df263e Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Tue, 30 Aug 2022 12:54:06 +0200 Subject: [PATCH 09/41] Add Django 4.1 to test --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 6ee441da..0ff53fe5 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = flake8, py{36,37,38,39,310}-django32, py{38,39,310}-django40, + py{38,39,310}-django41, [testenv:flake8] deps = flake8 @@ -29,6 +30,7 @@ setenv = deps = django32: Django>=3.2,<3.3 django40: Django>=4.0,<4.1 + django41: Django>=4.1,<4.2 markdown<3.0 isort>=5.0 djangorestframework From 19e18cf64d5f5ddad70aaa1f4c1437650f47b073 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Tue, 30 Aug 2022 13:22:33 +0200 Subject: [PATCH 10/41] Add deprecation note to `USE_L10N` --- knox_project/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knox_project/settings.py b/knox_project/settings.py index caa79f08..d5b6c40c 100644 --- a/knox_project/settings.py +++ b/knox_project/settings.py @@ -49,7 +49,7 @@ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True -USE_L10N = True +USE_L10N = True # Deprecated since django 4.0. USE_TZ = True STATIC_URL = '/static/' From 3651a07be56addcf7f9f17151f438de67042444c Mon Sep 17 00:00:00 2001 From: Bruk Berhane Asfaw Date: Mon, 17 Oct 2022 17:15:29 +0300 Subject: [PATCH 11/41] update AuthTokenManager to support custom values --- knox/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/knox/models.py b/knox/models.py index b054f289..33ec7239 100644 --- a/knox/models.py +++ b/knox/models.py @@ -17,7 +17,8 @@ def create( self, user, expiry=knox_settings.TOKEN_TTL, - prefix=knox_settings.TOKEN_PREFIX + prefix=knox_settings.TOKEN_PREFIX, + **kwargs ): token = prefix + crypto.create_token_string() digest = crypto.hash_token(token) @@ -25,7 +26,7 @@ def create( expiry = timezone.now() + expiry instance = super(AuthTokenManager, self).create( token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH], digest=digest, - user=user, expiry=expiry) + user=user, expiry=expiry, **kwargs) return instance, token From d2fc245037de7f458d82fc3d282a7adca41e1074 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Sun, 8 Jan 2023 15:24:52 +0100 Subject: [PATCH 12/41] Remove legacy code from Python 2 --- knox/auth.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/knox/auth.py b/knox/auth.py index a7b5f873..bf3749e2 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -1,10 +1,5 @@ -try: - from hmac import compare_digest -except ImportError: - def compare_digest(a, b): - return a == b - import binascii +from hmac import compare_digest from django.utils import timezone from django.utils.translation import gettext_lazy as _ From 68287387361bf337fee3fc0a92b5468d2fe57916 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Sun, 15 Nov 2020 20:08:41 +0100 Subject: [PATCH 13/41] [doc] improve readability of example code --- docs/auth.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/auth.md b/docs/auth.md index 63828ed9..8e81c01f 100644 --- a/docs/auth.md +++ b/docs/auth.md @@ -49,18 +49,17 @@ If it is your only default authentication class, remember to overwrite knox's Lo For instance, you can authenticate users using Basic Authentication by simply overwriting knox's LoginView and setting BasicAuthentication as one of the acceptable authentication classes, as follows: +**views.py:** ```python - -views.py: - from knox.views import LoginView as KnoxLoginView from rest_framework.authentication import BasicAuthentication class LoginView(KnoxLoginView): authentication_classes = [BasicAuthentication] +``` -urls.py: - +**urls.py:** +```python from knox import views as knox_views from yourapp.api.views import LoginView @@ -75,10 +74,8 @@ You can use any number of authentication classes if you want to be able to authe If you decide to use Token Authentication as your only authentication class, you can overwrite knox's login view as such: +**views.py:** ```python - -views.py: - from django.contrib.auth import login from rest_framework import permissions @@ -94,9 +91,10 @@ class LoginView(KnoxLoginView): user = serializer.validated_data['user'] login(request, user) return super(LoginView, self).post(request, format=None) +``` -urls.py: - +**urls.py:** +```python from knox import views as knox_views from yourapp.api.views import LoginView From 1a9d833ab6eb0151f1400cd69967a948fab9b43e Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Thu, 5 Jan 2023 15:32:44 +0100 Subject: [PATCH 14/41] Add python 3.11 to test --- .github/workflows/test.yml | 2 +- setup.py | 1 + tox.ini | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 379d39cc..d64012e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false max-parallel: 5 matrix: - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index a75d3689..ffcba9a2 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', ], # What does your project relate to? diff --git a/tox.ini b/tox.ini index 0ff53fe5..86deadc4 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = flake8, py{36,37,38,39,310}-django32, py{38,39,310}-django40, - py{38,39,310}-django41, + py{38,39,310,311}-django41, [testenv:flake8] deps = flake8 @@ -30,7 +30,7 @@ setenv = deps = django32: Django>=3.2,<3.3 django40: Django>=4.0,<4.1 - django41: Django>=4.1,<4.2 + django41: Django>=4.1.3,<4.2 markdown<3.0 isort>=5.0 djangorestframework @@ -48,3 +48,4 @@ python = 3.8: py38 3.9: py39, isort, flake8 3.10: py310 + 3.11: py311 From 2838c5c4f8785bf09a09d3d726d376e1725695cd Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Sun, 8 Jan 2023 14:25:07 +0100 Subject: [PATCH 15/41] Move isort, flake8 test to Python 3.10 --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 86deadc4..dece6d1a 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,6 @@ python = 3.6: py36 3.7: py37 3.8: py38 - 3.9: py39, isort, flake8 - 3.10: py310 + 3.9: py39 + 3.10: py310, isort, flake8 3.11: py311 From af85fc66941e2b7a3017db04f52b78d9b67cdc8e Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Sun, 8 Jan 2023 14:34:05 +0100 Subject: [PATCH 16/41] Bump GH Actions versions --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d64012e9..3ddb41c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,10 +12,10 @@ jobs: python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -25,7 +25,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: From 9e94bb38ecc9ac162991d17ef7868905679502b0 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Sun, 8 Jan 2023 14:48:17 +0100 Subject: [PATCH 17/41] GitHub Actions: Deprecating save-state and set-output commands https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ddb41c2..ca8a3067 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: Get pip cache dir id: pip-cache run: | - echo "::set-output name=dir::$(pip cache dir)" + echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: Cache uses: actions/cache@v3 From 767e7b8ed3f78daa76d2990ef58d83332e2416fd Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Fri, 13 Jan 2023 09:08:21 +0100 Subject: [PATCH 18/41] Update `markdown` dependency --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dece6d1a..a0a846d0 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ deps = django32: Django>=3.2,<3.3 django40: Django>=4.0,<4.1 django41: Django>=4.1.3,<4.2 - markdown<3.0 + markdown>=3.0 isort>=5.0 djangorestframework freezegun From 6f0c7a9949fb35bca480740ab21c95e009e13c2a Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Fri, 13 Jan 2023 11:57:49 +0100 Subject: [PATCH 19/41] Remove legacy compat lib `six` --- tests/tests.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/tests.py b/tests/tests.py index 574305c2..f6573cff 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,5 +1,6 @@ import base64 from datetime import datetime, timedelta +from importlib import reload from django.contrib.auth import get_user_model from django.test import override_settings @@ -8,7 +9,6 @@ from rest_framework.exceptions import AuthenticationFailed from rest_framework.serializers import DateTimeField from rest_framework.test import APIRequestFactory, APITestCase as TestCase -from six.moves import reload_module from knox import auth, crypto, views from knox.auth import TokenAuthentication @@ -93,7 +93,7 @@ def test_login_returns_serialized_token(self): def test_login_returns_serialized_token_and_username_field(self): with override_settings(REST_KNOX=user_serializer_knox): - reload_module(views) + reload(views) self.assertEqual(AuthToken.objects.count(), 0) url = reverse('knox_login') self.client.credentials( @@ -101,7 +101,7 @@ def test_login_returns_serialized_token_and_username_field(self): ) response = self.client.post(url, {}, format='json') self.assertEqual(user_serializer_knox["USER_SERIALIZER"], UserSerializer) - reload_module(views) + (views) self.assertEqual(response.status_code, 200) self.assertIn('token', response.data) username_field = self.user.USERNAME_FIELD @@ -111,7 +111,7 @@ def test_login_returns_serialized_token_and_username_field(self): def test_login_returns_configured_expiry_datetime_format(self): with override_settings(REST_KNOX=expiry_datetime_format_knox): - reload_module(views) + reload(views) self.assertEqual(AuthToken.objects.count(), 0) url = reverse('knox_login') self.client.credentials( @@ -122,7 +122,7 @@ def test_login_returns_configured_expiry_datetime_format(self): expiry_datetime_format_knox["EXPIRY_DATETIME_FORMAT"], EXPIRY_DATETIME_FORMAT ) - reload_module(views) + reload(views) self.assertEqual(response.status_code, 200) self.assertIn('token', response.data) self.assertNotIn('user', response.data) @@ -257,10 +257,10 @@ def test_token_expiry_is_extended_with_auto_refresh_activated(self): self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) five_hours_later = original_time + timedelta(hours=5) with override_settings(REST_KNOX=auto_refresh_knox): - reload_module(auth) # necessary to reload settings in core code + reload(auth) # necessary to reload settings in core code with freeze_time(five_hours_later): response = self.client.get(root_url, {}, format='json') - reload_module(auth) + reload(auth) self.assertEqual(response.status_code, 200) # original expiry date was extended: @@ -309,10 +309,10 @@ def test_token_expiry_is_not_extended_within_MIN_REFRESH_INTERVAL(self): self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) in_min_interval = now + timedelta(seconds=knox_settings.MIN_REFRESH_INTERVAL - 10) with override_settings(REST_KNOX=auto_refresh_knox): - reload_module(auth) # necessary to reload settings in core code + reload(auth) # necessary to reload settings in core code with freeze_time(in_min_interval): response = self.client.get(root_url, {}, format='json') - reload_module(auth) # necessary to reload settings in core code + reload(auth) # necessary to reload settings in core code self.assertEqual(response.status_code, 200) self.assertEqual(original_expiry, AuthToken.objects.get().expiry) @@ -334,7 +334,7 @@ def handler(sender, username, **kwargs): def test_exceed_token_amount_per_user(self): with override_settings(REST_KNOX=token_user_limit_knox): - reload_module(views) + reload(views) for _ in range(10): AuthToken.objects.create(user=self.user) url = reverse('knox_login') @@ -342,7 +342,7 @@ def test_exceed_token_amount_per_user(self): HTTP_AUTHORIZATION=get_basic_auth_header(self.username, self.password) ) response = self.client.post(url, {}, format='json') - reload_module(views) + reload(views) self.assertEqual(response.status_code, 403) self.assertEqual(response.data, {"error": "Maximum amount of tokens allowed per user exceeded."}) @@ -350,7 +350,7 @@ def test_exceed_token_amount_per_user(self): def test_does_not_exceed_on_expired_keys(self): with override_settings(REST_KNOX=token_user_limit_knox): - reload_module(views) + reload(views) for _ in range(9): AuthToken.objects.create(user=self.user) AuthToken.objects.create(user=self.user, expiry=timedelta(seconds=-1)) @@ -361,7 +361,7 @@ def test_does_not_exceed_on_expired_keys(self): ) response = self.client.post(url, {}, format='json') failed_response = self.client.post(url, {}, format='json') - reload_module(views) + reload(views) self.assertEqual(response.status_code, 200) self.assertIn('token', response.data) self.assertEqual(failed_response.status_code, 403) @@ -371,7 +371,7 @@ def test_does_not_exceed_on_expired_keys(self): def test_invalid_prefix_return_401(self): with override_settings(REST_KNOX=auth_header_prefix_knox): - reload_module(auth) + reload(auth) instance, token = AuthToken.objects.create(user=self.user) self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) failed_response = self.client.get(root_url) @@ -381,13 +381,13 @@ def test_invalid_prefix_return_401(self): ) ) response = self.client.get(root_url) - reload_module(auth) + reload(auth) self.assertEqual(failed_response.status_code, 401) self.assertEqual(response.status_code, 200) def test_expiry_present_also_when_none(self): with override_settings(REST_KNOX=token_no_expiration_knox): - reload_module(views) + reload(views) self.assertEqual(AuthToken.objects.count(), 0) url = reverse('knox_login') self.client.credentials( @@ -406,7 +406,7 @@ def test_expiry_present_also_when_none(self): response.data['expiry'], None ) - reload_module(views) + reload(views) def test_expiry_is_present(self): self.assertEqual(AuthToken.objects.count(), 0) @@ -429,8 +429,8 @@ def test_expiry_is_present(self): def test_login_returns_serialized_token_with_prefix_when_prefix_set(self): with override_settings(REST_KNOX=token_prefix_knox): - reload_module(views) - reload_module(crypto) + reload(views) + reload(crypto) self.assertEqual(AuthToken.objects.count(), 0) url = reverse('knox_login') self.client.credentials( @@ -443,12 +443,12 @@ def test_login_returns_serialized_token_with_prefix_when_prefix_set(self): ) self.assertEqual(response.status_code, 200) self.assertTrue(response.data['token'].startswith(token_prefix)) - reload_module(views) - reload_module(crypto) + reload(views) + reload(crypto) def test_token_with_prefix_returns_200(self): with override_settings(REST_KNOX=token_prefix_knox): - reload_module(views) + reload(views) self.assertEqual(AuthToken.objects.count(), 0) url = reverse('knox_login') self.client.credentials( @@ -464,7 +464,7 @@ def test_token_with_prefix_returns_200(self): self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % response.data['token'])) response = self.client.get(root_url, {}, format='json') self.assertEqual(response.status_code, 200) - reload_module(views) + reload(views) def test_prefix_set_longer_than_max_length_raises_valueerror(self): with self.assertRaises(ValueError): @@ -483,8 +483,8 @@ def test_tokens_created_before_prefix_still_work(self): ) self.assertFalse(response.data['token'].startswith(token_prefix)) with override_settings(REST_KNOX=token_prefix_knox): - reload_module(views) + reload(views) self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % response.data['token'])) response = self.client.get(root_url, {}, format='json') self.assertEqual(response.status_code, 200) - reload_module(views) + reload(views) From 761dad2d5bfd5c210b65305a4aab69837bbf92ea Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Mon, 16 Jan 2023 12:23:40 +0100 Subject: [PATCH 20/41] Enable customizing login/logout responses --- knox/views.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/knox/views.py b/knox/views.py index 7a6b5719..97cbe42c 100644 --- a/knox/views.py +++ b/knox/views.py @@ -58,6 +58,10 @@ def get_post_response_data(self, request, token, instance): ).data return data + def get_post_response(self, request, token, instance): + data = self.get_post_response_data(request, token, instance) + return Response(data) + def post(self, request, format=None): token_limit_per_user = self.get_token_limit_per_user() if token_limit_per_user is not None: @@ -71,19 +75,21 @@ def post(self, request, format=None): instance, token = self.create_token() user_logged_in.send(sender=request.user.__class__, request=request, user=request.user) - data = self.get_post_response_data(request, token, instance) - return Response(data) + return self.get_post_response(request, token, instance) class LogoutView(APIView): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) + def get_post_response(self, request): + return Response(None, status=status.HTTP_204_NO_CONTENT) + def post(self, request, format=None): request._auth.delete() user_logged_out.send(sender=request.user.__class__, request=request, user=request.user) - return Response(None, status=status.HTTP_204_NO_CONTENT) + return self.get_post_response(request) class LogoutAllView(APIView): @@ -94,8 +100,11 @@ class LogoutAllView(APIView): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) + def get_post_response(self, request): + return Response(None, status=status.HTTP_204_NO_CONTENT) + def post(self, request, format=None): request.user.auth_token_set.all().delete() user_logged_out.send(sender=request.user.__class__, request=request, user=request.user) - return Response(None, status=status.HTTP_204_NO_CONTENT) + return self.get_post_response(request) From bc82a16b55e82bc0f227442c9aa70c694df05649 Mon Sep 17 00:00:00 2001 From: Jack Morgan Date: Thu, 3 Aug 2023 14:11:31 +1200 Subject: [PATCH 21/41] Update link to documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 203db1f1..88fe5d30 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ default implementation: the app settings (default is 10 hours.) More information can be found in the -[Documentation](https://james1345.github.io/django-rest-knox/) +[Documentation](https://jazzband.github.io/django-rest-knox/) # Run the tests locally From 3ce2bc5d63ad82605cbd9c9b3b792407e31863a4 Mon Sep 17 00:00:00 2001 From: Giovanni Cimolin Date: Tue, 15 Aug 2023 19:20:43 +0200 Subject: [PATCH 22/41] docs: Update references to point to Jazzband --- CONTRIBUTING.md | 3 +++ README.md | 3 ++- mkdocs.yml | 2 +- setup.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..10d79191 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/) + +This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/about/conduct) and follow the [guidelines](https://jazzband.co/about/guidelines). \ No newline at end of file diff --git a/README.md b/README.md index 88fe5d30..8a5ca1d7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ django-rest-knox ================ -[![image](https://github.com/James1345/django-rest-knox/workflows/Test/badge.svg?branch=develop)](https://github.com/James1345/django-rest-knox/actions) +[![Jazzband](https://jazzband.co/static/img/badge.svg)](https://jazzband.co/) +[![image](https://github.com/jazzband/django-rest-knox/workflows/Test/badge.svg?branch=develop)](https://github.com/jazzband/django-rest-knox/actions) Authentication Module for django rest auth diff --git a/mkdocs.yml b/mkdocs.yml index 571ed993..2ecec2a3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Django-Rest-Knox -repo_url: https://github.com/James1345/django-rest-knox +repo_url: https://github.com/jazzband/django-rest-knox theme: readthedocs nav: - Home: 'index.md' diff --git a/setup.py b/setup.py index a75d3689..645da21e 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ long_description_content_type='text/markdown', # The project's main homepage. - url='https://github.com/James1345/django-rest-knox', + url='https://github.com/jazzband/django-rest-knox', # Author details author='James McMahon', From 88b41762c2c372698803e138fd66be0d1ecf3b9a Mon Sep 17 00:00:00 2001 From: Giovanni Cimolin Date: Tue, 15 Aug 2023 20:10:21 +0200 Subject: [PATCH 23/41] test: Set up pre-commit hooks and fix quality nits --- .pre-commit-config.yaml | 9 +++++++++ tests/tests.py | 16 ++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..12a562c3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: + - repo: https://github.com/PyCQA/isort + rev: 5.11.5 + hooks: + - id: isort + - repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 + hooks: + - id: flake8 \ No newline at end of file diff --git a/tests/tests.py b/tests/tests.py index 574305c2..f6eb6911 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -53,6 +53,7 @@ def get_basic_auth_header(username, password): token_prefix_too_long_knox = knox_settings.defaults.copy() token_prefix_too_long_knox["TOKEN_PREFIX"] = token_prefix_too_long + class AuthTestCase(TestCase): def setUp(self): @@ -325,7 +326,10 @@ def handler(sender, username, **kwargs): token_expired.connect(handler) - instance, token = AuthToken.objects.create(user=self.user, expiry=timedelta(seconds=-1)) + instance, token = AuthToken.objects.create( + user=self.user, + expiry=timedelta(seconds=-1), + ) self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) self.client.post(root_url, {}, format='json') @@ -461,13 +465,15 @@ def test_token_with_prefix_returns_200(self): ) self.assertEqual(response.status_code, 200) self.assertTrue(response.data['token'].startswith(token_prefix)) - self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % response.data['token'])) + self.client.credentials( + HTTP_AUTHORIZATION=('Token %s' % response.data['token']) + ) response = self.client.get(root_url, {}, format='json') self.assertEqual(response.status_code, 200) reload_module(views) def test_prefix_set_longer_than_max_length_raises_valueerror(self): - with self.assertRaises(ValueError): + with self.assertRaises(ValueError): with override_settings(REST_KNOX=token_prefix_too_long_knox): pass @@ -484,7 +490,9 @@ def test_tokens_created_before_prefix_still_work(self): self.assertFalse(response.data['token'].startswith(token_prefix)) with override_settings(REST_KNOX=token_prefix_knox): reload_module(views) - self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % response.data['token'])) + self.client.credentials( + HTTP_AUTHORIZATION=('Token %s' % response.data['token']) + ) response = self.client.get(root_url, {}, format='json') self.assertEqual(response.status_code, 200) reload_module(views) From 5b936a715150fa007d30947760c69cfdcddd5d88 Mon Sep 17 00:00:00 2001 From: Giovanni Cimolin Date: Tue, 15 Aug 2023 20:22:35 +0200 Subject: [PATCH 24/41] test: Add coverage and codecov bot --- .codecov.yml | 11 +++++++++++ .coveragerc | 5 +++++ .github/workflows/test.yml | 8 +++++++- tox.ini | 4 +++- 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .codecov.yml create mode 100644 .coveragerc diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 00000000..cfce0f82 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,11 @@ +coverage: + status: + project: + default: false + tests: + paths: tests + informational: true + knox: + paths: knox + informational: true + patch: off \ No newline at end of file diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..9d0f838e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[run] +branch = True +source = knox +omit = + */migrations/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 379d39cc..cb0dc5bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,8 +36,14 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install --upgrade tox tox-gh-actions + python -m pip install --upgrade tox tox-gh-actions coverage - name: Tox tests run: | tox -v + + - name: Generate coverage XML report + run: coverage xml + + - name: Codecov + uses: codecov/codecov-action@v3 diff --git a/tox.ini b/tox.ini index 6ee441da..ef5d6b05 100644 --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,8 @@ commands = isort --check-only --diff \ [testenv] commands = python manage.py migrate - python manage.py test + coverage run manage.py test + coverage report setenv = DJANGO_SETTINGS_MODULE = knox_project.settings PIP_INDEX_URL = https://pypi.python.org/simple/ @@ -38,6 +39,7 @@ deps = setuptools twine wheel + coverage [gh-actions] python = From 1a57338e8e1378a3483f888e10ccf5d791306f6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 20:21:43 +0000 Subject: [PATCH 25/41] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/isort: 5.11.5 → 5.12.0](https://github.com/PyCQA/isort/compare/5.11.5...5.12.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 12a562c3..5ec1b33f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/PyCQA/isort - rev: 5.11.5 + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/PyCQA/flake8 From 66833062aa85fdd06badfef6f35b5cf3624a5fd5 Mon Sep 17 00:00:00 2001 From: Niall McAndrew Date: Mon, 9 Oct 2023 08:58:28 +1300 Subject: [PATCH 26/41] Fix migrations when used in condition with a custom DB. --- knox/migrations/0006_auto_20160818_0932.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knox/migrations/0006_auto_20160818_0932.py b/knox/migrations/0006_auto_20160818_0932.py index b8540905..e1d01543 100644 --- a/knox/migrations/0006_auto_20160818_0932.py +++ b/knox/migrations/0006_auto_20160818_0932.py @@ -7,7 +7,7 @@ def cleanup_tokens(apps, schema_editor): AuthToken = apps.get_model('knox', 'AuthToken') - AuthToken.objects.filter(token_key__isnull=True).delete() + AuthToken.objects.using(schema_editor.connection.alias).filter(token_key__isnull=True).delete() class Migration(migrations.Migration): From b02a1553baff849d0f3a2b83da5dda5bf793157c Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Fri, 17 Jun 2022 18:03:51 +0200 Subject: [PATCH 27/41] fix(docs): add warning of migration to `4.2.0` Closes: #266 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6392694..8280b3ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 4.2.0 - compatibility with Python up to 3.10 and Django up to 4.0 - integration with github CI instead of travis -- Migration: "salt" field of model "AuthToken" is removed +- Migration: "salt" field of model "AuthToken" is removed, WARNING: invalidates old tokens! ## 4.1.0 From 03272c99211e8445517acef885220226a0d85a68 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Tue, 10 Oct 2023 14:36:05 +0200 Subject: [PATCH 28/41] fix(docs): correct typo --- knox/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knox/crypto.py b/knox/crypto.py index 02b53697..1ee36146 100644 --- a/knox/crypto.py +++ b/knox/crypto.py @@ -15,7 +15,7 @@ def create_token_string(): def make_hex_compatible(token: str) -> str: """ We need to make sure that the token, that is send is hex-compatible. - When a token prefix is used, we cannot garantee that. + When a token prefix is used, we cannot guarantee that. """ return binascii.unhexlify(binascii.hexlify(bytes(token, 'utf-8'))) From 43d983d49b4fbfb302077f84c61e8e49267b1a24 Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Tue, 17 Oct 2023 10:06:40 +0200 Subject: [PATCH 29/41] Add docs for get_post_response --- docs/views.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/views.md b/docs/views.md index 074b16a7..285222da 100644 --- a/docs/views.md +++ b/docs/views.md @@ -67,12 +67,24 @@ It responds to Knox Token Authentication. On a successful request, the token used to authenticate is deleted from the system and can no longer be used to authenticate. +By default, this endpoint returns a HTTP 204 response on a successful request. To +customize this behavior, you can override the `get_post_response` method, for example +to include a body in the logout response and/or to modify the status code: + +```python +...snip... + def get_post_response(self, request): + return Response({"bye-bye": request.user.username}, status=200) +...snip... +``` + ## LogoutAllView This view accepts only a post request with an empty body. It responds to Knox Token Authentication. -On a successful request, the token used to authenticate, and *all other tokens* -registered to the same `User` account, are deleted from the -system and can no longer be used to authenticate. +On a successful request, a HTTP 204 is returned and the token used to authenticate, +and *all other tokens* registered to the same `User` account, are deleted from the +system and can no longer be used to authenticate. The success response can be modified +like the `LogoutView` by overriding the `get_post_response` method. **Note** It is not recommended to alter the Logout views. They are designed specifically for token management, and to respond to Knox authentication. From f33c1af4aaa851443c8d03d5a8fbce6bc0621478 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Tue, 10 Oct 2023 13:20:48 +0200 Subject: [PATCH 30/41] feat(ci): add django4.2; remove eol django4.0 --- .github/workflows/test.yml | 2 +- tox.ini | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a85d90a..b2dd4e58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 diff --git a/tox.ini b/tox.ini index e60a8d0e..a3b04839 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,8 @@ envlist = isort, flake8, py{36,37,38,39,310}-django32, - py{38,39,310}-django40, py{38,39,310,311}-django41, + py{38,39,310,311}-django42, [testenv:flake8] deps = flake8 @@ -30,8 +30,8 @@ setenv = PIP_INDEX_URL = https://pypi.python.org/simple/ deps = django32: Django>=3.2,<3.3 - django40: Django>=4.0,<4.1 django41: Django>=4.1.3,<4.2 + django42: Django>=4.2,<4.3 markdown>=3.0 isort>=5.0 djangorestframework @@ -49,5 +49,6 @@ python = 3.7: py37 3.8: py38 3.9: py39 - 3.10: py310, isort, flake8 - 3.11: py311 + 3.10: py310 + 3.11: py311, isort, flake8 + 3.12: py312 From 18b4775153f1c4754474664f85750c96c8e01942 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Sun, 10 Dec 2023 19:15:00 +0100 Subject: [PATCH 31/41] feat(ci): add django5.0; add python3.12; remove eol django4.1 --- .github/workflows/test.yml | 2 +- setup.py | 1 + tox.ini | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2dd4e58..de70e1e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false max-parallel: 5 matrix: - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index fefc3fc5..6ff24321 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', ], # What does your project relate to? diff --git a/tox.ini b/tox.ini index a3b04839..3a159a54 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,8 @@ envlist = isort, flake8, py{36,37,38,39,310}-django32, - py{38,39,310,311}-django41, - py{38,39,310,311}-django42, + py{38,39,310,311,312}-django42, + py{310,311,312}-django50, [testenv:flake8] deps = flake8 @@ -30,8 +30,8 @@ setenv = PIP_INDEX_URL = https://pypi.python.org/simple/ deps = django32: Django>=3.2,<3.3 - django41: Django>=4.1.3,<4.2 django42: Django>=4.2,<4.3 + django50: Django>=5.0,<5.1 markdown>=3.0 isort>=5.0 djangorestframework From 22d787e3d75a563853df2d3886f87db40f05785a Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Sun, 17 Dec 2023 19:43:06 +0100 Subject: [PATCH 32/41] feat: add typing --- knox/auth.py | 4 ++-- knox/crypto.py | 4 ++-- knox/models.py | 2 +- knox/settings.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/knox/auth.py b/knox/auth.py index bf3749e2..ffb0880b 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -71,7 +71,7 @@ def authenticate_credentials(self, token): return self.validate_user(auth_token) raise exceptions.AuthenticationFailed(msg) - def renew_token(self, auth_token): + def renew_token(self, auth_token) -> None: current_expiry = auth_token.expiry new_expiry = timezone.now() + knox_settings.TOKEN_TTL auth_token.expiry = new_expiry @@ -89,7 +89,7 @@ def validate_user(self, auth_token): def authenticate_header(self, request): return knox_settings.AUTH_HEADER_PREFIX - def _cleanup_token(self, auth_token): + def _cleanup_token(self, auth_token) -> bool: for other_token in auth_token.user.auth_token_set.all(): if other_token.digest != auth_token.digest and other_token.expiry: if other_token.expiry < timezone.now(): diff --git a/knox/crypto.py b/knox/crypto.py index 1ee36146..02e70ffe 100644 --- a/knox/crypto.py +++ b/knox/crypto.py @@ -6,13 +6,13 @@ hash_func = knox_settings.SECURE_HASH_ALGORITHM -def create_token_string(): +def create_token_string() -> str: return binascii.hexlify( generate_bytes(int(knox_settings.AUTH_TOKEN_CHARACTER_LENGTH / 2)) ).decode() -def make_hex_compatible(token: str) -> str: +def make_hex_compatible(token: str) -> bytes: """ We need to make sure that the token, that is send is hex-compatible. When a token prefix is used, we cannot guarantee that. diff --git a/knox/models.py b/knox/models.py index b054f289..80929bcf 100644 --- a/knox/models.py +++ b/knox/models.py @@ -48,7 +48,7 @@ class AbstractAuthToken(models.Model): class Meta: abstract = True - def __str__(self): + def __str__(self) -> str: return '%s : %s' % (self.digest, self.user) diff --git a/knox/settings.py b/knox/settings.py index a5def499..d1b09347 100644 --- a/knox/settings.py +++ b/knox/settings.py @@ -55,4 +55,4 @@ def __setattr__(self, *args, **kwargs): ''') -CONSTANTS = CONSTANTS() +CONSTANTS = CONSTANTS() # type: ignore From d860b416e2877fe66b47f583cd147155e217ee58 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:36:11 +0000 Subject: [PATCH 33/41] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/isort: 5.12.0 → 5.13.2](https://github.com/PyCQA/isort/compare/5.12.0...5.13.2) - [github.com/PyCQA/flake8: 6.1.0 → 7.0.0](https://github.com/PyCQA/flake8/compare/6.1.0...7.0.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5ec1b33f..eac9b90d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,9 @@ repos: - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.0.0 hooks: - id: flake8 \ No newline at end of file From 4015993682a6c903e013686637aaaed956d53063 Mon Sep 17 00:00:00 2001 From: luqmansen Date: Fri, 26 Jan 2024 14:09:19 +0700 Subject: [PATCH 34/41] refactor: use `self.authenticate_header()` to get auth header --- knox/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knox/auth.py b/knox/auth.py index ffb0880b..858ee504 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -29,7 +29,7 @@ class TokenAuthentication(BaseAuthentication): def authenticate(self, request): auth = get_authorization_header(request).split() - prefix = knox_settings.AUTH_HEADER_PREFIX.encode() + prefix = self.authenticate_header(request).encode() if not auth: return None From af386f917b385dc49ebcf58d619625b09d8e3776 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Sun, 17 Dec 2023 19:52:18 +0100 Subject: [PATCH 35/41] chore(migration): use f-string --- knox/models.py | 2 +- tests/tests.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/knox/models.py b/knox/models.py index 80929bcf..32f95ea4 100644 --- a/knox/models.py +++ b/knox/models.py @@ -49,7 +49,7 @@ class Meta: abstract = True def __str__(self) -> str: - return '%s : %s' % (self.digest, self.user) + return f'{self.digest} : {self.user}' class AuthToken(AbstractAuthToken): diff --git a/tests/tests.py b/tests/tests.py index 6fa5ca97..9494db0b 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -23,7 +23,7 @@ def get_basic_auth_header(username, password): return 'Basic %s' % base64.b64encode( - ('%s:%s' % (username, password)).encode('ascii')).decode() + (f'{username}:{password}').encode('ascii')).decode() auto_refresh_knox = knox_settings.defaults.copy() @@ -198,7 +198,7 @@ def test_update_token_key(self): instance, token = AuthToken.objects.create(self.user) rf = APIRequestFactory() request = rf.get('/') - request.META = {'HTTP_AUTHORIZATION': 'Token {}'.format(token)} + request.META = {'HTTP_AUTHORIZATION': f'Token {token}'} (self.user, auth_token) = TokenAuthentication().authenticate(request) self.assertEqual( token[:CONSTANTS.TOKEN_KEY_LENGTH], From 9b341681d1cd74d0e91d62c93953d12033dbc458 Mon Sep 17 00:00:00 2001 From: nbro Date: Mon, 1 Apr 2024 23:08:49 +0200 Subject: [PATCH 36/41] Fix README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8a5ca1d7..eec181ac 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ django-rest-knox [![Jazzband](https://jazzband.co/static/img/badge.svg)](https://jazzband.co/) [![image](https://github.com/jazzband/django-rest-knox/workflows/Test/badge.svg?branch=develop)](https://github.com/jazzband/django-rest-knox/actions) -Authentication Module for django rest auth +Authentication module for Django rest auth. Knox provides easy to use authentication for [Django REST -Framework](https://www.django-rest-framework.org/) The aim is to allow +Framework](https://www.django-rest-framework.org/). The aim is to allow for common patterns in applications that are REST based, with little extra effort; and to ensure that connections remain secure. @@ -44,8 +44,7 @@ More information can be found in the # Run the tests locally -If you need to debug a test locally and if you have [docker](https://www.docker.com/) installed: - +If you need to debug a test locally and if you have [docker](https://www.docker.com/) installed, simply run the ``./docker-run-tests.sh`` script and it will run the test suite in every Python / Django versions. From 7d21736ff60c590c9f0531c7c53b7e2d0f46417e Mon Sep 17 00:00:00 2001 From: Angus Holder Date: Fri, 26 Apr 2024 17:35:10 +0100 Subject: [PATCH 37/41] Avoid importing `django.test` in main code `setting_changed` is actually from `django.core.signals`, and is re-exported from `django.test.signals`. `django.test` takes 20-40ms to import, so not too much, but it's an easy win to avoid this import. --- knox/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knox/settings.py b/knox/settings.py index d1b09347..a2c3d9c8 100644 --- a/knox/settings.py +++ b/knox/settings.py @@ -1,7 +1,7 @@ from datetime import timedelta from django.conf import settings -from django.test.signals import setting_changed +from django.core.signals import setting_changed from rest_framework.settings import APISettings, api_settings USER_SETTINGS = getattr(settings, 'REST_KNOX', None) From cde8a888bff833dca3ee503699c701dfc4de0150 Mon Sep 17 00:00:00 2001 From: Giovanni Cimolin Date: Fri, 3 May 2024 10:33:46 +0200 Subject: [PATCH 38/41] feat: Add action to build and deploy pages to GH-Pages --- .github/workflows/gh-pages.yml | 34 ++++++++++++++++++++++++++++++++++ mkdocs.sh | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/gh-pages.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 00000000..7f3937b6 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,34 @@ +name: Publish Docs to GitHub Pages + +permissions: + contents: write + +on: + push: + branches: + - develop + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.x + + - name: Install dependencies + run: pip install mkdocs-material + + - name: Build docs + run: mkdocs build + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + personal_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./site \ No newline at end of file diff --git a/mkdocs.sh b/mkdocs.sh index ac64849e..8365e1ae 100755 --- a/mkdocs.sh +++ b/mkdocs.sh @@ -9,4 +9,4 @@ docker run --rm -it \ -w $MOUNT_FOLDER \ -p $MKDOCS_DEV_PORT:$MKDOCS_DEV_PORT \ -e MKDOCS_DEV_ADDR="$MKDOCS_DEV_ADDR:$MKDOCS_DEV_PORT" \ - squidfunk/mkdocs-material:3.2.0 $* + squidfunk/mkdocs-material:latest $* From f7eae694191d2a8b9c7dcbb9a88d0738a4036259 Mon Sep 17 00:00:00 2001 From: Calum Young Date: Sat, 4 May 2024 22:02:39 +0100 Subject: [PATCH 39/41] Remove flake8 and isort from tox config --- tox.ini | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/tox.ini b/tox.ini index 3a159a54..aaf77486 100644 --- a/tox.ini +++ b/tox.ini @@ -1,25 +1,9 @@ [tox] envlist = - isort, - flake8, py{36,37,38,39,310}-django32, py{38,39,310,311,312}-django42, py{310,311,312}-django50, -[testenv:flake8] -deps = flake8 -changedir = {toxinidir} -commands = flake8 knox - -[testenv:isort] -deps = isort -changedir = {toxinidir} -commands = isort --check-only --diff \ - knox \ - knox_project/views.py \ - setup.py \ - tests - [testenv] commands = python manage.py migrate @@ -33,7 +17,6 @@ deps = django42: Django>=4.2,<4.3 django50: Django>=5.0,<5.1 markdown>=3.0 - isort>=5.0 djangorestframework freezegun mkdocs @@ -50,5 +33,5 @@ python = 3.8: py38 3.9: py39 3.10: py310 - 3.11: py311, isort, flake8 + 3.11: py311 3.12: py312 From a2edb245c548340e78e175eb56d78d170a88ea94 Mon Sep 17 00:00:00 2001 From: Jonathan Liuti Date: Mon, 1 Jul 2024 15:09:42 +0200 Subject: [PATCH 40/41] Add changelog entries for release 4.3.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8280b3ac..2bd1b34a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 4.3.0 +- Fix migration reverse flow, enable migrate 0 +- Various documentation fixes and improvements +- Drop `cryptography` in favor of hashlib +- Make custom AuthModel work +- Token prefix can be set in the setttings +- Drop support for Django 4.0 +- Add support for Dango 4.2, 5.0 and Python 3.11 and 3.12 +- Cleanup legacy Python 2.0 code +- Fix isort, flake8 usage for Python 3.10 in the test suite +- Update Github actions version +- Upgrade markdown dependency +- Get rid of the `six` library +- Add custom login / logout response support +- Join the jazzband organization +- Add pre-commit hooks +- Add tracking of tests code coverage +- Fix migrations when used in condition with a custom DB +- Improve typing +- Use `self.authenticate_header()` in `authenticate()` method to get auth header prefix + ## 4.2.0 - compatibility with Python up to 3.10 and Django up to 4.0 - integration with github CI instead of travis From eeed21751c6c7492cf1204f3419926a8749e5c31 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Tue, 9 Jul 2024 20:11:08 +0200 Subject: [PATCH 41/41] docs(changelog): bump to 5.0.0, add token warning I've increased the version to major, as this kind of a big change asks for one. Fixes https://github.com/jazzband/django-rest-knox/issues/357 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bd1b34a..c7db5936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ -## 4.3.0 +## 5.0.0 +- Tokens created prior to this release will no longer work - Fix migration reverse flow, enable migrate 0 - Various documentation fixes and improvements - Drop `cryptography` in favor of hashlib