From 7570615b6faaa741b1b5e5b0a76965047a2dd4c4 Mon Sep 17 00:00:00 2001 From: Niclas Date: Thu, 9 Aug 2018 05:18:40 +0200 Subject: [PATCH 01/10] fix: UserSerializer to be None by default. --- docs/changes.md | 4 ++++ docs/views.md | 16 ++++++++++------ knox/serializers.py | 2 +- knox/settings.py | 2 +- knox/views.py | 4 ++++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/changes.md b/docs/changes.md index ed716a97..fc349bc2 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,5 +1,9 @@ #Changelog +## 3.x.x +- **Breaking changes**: Successful authentication **ONLY** returns `Token` object by default now. +`USER_SERIALIZER` must be overridden to return custom data. + ## 3.2.0 - Introduce new setting AUTO_REFRESH for controlling if token expiry time should be extended automatically diff --git a/docs/views.md b/docs/views.md index 574d5b67..9dbdb521 100644 --- a/docs/views.md +++ b/docs/views.md @@ -13,13 +13,17 @@ schemes. If you would like to use a different authentication scheme to the default, you can extend this class to provide your own value for `authentication_classes` -When it receives an authenticated request, it will return json -- `user` an object representing the user that was authenticated -- `token` the token that should be used for any token +--- +When the endpoint authenticates a request, a json object will be returned +containing the `token` key along with the actual value for the key by default. + +> *This is because `USER_SERIALIZER` setting is `None` by default.* + +If you wish to return custom data upon successful authentication +like `first_name`, `last_name`, and `username` then the included `UserSerializer` +class can be used inside `REST_KNOX` settings by adding `knox.serializers.UserSerializer` +--- -The returned `user` object is serialized using the `USER_SERIALIZER` setting. -If this setting is not changed, the default serializer returns the user's -`first_name`, `last_name` and `username`. Obviously, if your app uses a custom user model that does not have these fields, a custom serializer must be used. diff --git a/knox/serializers.py b/knox/serializers.py index 9c77897b..8639309a 100644 --- a/knox/serializers.py +++ b/knox/serializers.py @@ -9,4 +9,4 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = (username_field, 'first_name', 'last_name',) + fields = (username_field,) diff --git a/knox/settings.py b/knox/settings.py index 9da088ef..30e3fa3a 100644 --- a/knox/settings.py +++ b/knox/settings.py @@ -9,7 +9,7 @@ 'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512', 'AUTH_TOKEN_CHARACTER_LENGTH': 64, 'TOKEN_TTL': timedelta(hours=10), - 'USER_SERIALIZER': 'knox.serializers.UserSerializer', + 'USER_SERIALIZER': None, 'AUTO_REFRESH': False, } diff --git a/knox/views.py b/knox/views.py index 655647ef..e2532757 100644 --- a/knox/views.py +++ b/knox/views.py @@ -19,6 +19,10 @@ def post(self, request, format=None): user_logged_in.send(sender=request.user.__class__, request=request, user=request.user) UserSerializer = knox_settings.USER_SERIALIZER context = {'request': self.request, 'format': self.format_kwarg, 'view': self} + if UserSerializer is None: + return Response( + {'token': token} + ) return Response({ 'user': UserSerializer(request.user, context=context).data, 'token': token, From 17ba4bc1e5c5c5762ad2758a093952e4075998d4 Mon Sep 17 00:00:00 2001 From: Niclas Date: Fri, 10 Aug 2018 09:22:38 +0200 Subject: [PATCH 02/10] added tests --- tests/tests.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 180c0096..96efff2b 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -22,6 +22,7 @@ from knox.auth import TokenAuthentication from knox.models import AuthToken from knox.settings import CONSTANTS, knox_settings +from knox.serializers import UserSerializer User = get_user_model() root_url = reverse('api-root') @@ -55,6 +56,34 @@ def test_login_creates_keys(self): self.assertEqual(AuthToken.objects.count(), 5) self.assertTrue(all(e.token_key for e in AuthToken.objects.all())) + def test_login_returns_serialized_token(self): + 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.assertEqual(knox_settings.USER_SERIALIZER, None) + self.assertIn('token', response.data) + username_field = self.user.USERNAME_FIELD + self.assertNotIn(username_field, response.data) + + def test_login_returns_serialized_token_and_username_field(self): + self.assertEqual(AuthToken.objects.count(), 0) + url = reverse('knox_login') + self.client.credentials( + HTTP_AUTHORIZATION=get_basic_auth_header(self.username, self.password) + ) + knox_settings.USER_SERIALIZER = UserSerializer + response = self.client.post(url, {}, format='json') + self.assertEqual(response.status_code, 200) + self.assertNotEqual(knox_settings.USER_SERIALIZER, None) + self.assertIn('token', response.data) + username_field = self.user.USERNAME_FIELD + self.assertIn('user', response.data) + self.assertIn(username_field, response.data['user']) + def test_logout_deletes_keys(self): self.assertEqual(AuthToken.objects.count(), 0) for _ in range(2): @@ -203,4 +232,4 @@ def test_token_expiry_is_not_extended_within_MIN_REFRESH_INTERVAL(self): response = self.client.get(root_url, {}, format='json') self.assertEqual(response.status_code, 200) - self.assertEqual(original_expiry, AuthToken.objects.get().expires) + self.assertEqual(original_expiry, AuthToken.objects.get().expires) \ No newline at end of file From f572860d85c5235618fc794bcc99a51dab4ea4b0 Mon Sep 17 00:00:00 2001 From: Niclas Date: Mon, 20 Aug 2018 13:02:16 +0200 Subject: [PATCH 03/10] prepare for release 3.3.0 --- CHANGELOG.rst | 5 +++++ docs/changes.md | 5 ++--- setup.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7338bc99..5dd5067d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +###### +3.3.0 +###### +- **Breaking changes**: Successful authentication **ONLY** returns `Token` object by default now. `USER_SERIALIZER` must be overridden to return custom data. This also removes hardcoded user fields from `UserSerializer` class to allow more generic behaivor by default. + ###### 3.2.0 ###### diff --git a/docs/changes.md b/docs/changes.md index fc349bc2..a4214054 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,8 +1,7 @@ #Changelog -## 3.x.x -- **Breaking changes**: Successful authentication **ONLY** returns `Token` object by default now. -`USER_SERIALIZER` must be overridden to return custom data. +## 3.3.0 +- **Breaking changes**: Successful authentication **ONLY** returns `Token` object by default now. `USER_SERIALIZER` must be overridden to return custom data. This also removes hardcoded user fields from `UserSerializer` class to allow more generic behaivor by default. ## 3.2.0 - Introduce new setting AUTO_REFRESH for controlling if token expiry time should be extended automatically diff --git a/setup.py b/setup.py index 63895a8f..1d5d0f4d 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.2.0', + version='3.3.0', description='Authentication for django rest framework', long_description=long_description, From 6269b0696f6051f0883c8569eb7aab58d8e196c8 Mon Sep 17 00:00:00 2001 From: James McMahon Date: Wed, 26 Sep 2018 15:40:52 +0100 Subject: [PATCH 04/10] signal tests --- tests/tests.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 6b49b6a8..b04f8559 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -21,6 +21,7 @@ ) from knox.auth import TokenAuthentication +from knox.signals import token_expired from knox.models import AuthToken from knox.settings import CONSTANTS, knox_settings from knox.serializers import UserSerializer @@ -237,4 +238,19 @@ def test_token_expiry_is_not_extended_within_MIN_REFRESH_INTERVAL(self): reload_module(auth) # necessary to reload settings in core code self.assertEqual(response.status_code, 200) - self.assertEqual(original_expiry, AuthToken.objects.get().expires) \ No newline at end of file + self.assertEqual(original_expiry, AuthToken.objects.get().expires) + + def test_expiry_signals(self): + self.signal_was_called = False + + def handler(sender, username, **kwargs): + self.signal_was_called = True + + token_expired.connect(handler) + + token = AuthToken.objects.create(user=self.user, expires=timedelta(0)) + self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) + response = self.client.post(root_url, {}, format='json') + + self.assertTrue(self.signal_was_called) + From 7abaad69289e7fc244ab6539e91e978bd1dfec34 Mon Sep 17 00:00:00 2001 From: James McMahon Date: Wed, 26 Sep 2018 15:49:13 +0100 Subject: [PATCH 05/10] signal when token expires --- knox/auth.py | 3 +++ knox/signals.py | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 knox/signals.py diff --git a/knox/auth.py b/knox/auth.py index c616c29d..9bdc736c 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -19,6 +19,7 @@ def compare_digest(a, b): from knox.crypto import hash_token from knox.models import AuthToken from knox.settings import CONSTANTS, knox_settings +from knox.signals import token_expired User = settings.AUTH_USER_MODEL @@ -101,7 +102,9 @@ def _cleanup_token(self, auth_token): other_token.delete() if auth_token.expires is not None: if auth_token.expires < timezone.now(): + username = auth_token.user.username auth_token.delete() + token_expired.send(sender=self.__class__, username=username) return True return False diff --git a/knox/signals.py b/knox/signals.py new file mode 100644 index 00000000..8f51ad0a --- /dev/null +++ b/knox/signals.py @@ -0,0 +1,3 @@ +import django.dispatch + +token_expired = django.dispatch.Signal(providing_args=["username"]) From 9c87972cad14a433663013ed545793c84678a458 Mon Sep 17 00:00:00 2001 From: James McMahon Date: Wed, 26 Sep 2018 15:59:28 +0100 Subject: [PATCH 06/10] some tweaks --- tests/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/tests.py b/tests/tests.py index b04f8559..92e387a8 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -242,12 +242,12 @@ def test_token_expiry_is_not_extended_within_MIN_REFRESH_INTERVAL(self): def test_expiry_signals(self): self.signal_was_called = False - + def handler(sender, username, **kwargs): self.signal_was_called = True - + token_expired.connect(handler) - + token = AuthToken.objects.create(user=self.user, expires=timedelta(0)) self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) response = self.client.post(root_url, {}, format='json') From 7f571b034f35071e94ee5e427e72ae4ebfa33ea0 Mon Sep 17 00:00:00 2001 From: James McMahon Date: Wed, 26 Sep 2018 16:23:46 +0100 Subject: [PATCH 07/10] fix for borked Markdown package --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 4c395061..d76d1fcf 100644 --- a/tox.ini +++ b/tox.ini @@ -14,6 +14,7 @@ deps = django111: Django>=1.11,<1.12 django20: Django>=2.0,<2.1 django-nose + markdown<3.0 djangorestframework flake8 freezegun From fb42a738402ca7baaee5c7f9bb8116796cf0a358 Mon Sep 17 00:00:00 2001 From: James McMahon Date: Wed, 26 Sep 2018 16:32:44 +0100 Subject: [PATCH 08/10] include source for where the token expired from --- knox/auth.py | 4 +++- knox/signals.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/knox/auth.py b/knox/auth.py index 9bdc736c..0886cb38 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -100,11 +100,13 @@ def _cleanup_token(self, auth_token): if other_token.digest != auth_token.digest and other_token.expires is not None: if other_token.expires < timezone.now(): other_token.delete() + username = other_token.user.username + token_expired.send(sender=self.__class__, username=username, source="other_token") if auth_token.expires is not None: if auth_token.expires < timezone.now(): username = auth_token.user.username auth_token.delete() - token_expired.send(sender=self.__class__, username=username) + token_expired.send(sender=self.__class__, username=username, source="auth_token") return True return False diff --git a/knox/signals.py b/knox/signals.py index 8f51ad0a..ad8a7538 100644 --- a/knox/signals.py +++ b/knox/signals.py @@ -1,3 +1,3 @@ import django.dispatch -token_expired = django.dispatch.Signal(providing_args=["username"]) +token_expired = django.dispatch.Signal(providing_args=["username", "source"]) From 801912f22cfb2b07145b055a2cf40494e44ece16 Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Wed, 26 Sep 2018 16:59:39 +0200 Subject: [PATCH 09/10] Release 3.3.1: Support for Python 3.7 and django 2.1 - Add tox+travis definitions for building rest-knox also on django 2.1 up to python 3.7. - Add changelog for this release --- .travis.yml | 6 ++++-- CHANGELOG.rst | 6 ++++++ docs/changes.md | 3 +++ setup.py | 4 +++- tox.ini | 4 +++- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7b461a05..8b3ca442 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,11 @@ matrix: - python: "3.4" env: TOX_ENVS=py34-django111,py34-django20 - python: "3.5" - env: TOX_ENVS=py35-django111,py35-django20 + env: TOX_ENVS=py35-django111,py35-django20,py35-django20,py35-django21 - python: "3.6" - env: TOX_ENVS=py36-django111,py36-django20 + env: TOX_ENVS=py36-django111,py36-django20,py36-django21 + - python: "3.7" + env: TOX_ENVS=py37-django20,py37-django21 script: - tox -e $TOX_ENVS diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5b619130..2ec24fa9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +###### +3.3.1 +###### +- Ensure compatibility with Django 2.1 up to Python 3.7 + ###### 3.3.0 ###### @@ -6,6 +11,7 @@ ###### 3.2.1 +###### - Fix !111: Avoid knox failing if settings are not overwritten ###### diff --git a/docs/changes.md b/docs/changes.md index 95678dab..05cec71a 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,5 +1,8 @@ #Changelog +## 3.3.1 +- Ensure compatibility with Django 2.1 up to Python 3.7 + ## 3.3.0 - **Breaking changes**: Successful authentication **ONLY** returns `Token` object by default now. `USER_SERIALIZER` must be overridden to return more data. diff --git a/setup.py b/setup.py index 03456f02..355af8db 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='3.3.0', + version='3.3.1', description='Authentication for django rest framework', long_description=long_description, @@ -49,6 +49,8 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], # What does your project relate to? diff --git a/tox.ini b/tox.ini index d76d1fcf..8912df8d 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,7 @@ envlist = py{27,34,35,36}-django111, py{34,35,36}-django20, + py{35,36,37}-django21, [testenv] commands = @@ -11,8 +12,9 @@ setenv = DJANGO_SETTINGS_MODULE = knox_project.settings PIP_INDEX_URL = https://pypi.python.org/simple/ deps = - django111: Django>=1.11,<1.12 + django111: Django>=1.11,<2.0 django20: Django>=2.0,<2.1 + django21: Django>=2.1,<2.2 django-nose markdown<3.0 djangorestframework From 1759565939cf3885baf63232270237b87a95f1a1 Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Thu, 27 Sep 2018 11:16:07 +0200 Subject: [PATCH 10/10] Add matrix for python versions cause travis does not find python 3.7 Seems to be the recommended fix according to https://github.com/travis-ci/travis-ci/issues/9815 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8b3ca442..f3dfef13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ matrix: env: TOX_ENVS=py36-django111,py36-django20,py36-django21 - python: "3.7" env: TOX_ENVS=py37-django20,py37-django21 + dist: xenial + sudo: true script: - tox -e $TOX_ENVS