Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.0.1

- Fix for tox config to build Django 2.2 on python 3.6

## 4.0.0

**BREAKING** This is a major release version because it
Expand Down
10 changes: 10 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ Example `settings.py`
#...snip...
# These are the default values if none are set
from datetime import timedelta
from rest_framework.settings import api_settings
REST_KNOX = {
'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512',
'AUTH_TOKEN_CHARACTER_LENGTH': 64,
'TOKEN_TTL': timedelta(hours=10),
'USER_SERIALIZER': 'knox.serializers.UserSerializer',
'TOKEN_LIMIT_PER_USER': None,
'AUTO_REFRESH': False,
'EXPIRY_DATETIME_FORMAT': api_settings.DATETME_FORMAT,
}
#...snip...
```
Expand Down Expand Up @@ -74,6 +76,14 @@ in the database.
## AUTH_HEADER_PREFIX
This is the Authorization header value prefix. The default is `Token`

## EXPIRY_DATETIME_FORMAT
This is the expiry datetime format returned in the login view. The default is the
[DATETIME_FORMAT][DATETIME_FORMAT] of Django REST framework. May be any of `None`, `iso-8601`
or a Python [strftime format][strftime format] string.

[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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 very nice!


# 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
Expand Down
33 changes: 28 additions & 5 deletions docs/views.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,36 @@ default, you can extend this class to provide your own value for

It is possible to customize LoginView behaviour by overriding the following
helper methods:
- `get_context`, to change the context passed to the `UserSerializer`
- `get_token_ttl`, to change the token ttl
- `get_token_limit_per_user`, to change the number of tokens available for a user
- `get_user_serializer_class`, to change the class used for serializing the user
- `get_context(self)`, to change the context passed to the `UserSerializer`
- `get_token_ttl(self)`, to change the token ttl
- `get_token_limit_per_user(self)`, to change the number of tokens available for a user
- `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 convinience

Finally, if none of these helper methods are sufficient, you can also override `get_post_response_data`
to return a fully customized payload.

```python
...snip...
def get_post_response_data(self, request, token, instance):
UserSerializer = self.get_user_serializer_class()

data = {
'expiry': self.format_expiry_datetime(instance.expiry),
'token': token
}
if UserSerializer is not None:
data["user"] = UserSerializer(
request.user,
context=self.get_context()
).data
return data
...snip...
```

---
When the endpoint authenticates a request, a json object will be returned
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.
The success response also includes a `expiry` key with a timestamp for when
the token expires.
Expand Down
3 changes: 2 additions & 1 deletion knox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from django.conf import settings
from django.test.signals import setting_changed
from rest_framework.settings import APISettings
from rest_framework.settings import APISettings, api_settings

USER_SETTINGS = getattr(settings, 'REST_KNOX', None)

Expand All @@ -15,6 +15,7 @@
'AUTO_REFRESH': False,
'MIN_REFRESH_INTERVAL': 60,
'AUTH_HEADER_PREFIX': 'Token',
'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT,
}

IMPORT_STRINGS = {
Expand Down
34 changes: 23 additions & 11 deletions knox/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.serializers import DateTimeField
from rest_framework.settings import api_settings
from rest_framework.views import APIView

Expand All @@ -27,6 +28,27 @@ def get_token_limit_per_user(self):
def get_user_serializer_class(self):
return knox_settings.USER_SERIALIZER

def get_expiry_datetime_format(self):
return knox_settings.EXPIRY_DATETIME_FORMAT

def format_expiry_datetime(self, expiry):
datetime_format = self.get_expiry_datetime_format()
return DateTimeField(format=datetime_format).to_representation(expiry)

def get_post_response_data(self, request, token, instance):
UserSerializer = self.get_user_serializer_class()

data = {
'expiry': self.format_expiry_datetime(instance.expiry),
'token': token
}
if UserSerializer is not None:
data["user"] = UserSerializer(
request.user,
context=self.get_context()
).data
return 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:
Expand All @@ -41,17 +63,7 @@ def post(self, request, format=None):
instance, token = AuthToken.objects.create(request.user, token_ttl)
user_logged_in.send(sender=request.user.__class__,
request=request, user=request.user)
UserSerializer = self.get_user_serializer_class()

data = {
'expiry': instance.expiry,
'token': token
}
if UserSerializer is not None:
data["user"] = UserSerializer(
request.user,
context=self.get_context()
).data
data = self.get_post_response_data(request, token, instance)
return Response(data)


Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.0.0',
version='4.0.1',
description='Authentication for django rest framework',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down
32 changes: 31 additions & 1 deletion tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.test import override_settings
from django.utils.six.moves import reload_module
from freezegun import freeze_time
from rest_framework.serializers import DateTimeField
from rest_framework.test import APIRequestFactory, APITestCase as TestCase

from knox import auth, views
Expand Down Expand Up @@ -45,6 +46,10 @@ def get_basic_auth_header(username, password):
token_no_expiration_knox = knox_settings.defaults.copy()
token_no_expiration_knox["TOKEN_TTL"] = None

EXPIRY_DATETIME_FORMAT = '%H:%M %d/%m/%y'
expiry_datetime_format_knox = knox_settings.defaults.copy()
expiry_datetime_format_knox["EXPIRY_DATETIME_FORMAT"] = EXPIRY_DATETIME_FORMAT


class AuthTestCase(TestCase):

Expand Down Expand Up @@ -101,6 +106,31 @@ def test_login_returns_serialized_token_and_username_field(self):
self.assertIn('user', response.data)
self.assertIn(username_field, response.data['user'])

def test_login_returns_configured_expiry_datetime_format(self):

with override_settings(REST_KNOX=expiry_datetime_format_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(
expiry_datetime_format_knox["EXPIRY_DATETIME_FORMAT"],
EXPIRY_DATETIME_FORMAT
)
reload_module(views)
self.assertEqual(response.status_code, 200)
self.assertIn('token', response.data)
self.assertNotIn('user', response.data)
self.assertEqual(
response.data['expiry'],
DateTimeField(format=EXPIRY_DATETIME_FORMAT).to_representation(
AuthToken.objects.first().expiry
)
)

def test_logout_deletes_keys(self):
self.assertEqual(AuthToken.objects.count(), 0)
for _ in range(2):
Expand Down Expand Up @@ -364,5 +394,5 @@ def test_expiry_is_present(self):
self.assertIn('expiry', response.data)
self.assertEqual(
response.data['expiry'],
AuthToken.objects.first().expiry
DateTimeField().to_representation(AuthToken.objects.first().expiry)
)