Merge pull request #28 from mjlabe/logout-blacklist-jwt-token

Refresh token not blacklisted on logout
This commit is contained in:
Michael 2020-04-09 21:03:07 -05:00 committed by GitHub
commit 275d1c4952
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 2 deletions

3
.gitignore vendored
View File

@ -72,6 +72,9 @@ target/
# Jupyter Notebook
.ipynb_checkpoints
# IDE
.idea
# pyenv
.python-version

View File

@ -44,7 +44,12 @@ REST_USE_JWT = True
JWT_AUTH_COOKIE = 'jwt-auth'
```
### Testing
To run the tests within a virtualenv, run `python runtests.py` from the repository directory.
The easiest way to run test coverage is with [`coverage`](https://pypi.org/project/coverage/),
which runs the tests against all supported Django installs. To run the test coverage
within a virtualenv, run `coverage run ./runtests.py` from the repository directory then run `coverage report`.
### Documentation

View File

@ -94,6 +94,8 @@ INSTALLED_APPS = [
'dj_rest_auth',
'dj_rest_auth.registration',
'rest_framework_simplejwt.token_blacklist'
]
SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd"

View File

@ -1,6 +1,6 @@
import json
from allauth.account import app_settings as account_app_settings
from dj_rest_auth.registration.app_settings import register_permission_classes
from dj_rest_auth.registration.views import RegisterView
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core import mail
@ -9,6 +9,8 @@ from django.utils.encoding import force_text
from rest_framework import status
from rest_framework.test import APIRequestFactory
from dj_rest_auth.registration.app_settings import register_permission_classes
from dj_rest_auth.registration.views import RegisterView
from .mixins import CustomPermissionClass, TestsMixin
try:
@ -555,3 +557,44 @@ class APIBasicTests(TestsMixin, TestCase):
self.assertEqual(['jwt-auth'], list(resp.cookies.keys()))
resp = self.get('/protected-view/')
self.assertEquals(resp.status_code, 200)
@override_settings(REST_USE_JWT=True)
def test_blacklisting_not_installed(self):
settings.INSTALLED_APPS.remove('rest_framework_simplejwt.token_blacklist')
payload = {
"username": self.USERNAME,
"password": self.PASS
}
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
resp = self.post(self.login_url, data=payload, status_code=200)
token = resp.data['refresh_token']
resp = self.post(self.logout_url, status=200, data={'refresh': token})
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data["detail"],
"Neither cookies or blacklist are enabled, so the token has not been deleted server side. "
"Please make sure the token is deleted client side.")
@override_settings(REST_USE_JWT=True)
def test_blacklisting(self):
payload = {
"username": self.USERNAME,
"password": self.PASS
}
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
resp = self.post(self.login_url, data=payload, status_code=200)
token = resp.data['refresh_token']
# test refresh token not included in request data
resp = self.post(self.logout_url, status=200)
self.assertEqual(resp.status_code, 401)
# test token is invalid or expired
resp = self.post(self.logout_url, status=200, data={'refresh': '1'})
self.assertEqual(resp.status_code, 401)
# test successful logout
resp = self.post(self.logout_url, status=200, data={'refresh': token})
self.assertEqual(resp.status_code, 200)
# test token is blacklisted
resp = self.post(self.logout_url, status=200, data={'refresh': token})
self.assertEqual(resp.status_code, 401)
# test other TokenError, AttributeError, TypeError (invalid format)
resp = self.post(self.logout_url, status=200, data=json.dumps({'refresh': token}))
self.assertEqual(resp.status_code, 500)

View File

@ -11,6 +11,8 @@ from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.exceptions import TokenError
from rest_framework_simplejwt.tokens import RefreshToken
from .app_settings import (JWTSerializer, LoginSerializer,
PasswordChangeSerializer,
@ -132,15 +134,48 @@ class LogoutView(APIView):
request.user.auth_token.delete()
except (AttributeError, ObjectDoesNotExist):
pass
if getattr(settings, 'REST_SESSION_LOGIN', True):
django_logout(request)
response = Response({"detail": _("Successfully logged out.")},
status=status.HTTP_200_OK)
if getattr(settings, 'REST_USE_JWT', False):
cookie_name = getattr(settings, 'JWT_AUTH_COOKIE', None)
if cookie_name:
response.delete_cookie(cookie_name)
elif 'rest_framework_simplejwt.token_blacklist' in settings.INSTALLED_APPS:
# add refresh token to blacklist
try:
token = RefreshToken(request.data['refresh'])
token.blacklist()
except KeyError:
response = Response({"detail": _("Refresh token was not included in request data.")},
status=status.HTTP_401_UNAUTHORIZED)
except (TokenError, AttributeError, TypeError) as error:
if hasattr(error, 'args'):
if 'Token is blacklisted' in error.args or 'Token is invalid or expired' in error.args:
response = Response({"detail": _(error.args[0])},
status=status.HTTP_401_UNAUTHORIZED)
else:
response = Response({"detail": _("An error has occurred.")},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
response = Response({"detail": _("An error has occurred.")},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
response = Response({
"detail": _("Neither cookies or blacklist are enabled, so the token has not been deleted server "
"side. Please make sure the token is deleted client side."
)}, status=status.HTTP_200_OK)
return response