From ba9715af498835d93f657de504d05eb11e89602c Mon Sep 17 00:00:00 2001 From: Moises Rezonzew Date: Sun, 15 Sep 2019 16:06:12 +0300 Subject: [PATCH 1/5] added auto_refresh field to the AuthToken model --- knox/migrations/0008_authtoken_auto_refresh.py | 18 ++++++++++++++++++ knox/models.py | 6 +++--- 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 knox/migrations/0008_authtoken_auto_refresh.py diff --git a/knox/migrations/0008_authtoken_auto_refresh.py b/knox/migrations/0008_authtoken_auto_refresh.py new file mode 100644 index 00000000..927a7674 --- /dev/null +++ b/knox/migrations/0008_authtoken_auto_refresh.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.5 on 2019-09-15 11:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('knox', '0007_auto_20190111_0542'), + ] + + operations = [ + migrations.AddField( + model_name='authtoken', + name='auto_refresh', + field=models.BooleanField(null=True), + ), + ] diff --git a/knox/models.py b/knox/models.py index 39910ec4..3a84c270 100644 --- a/knox/models.py +++ b/knox/models.py @@ -9,7 +9,7 @@ class AuthTokenManager(models.Manager): - def create(self, user, expiry=knox_settings.TOKEN_TTL): + def create(self, user, expiry=knox_settings.TOKEN_TTL, auto_refresh=None): token = crypto.create_token_string() salt = crypto.create_salt_string() digest = crypto.hash_token(token, salt) @@ -19,12 +19,11 @@ def create(self, user, expiry=knox_settings.TOKEN_TTL): instance = super(AuthTokenManager, self).create( token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH], digest=digest, - salt=salt, user=user, expiry=expiry) + salt=salt, user=user, expiry=expiry, auto_refresh=auto_refresh) return instance, token class AuthToken(models.Model): - objects = AuthTokenManager() digest = models.CharField( @@ -37,6 +36,7 @@ class AuthToken(models.Model): related_name='auth_token_set', on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) expiry = models.DateTimeField(null=True, blank=True) + auto_refresh = models.BooleanField(null=True) def __str__(self): return '%s : %s' % (self.digest, self.user) From d1d59e856e7233bf073c0f6f5b83753029d7875b Mon Sep 17 00:00:00 2001 From: Moises Rezonzew Date: Sun, 15 Sep 2019 16:07:42 +0300 Subject: [PATCH 2/5] added the auto_refresh per token logic --- knox/auth.py | 5 +++-- tests/tests.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/knox/auth.py b/knox/auth.py index ffb5a287..f491f837 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -72,8 +72,9 @@ def authenticate_credentials(self, 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) + if auth_token.expiry: + if knox_settings.AUTO_REFRESH and auth_token.auto_refresh is None or auth_token.auto_refresh: + self.renew_token(auth_token) return self.validate_user(auth_token) raise exceptions.AuthenticationFailed(msg) diff --git a/tests/tests.py b/tests/tests.py index ffe6f2d8..b53def38 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -287,6 +287,62 @@ def test_token_expiry_is_not_extended_within_MIN_REFRESH_INTERVAL(self): self.assertEqual(response.status_code, 200) self.assertEqual(original_expiry, AuthToken.objects.get().expiry) + def test_token_expiry_is_extended_with_global_auto_refresh_deativated_and_specific_token_activated(self): + self.assertEqual(knox_settings.AUTO_REFRESH, False) + self.assertEqual(knox_settings.TOKEN_TTL, timedelta(hours=10)) + + ttl = knox_settings.TOKEN_TTL + original_time = datetime(2018, 7, 25, 0, 0, 0, 0) + + with freeze_time(original_time): + instance, token = AuthToken.objects.create(user=self.user,auto_refresh=True) + + self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) + five_hours_later = original_time + timedelta(hours=5) + with freeze_time(five_hours_later): + response = self.client.get(root_url, {}, format='json') + reload_module(auth) + self.assertEqual(response.status_code, 200) + + # original expiry date was extended: + new_expiry = AuthToken.objects.get().expiry + expected_expiry = original_time + ttl + timedelta(hours=5) + self.assertEqual(new_expiry.replace(tzinfo=None), expected_expiry, + "Expiry time should have been extended to {} but is {}." + .format(expected_expiry, new_expiry)) + + # token works after original expiry: + after_original_expiry = original_time + ttl + timedelta(hours=1) + with freeze_time(after_original_expiry): + response = self.client.get(root_url, {}, format='json') + self.assertEqual(response.status_code, 200) + + # token does not work after new expiry: + new_expiry = AuthToken.objects.get().expiry + with freeze_time(new_expiry + timedelta(seconds=1)): + response = self.client.get(root_url, {}, format='json') + self.assertEqual(response.status_code, 401) + + def test_token_expiry_is_not_extended_with_global_auto_refresh_ativated_and_specific_token_deactivated(self): + self.assertEqual(knox_settings.AUTO_REFRESH, False) + self.assertEqual(knox_settings.TOKEN_TTL, timedelta(hours=10)) + + now = datetime.now() + with freeze_time(now): + instance, token = AuthToken.objects.create(user=self.user,auto_refresh=False) + + original_expiry = AuthToken.objects.get().expiry + + self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) + with override_settings(REST_KNOX=auto_refresh_knox): + reload_module(auth) # necessary to reload settings in core code + with freeze_time(now + timedelta(hours=1)): + response = self.client.get(root_url, {}, format='json') + reload_module(auth) # necessary to reload settings in core code + + self.assertEqual(response.status_code, 200) + self.assertEqual(original_expiry, AuthToken.objects.get().expiry) + def test_expiry_signals(self): self.signal_was_called = False From 5ae55d7045206a89f5f13047a919c1cb48758ddf Mon Sep 17 00:00:00 2001 From: Moises Rezonzew Date: Sun, 15 Sep 2019 16:10:37 +0300 Subject: [PATCH 3/5] upgrade version to 4.2.0 --- CHANGELOG.md | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91e14522..d9e2ecec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.2.0 + +- Auto refresh now configurable per token in order to override global `AUTO_REFRESH` setting + ## 4.1.0 - Expiry format now defaults to whatever is used Django REST framework diff --git a/setup.py b/setup.py index 5e6ae73a..ecfbed3b 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,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='4.1.0', + version='4.2.0', description='Authentication for django rest framework', long_description=long_description, long_description_content_type='text/markdown', From e9717a7cbf086621debd1781a967fc5056de03c3 Mon Sep 17 00:00:00 2001 From: Moises Rezonzew Date: Sun, 15 Sep 2019 16:35:35 +0300 Subject: [PATCH 4/5] fix linting --- knox/auth.py | 7 ++++--- tests/tests.py | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/knox/auth.py b/knox/auth.py index f491f837..ab097842 100644 --- a/knox/auth.py +++ b/knox/auth.py @@ -72,9 +72,10 @@ def authenticate_credentials(self, token): except (TypeError, binascii.Error): raise exceptions.AuthenticationFailed(msg) if compare_digest(digest, auth_token.digest): - if auth_token.expiry: - if knox_settings.AUTO_REFRESH and auth_token.auto_refresh is None or auth_token.auto_refresh: - self.renew_token(auth_token) + if auth_token.expiry and (knox_settings.AUTO_REFRESH and + auth_token.auto_refresh is None + or auth_token.auto_refresh): + self.renew_token(auth_token) return self.validate_user(auth_token) raise exceptions.AuthenticationFailed(msg) diff --git a/tests/tests.py b/tests/tests.py index b53def38..2ac71528 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -287,7 +287,7 @@ def test_token_expiry_is_not_extended_within_MIN_REFRESH_INTERVAL(self): self.assertEqual(response.status_code, 200) self.assertEqual(original_expiry, AuthToken.objects.get().expiry) - def test_token_expiry_is_extended_with_global_auto_refresh_deativated_and_specific_token_activated(self): + def test_token_expiry_is_extended_for_token_with_auto_refresh_deativated(self): self.assertEqual(knox_settings.AUTO_REFRESH, False) self.assertEqual(knox_settings.TOKEN_TTL, timedelta(hours=10)) @@ -295,7 +295,7 @@ def test_token_expiry_is_extended_with_global_auto_refresh_deativated_and_specif original_time = datetime(2018, 7, 25, 0, 0, 0, 0) with freeze_time(original_time): - instance, token = AuthToken.objects.create(user=self.user,auto_refresh=True) + instance, token = AuthToken.objects.create(user=self.user, auto_refresh=True) self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % token)) five_hours_later = original_time + timedelta(hours=5) @@ -323,13 +323,13 @@ def test_token_expiry_is_extended_with_global_auto_refresh_deativated_and_specif response = self.client.get(root_url, {}, format='json') self.assertEqual(response.status_code, 401) - def test_token_expiry_is_not_extended_with_global_auto_refresh_ativated_and_specific_token_deactivated(self): + def test_token_expiry_is_not_extended_for_token_with_auto_refresh_activated(self): self.assertEqual(knox_settings.AUTO_REFRESH, False) self.assertEqual(knox_settings.TOKEN_TTL, timedelta(hours=10)) now = datetime.now() with freeze_time(now): - instance, token = AuthToken.objects.create(user=self.user,auto_refresh=False) + instance, token = AuthToken.objects.create(user=self.user, auto_refresh=False) original_expiry = AuthToken.objects.get().expiry From d848a97bc9d1391f450981555fa1ba7775837b2b Mon Sep 17 00:00:00 2001 From: Moises Rezonzew Date: Sun, 15 Sep 2019 16:43:05 +0300 Subject: [PATCH 5/5] modified field to NullBooleanField --- knox/migrations/0008_authtoken_auto_refresh.py | 4 ++-- knox/models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/knox/migrations/0008_authtoken_auto_refresh.py b/knox/migrations/0008_authtoken_auto_refresh.py index 927a7674..8a23ed34 100644 --- a/knox/migrations/0008_authtoken_auto_refresh.py +++ b/knox/migrations/0008_authtoken_auto_refresh.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.5 on 2019-09-15 11:22 +# Generated by Django 2.2.5 on 2019-09-15 13:41 from django.db import migrations, models @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='authtoken', name='auto_refresh', - field=models.BooleanField(null=True), + field=models.NullBooleanField(), ), ] diff --git a/knox/models.py b/knox/models.py index 3a84c270..4b4b653a 100644 --- a/knox/models.py +++ b/knox/models.py @@ -36,7 +36,7 @@ class AuthToken(models.Model): related_name='auth_token_set', on_delete=models.CASCADE) created = models.DateTimeField(auto_now_add=True) expiry = models.DateTimeField(null=True, blank=True) - auto_refresh = models.BooleanField(null=True) + auto_refresh = models.NullBooleanField(null=True) def __str__(self): return '%s : %s' % (self.digest, self.user)