From 5a751e241ca14b8db6a53decc79b37327d457d6d Mon Sep 17 00:00:00 2001 From: cbomprezzi Date: Mon, 15 Jun 2020 17:12:41 +0200 Subject: [PATCH 01/17] add secure and samesite jwt cookie support --- .gitignore | 5 ++++- dj_rest_auth/views.py | 7 ++++++- docs/configuration.rst | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 136132c..886268d 100644 --- a/.gitignore +++ b/.gitignore @@ -106,4 +106,7 @@ venv.bak/ # mypy .mypy_cache/ demo/react-spa/node_modules/ -demo/react-spa/yarn.lock \ No newline at end of file +demo/react-spa/yarn.lock + +# Visual Studio Code +.vscode/ diff --git a/dj_rest_auth/views.py b/dj_rest_auth/views.py index dc3dea8..6387de6 100644 --- a/dj_rest_auth/views.py +++ b/dj_rest_auth/views.py @@ -86,6 +86,9 @@ class LoginView(GenericAPIView): response = Response(serializer.data, status=status.HTTP_200_OK) if getattr(settings, 'REST_USE_JWT', False): cookie_name = getattr(settings, 'JWT_AUTH_COOKIE', None) + cookie_secure = getattr(settings, 'JWT_AUTH_SECURE', False) + cookie_samesite = getattr(settings, 'JWT_AUTH_HTTPONLY', True) + cookie_samesite = getattr(settings, 'JWT_AUTH_SAMESITE', 'Lax') from rest_framework_simplejwt.settings import api_settings as jwt_settings if cookie_name: from datetime import datetime @@ -94,7 +97,9 @@ class LoginView(GenericAPIView): cookie_name, self.access_token, expires=expiration, - httponly=True + secure=cookie_secure, + httponly=True, + samesite=cookie_samesite ) return response diff --git a/docs/configuration.rst b/docs/configuration.rst index aac74c2..3b363a8 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -48,6 +48,9 @@ Configuration - **REST_USE_JWT** - Enable JWT Authentication instead of Token/Session based. This is built on top of djangorestframework-simplejwt https://github.com/SimpleJWT/django-rest-framework-simplejwt, which must also be installed. (default: False) - **JWT_AUTH_COOKIE** - The cookie name/key. +- **JWT_AUTH_SECURE** - If you want the cookie to be only sent to the server when a request is made with the https scheme (default: False). +- **JWT_AUTH_HTTPONLY** - If you want to prevent client-side JavaScript from having access to the cookie (default: True). +- **JWT_AUTH_SAMESITE** - To tell the browser not to send this cookie when performing a cross-origin request (default: 'Lax'). SameSite isn’t supported by all browsers. - **OLD_PASSWORD_FIELD_ENABLED** - set it to True if you want to have old password verification on password change enpoint (default: False) - **LOGOUT_ON_PASSWORD_CHANGE** - set to False if you want to keep the current user logged in after a password change From f05abda5b067a73b7011cd00827c85ae511754c5 Mon Sep 17 00:00:00 2001 From: cbomprezzi Date: Mon, 15 Jun 2020 17:57:59 +0200 Subject: [PATCH 02/17] fix distraction errors --- dj_rest_auth/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dj_rest_auth/views.py b/dj_rest_auth/views.py index 6387de6..21e064c 100644 --- a/dj_rest_auth/views.py +++ b/dj_rest_auth/views.py @@ -87,7 +87,7 @@ class LoginView(GenericAPIView): if getattr(settings, 'REST_USE_JWT', False): cookie_name = getattr(settings, 'JWT_AUTH_COOKIE', None) cookie_secure = getattr(settings, 'JWT_AUTH_SECURE', False) - cookie_samesite = getattr(settings, 'JWT_AUTH_HTTPONLY', True) + cookie_httponly = getattr(settings, 'JWT_AUTH_HTTPONLY', True) cookie_samesite = getattr(settings, 'JWT_AUTH_SAMESITE', 'Lax') from rest_framework_simplejwt.settings import api_settings as jwt_settings if cookie_name: @@ -98,7 +98,7 @@ class LoginView(GenericAPIView): self.access_token, expires=expiration, secure=cookie_secure, - httponly=True, + httponly=cookie_httponly, samesite=cookie_samesite ) return response From 3c86fd9abb9fd0d3db7ef4132db9f5a5ad089526 Mon Sep 17 00:00:00 2001 From: cbomprezzi Date: Mon, 15 Jun 2020 18:27:57 +0200 Subject: [PATCH 03/17] add drf login/logout/registration/user customization --- demo/templates/rest_framework/api.html | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 demo/templates/rest_framework/api.html diff --git a/demo/templates/rest_framework/api.html b/demo/templates/rest_framework/api.html new file mode 100644 index 0000000..57f02c9 --- /dev/null +++ b/demo/templates/rest_framework/api.html @@ -0,0 +1,52 @@ +{% extends "rest_framework/base.html" %} + +{% block style %} + {{ block.super }} + +{% endblock %} + +{% block userlinks %} + {% if user.is_authenticated or response.data.access_token %} + + {% else %} + {% url 'rest_login' as login_url %} +
  • Login
  • + {% url 'rest_register' as register_url %} +
  • Register
  • + {% endif %} +{% endblock %} From bea6e3d229d615568db597f77b4cffc4b768120a Mon Sep 17 00:00:00 2001 From: cbomprezzi Date: Mon, 15 Jun 2020 19:28:23 +0200 Subject: [PATCH 04/17] aligned requirements to tox.ini --- demo/requirements.pip | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/requirements.pip b/demo/requirements.pip index e9276aa..4a0061a 100644 --- a/demo/requirements.pip +++ b/demo/requirements.pip @@ -1,5 +1,5 @@ -django>=1.9.0 -git+https://github.com/jazzband/dj-rest-auth.git@master +django>=2.2 +dj-rest-auth @ git+https://github.com/jazzband/dj-rest-auth.git@master djangorestframework>=3.11.0 djangorestframework-simplejwt==4.4.0 django-allauth>=0.24.1 From 034a8bcc410db798c9d1098cf4f3a96d3b7bf0f1 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:13:50 -0500 Subject: [PATCH 05/17] Adds store test results --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index efcf792..6305453 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,6 +21,7 @@ jobs: - run: command: COVERALLS_REPO_TOKEN=Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3 coveralls name: Coverage + - store_test_results test-django-2: <<: *template environment: From 095b1cacda2fd4fe1db9164285c4315256a36f89 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:25:55 -0500 Subject: [PATCH 06/17] Adds xml outputs --- .circleci/config.yml | 3 ++- .gitignore | 1 + dj_rest_auth/tests/settings.py | 3 +++ setup.py | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6305453..7faac87 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,7 +21,8 @@ jobs: - run: command: COVERALLS_REPO_TOKEN=Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3 coveralls name: Coverage - - store_test_results + - store_test_results: + path: ~/test-results/ test-django-2: <<: *template environment: diff --git a/.gitignore b/.gitignore index 136132c..4b70f55 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ coverage.xml *.cover .hypothesis/ .pytest_cache/ +test-results/ # Translations *.mo diff --git a/dj_rest_auth/tests/settings.py b/dj_rest_auth/tests/settings.py index 5f57f91..56f0e94 100644 --- a/dj_rest_auth/tests/settings.py +++ b/dj_rest_auth/tests/settings.py @@ -72,6 +72,9 @@ REST_FRAMEWORK = { ) } +TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' +TEST_OUTPUT_DIR = 'test-results' + INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.admin', diff --git a/setup.py b/setup.py index fac004b..fc9811d 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ setup( 'with_social': ['django-allauth>=0.25.0'], }, tests_require=[ + 'unittest-xml-reporting>=3.0.2', 'responses>=0.5.0', 'django-allauth>=0.25.0', 'djangorestframework-simplejwt>=4.4.0 ', From ec3cf1cd144e55573cd94257da7f86775254273a Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:31:16 -0500 Subject: [PATCH 07/17] Creates test output directory --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7faac87..12e3c37 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,13 +16,15 @@ jobs: - run: pip install --user -r dj_rest_auth/tests/requirements.pip - run: pip install -q --user coveralls djangorestframework==$DRF Django==$DJANGO_VERSION - run: - command: coverage run --source=dj_rest_auth setup.py test + command: | + mkdir -p test-results/ + coverage run --source=dj_rest_auth setup.py test name: Test - run: command: COVERALLS_REPO_TOKEN=Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3 coveralls name: Coverage - store_test_results: - path: ~/test-results/ + path: test-results/ test-django-2: <<: *template environment: From e913ed7f4dce283e6f3b1573d74325aef44e42b3 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:36:30 -0500 Subject: [PATCH 08/17] Coverage.pt report + html out --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 12e3c37..500d811 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,12 +19,16 @@ jobs: command: | mkdir -p test-results/ coverage run --source=dj_rest_auth setup.py test + coverage report + coverage html name: Test - run: command: COVERALLS_REPO_TOKEN=Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3 coveralls name: Coverage - store_test_results: path: test-results/ + - store_artifacts: + path: htmlcov/ test-django-2: <<: *template environment: From 6598cdc32373260de1262508c81626c5f08b85d3 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:42:11 -0500 Subject: [PATCH 09/17] Builds artifacts --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 500d811..91c0aff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,15 +20,16 @@ jobs: mkdir -p test-results/ coverage run --source=dj_rest_auth setup.py test coverage report - coverage html name: Test - run: command: COVERALLS_REPO_TOKEN=Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3 coveralls name: Coverage + - run: + - command: python3 setup.py sdist - store_test_results: path: test-results/ - store_artifacts: - path: htmlcov/ + path: dist/ test-django-2: <<: *template environment: From 3bd8a225d4bdee7343777a821be8b83bb8f49b0f Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:43:07 -0500 Subject: [PATCH 10/17] Adds Build Step --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 91c0aff..e80fac2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,7 +25,8 @@ jobs: command: COVERALLS_REPO_TOKEN=Q58WdUuZOi89XHyDeDsGE2lxUGQ2IfqP3 coveralls name: Coverage - run: - - command: python3 setup.py sdist + command: python3 setup.py sdist + name: Build - store_test_results: path: test-results/ - store_artifacts: From 3f0796647767e18cd665d78f40e59e442e08c680 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:53:16 -0500 Subject: [PATCH 11/17] Simplfies CI config + Pins dep versions --- .circleci/config.yml | 9 ++++++--- dev-requirements.txt | 2 +- dj_rest_auth/tests/requirements.pip | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e80fac2..59f7b6a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,9 +12,12 @@ jobs: executor: docker/docker steps: - checkout - - run: pip install --user -r dev-requirements.txt - - run: pip install --user -r dj_rest_auth/tests/requirements.pip - - run: pip install -q --user coveralls djangorestframework==$DRF Django==$DJANGO_VERSION + - run: + command: cat dj_rest_auth/tests/requirements.pip dev-requirements.txt | pip install --user -r /dev/stdin + name: Install Static Test Dependencies + - run: + command: pip install -q --user coveralls djangorestframework==$DRF Django==$DJANGO_VERSION + name: Install Specific Django + DRF Deps. - run: command: | mkdir -p test-results/ diff --git a/dev-requirements.txt b/dev-requirements.txt index cee8ea6..73b9db0 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,5 +1,5 @@ --editable . responses>=0.5.0 djangorestframework-simplejwt==4.4.0 -django-allauth +django-allauth>=0.25.0 coveralls>=1.11.1 \ No newline at end of file diff --git a/dj_rest_auth/tests/requirements.pip b/dj_rest_auth/tests/requirements.pip index 9f28d70..66af74f 100644 --- a/dj_rest_auth/tests/requirements.pip +++ b/dj_rest_auth/tests/requirements.pip @@ -1,4 +1,4 @@ django-allauth>=0.25.0 -responses>=0.3.0 +responses>=0.5.0 flake8==2.4.0 djangorestframework-simplejwt==4.4.0 From c167e8c5ae2cfbeac29c401e50a88501b7d3da94 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:54:54 -0500 Subject: [PATCH 12/17] Quotes YAML --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 59f7b6a..f815af5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,10 +14,10 @@ jobs: - checkout - run: command: cat dj_rest_auth/tests/requirements.pip dev-requirements.txt | pip install --user -r /dev/stdin - name: Install Static Test Dependencies + name: "Install Static Test Dependencies" - run: command: pip install -q --user coveralls djangorestframework==$DRF Django==$DJANGO_VERSION - name: Install Specific Django + DRF Deps. + name: "Install Django + DRF Deps" - run: command: | mkdir -p test-results/ From 476c0bb1e28c1d1c48109c2b8b4eea3309362c30 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 12:57:30 -0500 Subject: [PATCH 13/17] Updates format --- .circleci/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f815af5..c445f6b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,8 +13,9 @@ jobs: steps: - checkout - run: - command: cat dj_rest_auth/tests/requirements.pip dev-requirements.txt | pip install --user -r /dev/stdin - name: "Install Static Test Dependencies" + command: | + cat dj_rest_auth/tests/requirements.pip dev-requirements.txt | pip install --user -r /dev/stdin + name: Install - run: command: pip install -q --user coveralls djangorestframework==$DRF Django==$DJANGO_VERSION name: "Install Django + DRF Deps" From 89573f6e68e0d60f5e59fef1db5956c0c165e827 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 13:02:48 -0500 Subject: [PATCH 14/17] Adds run step names --- .circleci/config.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c445f6b..7032790 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,12 +13,11 @@ jobs: steps: - checkout - run: - command: | - cat dj_rest_auth/tests/requirements.pip dev-requirements.txt | pip install --user -r /dev/stdin - name: Install + command: pip install --user -r dev-requirements.txt + name: "Pip Install dev requirements" - run: - command: pip install -q --user coveralls djangorestframework==$DRF Django==$DJANGO_VERSION - name: "Install Django + DRF Deps" + command: pip install --user -r dj_rest_auth/tests/requirements.pip + name: "Pip Install test requirements" - run: command: | mkdir -p test-results/ From 0722ec4aeeefeae08a8e9a801394b4a09ee7670c Mon Sep 17 00:00:00 2001 From: alichass Date: Sun, 14 Jun 2020 17:26:28 -0400 Subject: [PATCH 15/17] added the ability to customise claims in the jwt token - has tests JWT claim serializer now can be set to something custom in settings: JWT_TOKEN_CLAIMS_SERIALIZER = myTokenObtainSerializer Ideally JWT_TOKEN_CLAIMS_SERIALIZER would be a key in REST_AUTH_SERIALIZERS and assigned through import_callable, as with the other serializers; however, I could not quite figure out how to implement it that way --- dj_rest_auth/tests/test_api.py | 66 ++++++++++++++++++++++++++++++++++ dj_rest_auth/utils.py | 20 +++++------ 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/dj_rest_auth/tests/test_api.py b/dj_rest_auth/tests/test_api.py index 2115415..9554d05 100644 --- a/dj_rest_auth/tests/test_api.py +++ b/dj_rest_auth/tests/test_api.py @@ -18,6 +18,9 @@ try: except ImportError: from django.core.urlresolvers import reverse +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer +from jwt import decode as decode_jwt + @override_settings(ROOT_URLCONF="tests.urls") class APIBasicTests(TestsMixin, TestCase): @@ -605,3 +608,66 @@ class APIBasicTests(TestsMixin, TestCase): # 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) + + + + class TESTTokenObtainPairSerializer(TokenObtainPairSerializer): + @classmethod + def get_token(cls, user): + token = super().get_token(user) + # Add custom claims + token['name'] = user.username + token['email'] = user.email + + return token + + + @override_settings(REST_USE_JWT=True) + @override_settings(JWT_AUTH_COOKIE=None) + @override_settings(REST_FRAMEWORK=dict( + DEFAULT_AUTHENTICATION_CLASSES=[ + 'dj_rest_auth.utils.JWTCookieAuthentication' + ] + )) + @override_settings(REST_SESSION_LOGIN=False) + @override_settings(JWT_TOKEN_CLAIMS_SERIALIZER = TESTTokenObtainPairSerializer) + def test_custom_jwt_claims(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS) + + self.post(self.login_url, data=payload, status_code=200) + self.assertEqual('access_token' in self.response.json.keys(), True) + self.token = self.response.json['access_token'] + claims = decode_jwt(self.token, settings.SECRET_KEY, algorithms='HS256') + self.assertEquals(claims['user_id'], 1) + self.assertEquals(claims['name'], 'person') + self.assertEquals(claims['email'], 'person1@world.com') + + + @override_settings(REST_USE_JWT=True) + @override_settings(JWT_AUTH_COOKIE='jwt-auth') + @override_settings(REST_FRAMEWORK=dict( + DEFAULT_AUTHENTICATION_CLASSES=[ + 'dj_rest_auth.utils.JWTCookieAuthentication' + ] + )) + @override_settings(REST_SESSION_LOGIN=False) + @override_settings(JWT_TOKEN_CLAIMS_SERIALIZER = TESTTokenObtainPairSerializer) + def test_custom_jwt_claims_cookie_w_authentication(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS) + resp = self.post(self.login_url, data=payload, status_code=200) + self.assertEqual(['jwt-auth'], list(resp.cookies.keys())) + token = resp.cookies.get('jwt-auth').value + claims = decode_jwt(token, settings.SECRET_KEY, algorithms='HS256') + self.assertEquals(claims['user_id'], 1) + self.assertEquals(claims['name'], 'person') + self.assertEquals(claims['email'], 'person1@world.com') + resp = self.get('/protected-view/') + self.assertEquals(resp.status_code, 200) \ No newline at end of file diff --git a/dj_rest_auth/utils.py b/dj_rest_auth/utils.py index e1ef77a..5f229af 100644 --- a/dj_rest_auth/utils.py +++ b/dj_rest_auth/utils.py @@ -15,18 +15,15 @@ def default_create_token(token_model, user, serializer): return token -def jwt_encode(user): - try: - from rest_framework_simplejwt.serializers import TokenObtainPairSerializer - except ImportError: - raise ImportError("rest-framework-simplejwt needs to be installed") - - refresh = TokenObtainPairSerializer.get_token(user) - return refresh.access_token, refresh - - try: + from django.conf import settings from rest_framework_simplejwt.authentication import JWTAuthentication + from rest_framework_simplejwt.serializers import TokenObtainPairSerializer + + def jwt_encode(user): + TOPS = getattr(settings, 'JWT_TOKEN_CLAIMS_SERIALIZER', TokenObtainPairSerializer) + refresh = TOPS.get_token(user) + return refresh.access_token, refresh class JWTCookieAuthentication(JWTAuthentication): """ @@ -35,7 +32,6 @@ try: preference to the header). """ def authenticate(self, request): - from django.conf import settings cookie_name = getattr(settings, 'JWT_AUTH_COOKIE', None) header = self.get_header(request) if header is None: @@ -53,4 +49,4 @@ try: return self.get_user(validated_token), validated_token except ImportError: - pass + raise ImportError("rest-framework-simplejwt needs to be installed") From 1dce78194241112f9b0829b26b710c5354248b5d Mon Sep 17 00:00:00 2001 From: alichass Date: Sun, 14 Jun 2020 19:15:22 -0400 Subject: [PATCH 16/17] made JWT_TOKEN_CLAIMS_SERIALIZER setting value a callable string rather than a function --- dj_rest_auth/tests/test_api.py | 26 ++++++++++++-------------- dj_rest_auth/utils.py | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/dj_rest_auth/tests/test_api.py b/dj_rest_auth/tests/test_api.py index 9554d05..f25f5fc 100644 --- a/dj_rest_auth/tests/test_api.py +++ b/dj_rest_auth/tests/test_api.py @@ -21,6 +21,16 @@ except ImportError: from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from jwt import decode as decode_jwt +class TESTTokenObtainPairSerializer(TokenObtainPairSerializer): + @classmethod + def get_token(cls, user): + token = super().get_token(user) + # Add custom claims + token['name'] = user.username + token['email'] = user.email + + return token + @override_settings(ROOT_URLCONF="tests.urls") class APIBasicTests(TestsMixin, TestCase): @@ -610,18 +620,6 @@ class APIBasicTests(TestsMixin, TestCase): self.assertEqual(resp.status_code, 500) - - class TESTTokenObtainPairSerializer(TokenObtainPairSerializer): - @classmethod - def get_token(cls, user): - token = super().get_token(user) - # Add custom claims - token['name'] = user.username - token['email'] = user.email - - return token - - @override_settings(REST_USE_JWT=True) @override_settings(JWT_AUTH_COOKIE=None) @override_settings(REST_FRAMEWORK=dict( @@ -630,7 +628,7 @@ class APIBasicTests(TestsMixin, TestCase): ] )) @override_settings(REST_SESSION_LOGIN=False) - @override_settings(JWT_TOKEN_CLAIMS_SERIALIZER = TESTTokenObtainPairSerializer) + @override_settings(JWT_TOKEN_CLAIMS_SERIALIZER = 'tests.test_api.TESTTokenObtainPairSerializer') def test_custom_jwt_claims(self): payload = { "username": self.USERNAME, @@ -655,7 +653,7 @@ class APIBasicTests(TestsMixin, TestCase): ] )) @override_settings(REST_SESSION_LOGIN=False) - @override_settings(JWT_TOKEN_CLAIMS_SERIALIZER = TESTTokenObtainPairSerializer) + @override_settings(JWT_TOKEN_CLAIMS_SERIALIZER = 'tests.test_api.TESTTokenObtainPairSerializer') def test_custom_jwt_claims_cookie_w_authentication(self): payload = { "username": self.USERNAME, diff --git a/dj_rest_auth/utils.py b/dj_rest_auth/utils.py index 5f229af..fa971de 100644 --- a/dj_rest_auth/utils.py +++ b/dj_rest_auth/utils.py @@ -21,7 +21,7 @@ try: from rest_framework_simplejwt.serializers import TokenObtainPairSerializer def jwt_encode(user): - TOPS = getattr(settings, 'JWT_TOKEN_CLAIMS_SERIALIZER', TokenObtainPairSerializer) + TOPS = import_callable(getattr(settings, 'JWT_TOKEN_CLAIMS_SERIALIZER', TokenObtainPairSerializer)) refresh = TOPS.get_token(user) return refresh.access_token, refresh From ed99925d724f53e9cc515335def0810be531fe11 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 20 Jun 2020 13:35:16 -0500 Subject: [PATCH 17/17] Adds docs --- docs/configuration.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/configuration.rst b/docs/configuration.rst index 3b363a8..f421d76 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -12,6 +12,8 @@ Configuration - JWT_SERIALIZER - (Using REST_USE_JWT=True) response for successful authentication in ``dj_rest_auth.views.LoginView``, default value ``dj_rest_auth.serializers.JWTSerializer`` + - JWT_TOKEN_CLAIMS_SERIALIZER - A custom JWT Claim serializer. Default is `rest_framework_simplejwt.serializers.TokenObtainPairSerializer` + - USER_DETAILS_SERIALIZER - serializer class in ``dj_rest_auth.views.UserDetailsView``, default value ``dj_rest_auth.serializers.UserDetailsSerializer`` - PASSWORD_RESET_SERIALIZER - serializer class in ``dj_rest_auth.views.PasswordResetView``, default value ``dj_rest_auth.serializers.PasswordResetSerializer``