From e4487dc0290e01ac504433e2433f66bfb0f0d34f Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 12:31:29 +0000 Subject: [PATCH 01/48] Added serializer for Knox AuthToken --- rest_auth/serializers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index d42242a..c217f3f 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -120,6 +120,16 @@ class TokenSerializer(serializers.ModelSerializer): fields = ('key',) +class KnoxTokenSerializer(serializers.ModelSerializer): + """ + Serializer for Knox AuthToken model. + """ + + class Meta: + model = knox.models.AuthToken + fields = ('token',) + + class UserDetailsSerializer(serializers.ModelSerializer): """ User model w/o password From 64b241c93f0344b4710aff64c80d3560853662a8 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 12:36:19 +0000 Subject: [PATCH 02/48] Added KnoxTokenSerializer to app_settings --- rest_auth/app_settings.py | 4 ++++ rest_auth/registration/views.py | 1 + rest_auth/views.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index 1b75fe6..f3e9da1 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -2,6 +2,7 @@ from django.conf import settings from rest_auth.serializers import ( TokenSerializer as DefaultTokenSerializer, + KnoxTokenSerializer as DefaultKnoxTokenSerializer, JWTSerializer as DefaultJWTSerializer, UserDetailsSerializer as DefaultUserDetailsSerializer, LoginSerializer as DefaultLoginSerializer, @@ -18,6 +19,9 @@ serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {}) TokenSerializer = import_callable( serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer)) +KnoxTokenSerializer = import_callable( + serializers.get('KNOX_TOKEN_SERIALIZER', DefaultKnoxTokenSerializer)) + JWTSerializer = import_callable( serializers.get('JWT_SERIALIZER', DefaultJWTSerializer)) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index e35153d..4a55add 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -16,6 +16,7 @@ from allauth.account import app_settings as allauth_settings from rest_auth.app_settings import (TokenSerializer, JWTSerializer, + KnoxTokenSerializer, create_token) from rest_auth.models import TokenModel from rest_auth.registration.serializers import (SocialLoginSerializer, diff --git a/rest_auth/views.py b/rest_auth/views.py index 0493a76..85c0520 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -16,7 +16,7 @@ from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView from rest_framework.permissions import IsAuthenticated, AllowAny from .app_settings import ( - TokenSerializer, UserDetailsSerializer, LoginSerializer, + TokenSerializer, KnoxTokenSerializer, UserDetailsSerializer, LoginSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer, PasswordChangeSerializer, JWTSerializer, create_token ) From ea9bdec6ca6c4642c0937c3604335a113a3d55d8 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 12:44:03 +0000 Subject: [PATCH 03/48] Use self.token instead of user.auth_token Removes implicit 1-to-1 user-to-token requirement --- rest_auth/registration/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 4a55add..9b08ce3 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -51,7 +51,7 @@ class RegisterView(CreateAPIView): } return JWTSerializer(data).data else: - return TokenSerializer(user.auth_token).data + return TokenSerializer(self.token).data def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) @@ -66,9 +66,9 @@ class RegisterView(CreateAPIView): def perform_create(self, serializer): user = serializer.save(self.request) if getattr(settings, 'REST_USE_JWT', False): - self.token = jwt_encode(user) + self.token = jwt_encode(user) else: - create_token(self.token_model, user, serializer) + self.token = create_token(self.token_model, user, serializer) complete_signup(self.request._request, user, allauth_settings.EMAIL_VERIFICATION, From ca0cbd88e68fdc00f9c1a579f561c563865a143e Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 12:47:46 +0000 Subject: [PATCH 04/48] Use Knox AuthToken as default Token Model if REST_USE_KNOX is True --- rest_auth/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_auth/models.py b/rest_auth/models.py index a132f9c..c40779e 100644 --- a/rest_auth/models.py +++ b/rest_auth/models.py @@ -1,6 +1,9 @@ from django.conf import settings -from rest_framework.authtoken.models import Token as DefaultTokenModel +if getattr(settings, 'REST_USE_KNOX', False): + from knox.models import AuthToken as DefaultTokenModel +else: + from rest_framework.authtoken.models import Token as DefaultTokenModel from .utils import import_callable From a8c6248134737724f04e4ac2c5dc278995a26d63 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 12:51:29 +0000 Subject: [PATCH 05/48] DefaultTokenSerializer depends on REST_USE_KNOX --- rest_auth/app_settings.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index f3e9da1..da143fe 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -1,8 +1,11 @@ from django.conf import settings +if getattr(settings, 'REST_USE_KNOX', False): + from rest_auth.serializers import KnoxTokenSerializer as DefaultTokenSerializer, +else: + from rest_auth.serializers import TokenSerializer as DefaultTokenSerializer, + from rest_auth.serializers import ( - TokenSerializer as DefaultTokenSerializer, - KnoxTokenSerializer as DefaultKnoxTokenSerializer, JWTSerializer as DefaultJWTSerializer, UserDetailsSerializer as DefaultUserDetailsSerializer, LoginSerializer as DefaultLoginSerializer, @@ -19,9 +22,6 @@ serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {}) TokenSerializer = import_callable( serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer)) -KnoxTokenSerializer = import_callable( - serializers.get('KNOX_TOKEN_SERIALIZER', DefaultKnoxTokenSerializer)) - JWTSerializer = import_callable( serializers.get('JWT_SERIALIZER', DefaultJWTSerializer)) From b9aadef03163288b52fc0f69909a3e0b4f915812 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 12:52:50 +0000 Subject: [PATCH 06/48] Remove KnoxTokenSerializer import --- rest_auth/registration/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 9b08ce3..1e7d8f6 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -16,7 +16,6 @@ from allauth.account import app_settings as allauth_settings from rest_auth.app_settings import (TokenSerializer, JWTSerializer, - KnoxTokenSerializer, create_token) from rest_auth.models import TokenModel from rest_auth.registration.serializers import (SocialLoginSerializer, From 41b5cca6dbfdb15951e18c594ca0bee91215bd67 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 13:01:17 +0000 Subject: [PATCH 07/48] Removed bad commas --- rest_auth/app_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index da143fe..eb00544 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -1,9 +1,9 @@ from django.conf import settings if getattr(settings, 'REST_USE_KNOX', False): - from rest_auth.serializers import KnoxTokenSerializer as DefaultTokenSerializer, + from rest_auth.serializers import KnoxTokenSerializer as DefaultTokenSerializer else: - from rest_auth.serializers import TokenSerializer as DefaultTokenSerializer, + from rest_auth.serializers import TokenSerializer as DefaultTokenSerializer from rest_auth.serializers import ( JWTSerializer as DefaultJWTSerializer, From 0c9c9b5a5a1d0080683c57cec60b84d68db2a24d Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 13:52:19 +0000 Subject: [PATCH 08/48] Use TokenModel in Knox serializer --- rest_auth/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index c217f3f..41414d7 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -126,7 +126,7 @@ class KnoxTokenSerializer(serializers.ModelSerializer): """ class Meta: - model = knox.models.AuthToken + model = TokenModel fields = ('token',) From d76b685c3655c223828706d36faba58acdca0d0c Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 13:53:06 +0000 Subject: [PATCH 09/48] try/except Knox AuthToken model import --- rest_auth/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_auth/models.py b/rest_auth/models.py index c40779e..b496706 100644 --- a/rest_auth/models.py +++ b/rest_auth/models.py @@ -1,7 +1,10 @@ from django.conf import settings if getattr(settings, 'REST_USE_KNOX', False): - from knox.models import AuthToken as DefaultTokenModel + try: + from knox.models import AuthToken as DefaultTokenModel + except ImportError: + raise ImportError("Install django-rest-knox to use REST_USE_KNOX = True") else: from rest_framework.authtoken.models import Token as DefaultTokenModel From b926291fdcb1eb43bba8be4fee9af67652ebe715 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 13:56:23 +0000 Subject: [PATCH 10/48] Replaced tabs with spaces --- rest_auth/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_auth/models.py b/rest_auth/models.py index b496706..13d4f74 100644 --- a/rest_auth/models.py +++ b/rest_auth/models.py @@ -1,12 +1,12 @@ from django.conf import settings if getattr(settings, 'REST_USE_KNOX', False): - try: + try: from knox.models import AuthToken as DefaultTokenModel except ImportError: raise ImportError("Install django-rest-knox to use REST_USE_KNOX = True") else: - from rest_framework.authtoken.models import Token as DefaultTokenModel + from rest_framework.authtoken.models import Token as DefaultTokenModel from .utils import import_callable From b49bb2a1aa765b722a7101e2e62c4d3066146c82 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 13:59:17 +0000 Subject: [PATCH 11/48] Remove KnoxTokenSerializer import from views As default TokenSerializer is set depending on REST_USE_KNOX --- rest_auth/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_auth/views.py b/rest_auth/views.py index 85c0520..0493a76 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -16,7 +16,7 @@ from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView from rest_framework.permissions import IsAuthenticated, AllowAny from .app_settings import ( - TokenSerializer, KnoxTokenSerializer, UserDetailsSerializer, LoginSerializer, + TokenSerializer, UserDetailsSerializer, LoginSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer, PasswordChangeSerializer, JWTSerializer, create_token ) From e9c4ecbc070d2fd6d1df0092d156633b5c9a5760 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 14:20:41 +0000 Subject: [PATCH 12/48] Use REST_AUTH_TOKEN_APP and remove REST_USE_JWT Prevent simultaneous Knox and JWT use. Options are 'jwt' or 'knox' --- docs/configuration.rst | 4 ++-- docs/installation.rst | 2 +- rest_auth/app_settings.py | 2 +- rest_auth/models.py | 4 ++-- rest_auth/registration/views.py | 4 ++-- rest_auth/tests/test_api.py | 6 +++--- rest_auth/tests/test_base.py | 2 +- rest_auth/tests/test_social.py | 2 +- rest_auth/views.py | 6 +++--- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 1f5b40f..31cd641 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -10,7 +10,7 @@ Configuration - TOKEN_SERIALIZER - response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.TokenSerializer`` - - JWT_SERIALIZER - (Using REST_USE_JWT=True) response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.JWTSerializer`` + - JWT_SERIALIZER - (Using REST_AUTH_TOKEN_APP = 'jwt') response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.JWTSerializer`` - USER_DETAILS_SERIALIZER - serializer class in ``rest_auth.views.UserDetailsView``, default value ``rest_auth.serializers.UserDetailsSerializer`` @@ -46,7 +46,7 @@ Configuration - **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True) -- **REST_USE_JWT** - Enable JWT Authentication instead of Token/Session based. This is built on top of django-rest-framework-jwt http://getblimp.github.io/django-rest-framework-jwt/, which must also be installed. (default: False) +- **REST_AUTH_TOKEN_APP** - Enable a compatible installed third party authentication app instead of built-in Django Rest Framework Tokens. Options are `'jwt'`, using django-rest-framework-jwt http://getblimp.github.io/django-rest-framework-jwt/, and `'knox'`, using django-rest-knox https://github.com/James1345/django-rest-knox/. (default: False) - **OLD_PASSWORD_FIELD_ENABLED** - set it to True if you want to have old password verification on password change enpoint (default: False) diff --git a/docs/installation.rst b/docs/installation.rst index 6144011..8d056a7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -159,5 +159,5 @@ By default, ``django-rest-auth`` uses Django's Token-based authentication. If yo .. code-block:: python - REST_USE_JWT = True + REST_AUTH_TOKEN_APP = 'jwt' diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index eb00544..96568aa 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -1,6 +1,6 @@ from django.conf import settings -if getattr(settings, 'REST_USE_KNOX', False): +if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'knox': from rest_auth.serializers import KnoxTokenSerializer as DefaultTokenSerializer else: from rest_auth.serializers import TokenSerializer as DefaultTokenSerializer diff --git a/rest_auth/models.py b/rest_auth/models.py index 13d4f74..3061a97 100644 --- a/rest_auth/models.py +++ b/rest_auth/models.py @@ -1,10 +1,10 @@ from django.conf import settings -if getattr(settings, 'REST_USE_KNOX', False): +if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'knox': try: from knox.models import AuthToken as DefaultTokenModel except ImportError: - raise ImportError("Install django-rest-knox to use REST_USE_KNOX = True") + raise ImportError("Install django-rest-knox before setting REST_AUTH_TOKEN_APP to 'knox'") else: from rest_framework.authtoken.models import Token as DefaultTokenModel diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 1e7d8f6..ce84a0d 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -43,7 +43,7 @@ class RegisterView(CreateAPIView): allauth_settings.EmailVerificationMethod.MANDATORY: return {"detail": _("Verification e-mail sent.")} - if getattr(settings, 'REST_USE_JWT', False): + if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': data = { 'user': user, 'token': self.token @@ -64,7 +64,7 @@ class RegisterView(CreateAPIView): def perform_create(self, serializer): user = serializer.save(self.request) - if getattr(settings, 'REST_USE_JWT', False): + if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': self.token = jwt_encode(user) else: self.token = create_token(self.token_model, user, serializer) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index a24e9f0..014fc43 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -136,7 +136,7 @@ class APITestCase1(TestCase, BaseAPITestCase): self.post(self.login_url, data=payload, status_code=200) - @override_settings(REST_USE_JWT=True) + @override_settings(REST_AUTH_TOKEN_APP='jwt') def test_login_jwt(self): payload = { "username": self.USERNAME, @@ -367,7 +367,7 @@ class APITestCase1(TestCase, BaseAPITestCase): self.assertEqual(user.last_name, self.response.json['last_name']) self.assertEqual(user.email, self.response.json['email']) - @override_settings(REST_USE_JWT=True) + @override_settings(REST_AUTH_TOKEN_APP='jwt') def test_user_details_using_jwt(self): user = get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS) payload = { @@ -398,7 +398,7 @@ class APITestCase1(TestCase, BaseAPITestCase): self._login() self._logout() - @override_settings(REST_USE_JWT=True) + @override_settings(REST_AUTH_TOKEN_APP='jwt') def test_registration_with_jwt(self): user_count = get_user_model().objects.all().count() diff --git a/rest_auth/tests/test_base.py b/rest_auth/tests/test_base.py index faaf7bb..616ca9d 100644 --- a/rest_auth/tests/test_base.py +++ b/rest_auth/tests/test_base.py @@ -37,7 +37,7 @@ class BaseAPITestCase(object): # check_headers = kwargs.pop('check_headers', True) if hasattr(self, 'token'): - if getattr(settings, 'REST_USE_JWT', False): + if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': kwargs['HTTP_AUTHORIZATION'] = 'JWT %s' % self.token else: kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token diff --git a/rest_auth/tests/test_social.py b/rest_auth/tests/test_social.py index 56bdace..82a41e2 100644 --- a/rest_auth/tests/test_social.py +++ b/rest_auth/tests/test_social.py @@ -280,7 +280,7 @@ class TestSocialAuth(TestCase, BaseAPITestCase): @responses.activate @override_settings( - REST_USE_JWT=True + REST_AUTH_TOKEN_APP='jwt' ) def test_jwt(self): resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true}' # noqa diff --git a/rest_auth/views.py b/rest_auth/views.py index 0493a76..3a03084 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -52,7 +52,7 @@ class LoginView(GenericAPIView): django_login(self.request, self.user) def get_response_serializer(self): - if getattr(settings, 'REST_USE_JWT', False): + if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': response_serializer = JWTSerializer else: response_serializer = TokenSerializer @@ -61,7 +61,7 @@ class LoginView(GenericAPIView): def login(self): self.user = self.serializer.validated_data['user'] - if getattr(settings, 'REST_USE_JWT', False): + if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': self.token = jwt_encode(self.user) else: self.token = create_token(self.token_model, self.user, @@ -73,7 +73,7 @@ class LoginView(GenericAPIView): def get_response(self): serializer_class = self.get_response_serializer() - if getattr(settings, 'REST_USE_JWT', False): + if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': data = { 'user': self.user, 'token': self.token From 83f4db64a4e4315685bc54227ea015b4e33525c2 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 14:34:06 +0000 Subject: [PATCH 13/48] Added create_knox_token as default_create_token fails --- rest_auth/app_settings.py | 4 +++- rest_auth/utils.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index 96568aa..b1960eb 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -2,8 +2,10 @@ from django.conf import settings if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'knox': from rest_auth.serializers import KnoxTokenSerializer as DefaultTokenSerializer + from .utils import create_knox_token as default_create_token else: from rest_auth.serializers import TokenSerializer as DefaultTokenSerializer + from .utils import default_create_token from rest_auth.serializers import ( JWTSerializer as DefaultJWTSerializer, @@ -12,7 +14,7 @@ from rest_auth.serializers import ( PasswordResetSerializer as DefaultPasswordResetSerializer, PasswordResetConfirmSerializer as DefaultPasswordResetConfirmSerializer, PasswordChangeSerializer as DefaultPasswordChangeSerializer) -from .utils import import_callable, default_create_token +from .utils import import_callable create_token = import_callable( getattr(settings, 'REST_AUTH_TOKEN_CREATOR', default_create_token)) diff --git a/rest_auth/utils.py b/rest_auth/utils.py index 800f184..14c351f 100644 --- a/rest_auth/utils.py +++ b/rest_auth/utils.py @@ -16,6 +16,11 @@ def default_create_token(token_model, user, serializer): return token +def create_knox_token(token_model, user, serializer): + token = token_model.objects.get_or_create(user=user) + return token + + def jwt_encode(user): try: from rest_framework_jwt.settings import api_settings From 3ac64f99dd18c631a05bea9d864b50ebdd2b2289 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 14:37:46 +0000 Subject: [PATCH 14/48] Knox tokens should be created, not retrieved --- rest_auth/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_auth/utils.py b/rest_auth/utils.py index 14c351f..dbd7ee5 100644 --- a/rest_auth/utils.py +++ b/rest_auth/utils.py @@ -17,7 +17,7 @@ def default_create_token(token_model, user, serializer): def create_knox_token(token_model, user, serializer): - token = token_model.objects.get_or_create(user=user) + token = token_model.objects.create(user=user) return token From 544c45f84ad42d7ebce96e153717195a073b2bef Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 14:42:53 +0000 Subject: [PATCH 15/48] Knox model stores token_key, not token --- rest_auth/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 41414d7..34da351 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -127,7 +127,7 @@ class KnoxTokenSerializer(serializers.ModelSerializer): class Meta: model = TokenModel - fields = ('token',) + fields = ('token_key',) class UserDetailsSerializer(serializers.ModelSerializer): From c27286170caef94748528cafe35767b7636941d0 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 14:48:24 +0000 Subject: [PATCH 16/48] KnoxTokenSerializer should just return the generated `token` --- rest_auth/serializers.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 34da351..0e4d977 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -124,10 +124,7 @@ class KnoxTokenSerializer(serializers.ModelSerializer): """ Serializer for Knox AuthToken model. """ - - class Meta: - model = TokenModel - fields = ('token_key',) + token = serializers.CharField() class UserDetailsSerializer(serializers.ModelSerializer): From a2aa430d4a23e3fe6bd6d2e5bc67bb01fb3f93a7 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 14:49:46 +0000 Subject: [PATCH 17/48] KnoxTokenSerializer should be serializers.Serializer --- rest_auth/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 0e4d977..503cb76 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -120,7 +120,7 @@ class TokenSerializer(serializers.ModelSerializer): fields = ('key',) -class KnoxTokenSerializer(serializers.ModelSerializer): +class KnoxTokenSerializer(serializers.Serializer): """ Serializer for Knox AuthToken model. """ From 6f2319e20ee58e1d4f71049c1e3e20d6e871891c Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Tue, 24 Jan 2017 14:54:14 +0000 Subject: [PATCH 18/48] Pass Knox serializer appropriate data format and separate from default --- rest_auth/app_settings.py | 7 +++++-- rest_auth/registration/views.py | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index b1960eb..bb6c27a 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -1,13 +1,13 @@ from django.conf import settings if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'knox': - from rest_auth.serializers import KnoxTokenSerializer as DefaultTokenSerializer from .utils import create_knox_token as default_create_token else: - from rest_auth.serializers import TokenSerializer as DefaultTokenSerializer from .utils import default_create_token from rest_auth.serializers import ( + TokenSerializer as DefaultTokenSerializer, + KnoxTokenSerializer as DefaultKnoxTokenSerializer, JWTSerializer as DefaultJWTSerializer, UserDetailsSerializer as DefaultUserDetailsSerializer, LoginSerializer as DefaultLoginSerializer, @@ -27,6 +27,9 @@ TokenSerializer = import_callable( JWTSerializer = import_callable( serializers.get('JWT_SERIALIZER', DefaultJWTSerializer)) +KnoxTokenSerializer = import_callable( + serializers.get('KNOX_TOKEN_SERIALIZER', DefaultKnoxTokenSerializer)) + UserDetailsSerializer = import_callable( serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer) ) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index ce84a0d..9e2dd6d 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -16,6 +16,7 @@ from allauth.account import app_settings as allauth_settings from rest_auth.app_settings import (TokenSerializer, JWTSerializer, + KnoxTokenSerializer, create_token) from rest_auth.models import TokenModel from rest_auth.registration.serializers import (SocialLoginSerializer, @@ -49,6 +50,12 @@ class RegisterView(CreateAPIView): 'token': self.token } return JWTSerializer(data).data + if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'knox': + data = { + 'token': self.token + } + return KnoxTokenSerializer(data).data + else: return TokenSerializer(self.token).data From e1dffa4ef7ee07a9fa0947f66c8b84c5f7bd3f1d Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 10:44:31 +0000 Subject: [PATCH 19/48] Reverted use of REST_AUTH_TOKEN_APP This would be a breaking change, and would make it harder to use JWT and Knox simultaneously (e.g. JWT for Web, Knox for Mobile), so reverted. --- docs/configuration.rst | 4 ++-- docs/installation.rst | 2 +- rest_auth/app_settings.py | 2 +- rest_auth/models.py | 4 ++-- rest_auth/registration/views.py | 4 ++-- rest_auth/tests/test_api.py | 6 +++--- rest_auth/tests/test_base.py | 2 +- rest_auth/tests/test_social.py | 2 +- rest_auth/views.py | 6 +++--- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 31cd641..1f5b40f 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -10,7 +10,7 @@ Configuration - TOKEN_SERIALIZER - response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.TokenSerializer`` - - JWT_SERIALIZER - (Using REST_AUTH_TOKEN_APP = 'jwt') response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.JWTSerializer`` + - JWT_SERIALIZER - (Using REST_USE_JWT=True) response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.JWTSerializer`` - USER_DETAILS_SERIALIZER - serializer class in ``rest_auth.views.UserDetailsView``, default value ``rest_auth.serializers.UserDetailsSerializer`` @@ -46,7 +46,7 @@ Configuration - **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True) -- **REST_AUTH_TOKEN_APP** - Enable a compatible installed third party authentication app instead of built-in Django Rest Framework Tokens. Options are `'jwt'`, using django-rest-framework-jwt http://getblimp.github.io/django-rest-framework-jwt/, and `'knox'`, using django-rest-knox https://github.com/James1345/django-rest-knox/. (default: False) +- **REST_USE_JWT** - Enable JWT Authentication instead of Token/Session based. This is built on top of django-rest-framework-jwt http://getblimp.github.io/django-rest-framework-jwt/, which must also be installed. (default: False) - **OLD_PASSWORD_FIELD_ENABLED** - set it to True if you want to have old password verification on password change enpoint (default: False) diff --git a/docs/installation.rst b/docs/installation.rst index 8d056a7..6144011 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -159,5 +159,5 @@ By default, ``django-rest-auth`` uses Django's Token-based authentication. If yo .. code-block:: python - REST_AUTH_TOKEN_APP = 'jwt' + REST_USE_JWT = True diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index bb6c27a..59d3456 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -1,6 +1,6 @@ from django.conf import settings -if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'knox': +if getattr(settings, 'REST_USE_KNOX', False): from .utils import create_knox_token as default_create_token else: from .utils import default_create_token diff --git a/rest_auth/models.py b/rest_auth/models.py index 3061a97..13d4f74 100644 --- a/rest_auth/models.py +++ b/rest_auth/models.py @@ -1,10 +1,10 @@ from django.conf import settings -if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'knox': +if getattr(settings, 'REST_USE_KNOX', False): try: from knox.models import AuthToken as DefaultTokenModel except ImportError: - raise ImportError("Install django-rest-knox before setting REST_AUTH_TOKEN_APP to 'knox'") + raise ImportError("Install django-rest-knox to use REST_USE_KNOX = True") else: from rest_framework.authtoken.models import Token as DefaultTokenModel diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 9e2dd6d..225ae0d 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -44,7 +44,7 @@ class RegisterView(CreateAPIView): allauth_settings.EmailVerificationMethod.MANDATORY: return {"detail": _("Verification e-mail sent.")} - if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': + if getattr(settings, 'REST_USE_JWT', False): data = { 'user': user, 'token': self.token @@ -71,7 +71,7 @@ class RegisterView(CreateAPIView): def perform_create(self, serializer): user = serializer.save(self.request) - if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': + if getattr(settings, 'REST_USE_JWT', False): self.token = jwt_encode(user) else: self.token = create_token(self.token_model, user, serializer) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index 014fc43..a24e9f0 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -136,7 +136,7 @@ class APITestCase1(TestCase, BaseAPITestCase): self.post(self.login_url, data=payload, status_code=200) - @override_settings(REST_AUTH_TOKEN_APP='jwt') + @override_settings(REST_USE_JWT=True) def test_login_jwt(self): payload = { "username": self.USERNAME, @@ -367,7 +367,7 @@ class APITestCase1(TestCase, BaseAPITestCase): self.assertEqual(user.last_name, self.response.json['last_name']) self.assertEqual(user.email, self.response.json['email']) - @override_settings(REST_AUTH_TOKEN_APP='jwt') + @override_settings(REST_USE_JWT=True) def test_user_details_using_jwt(self): user = get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS) payload = { @@ -398,7 +398,7 @@ class APITestCase1(TestCase, BaseAPITestCase): self._login() self._logout() - @override_settings(REST_AUTH_TOKEN_APP='jwt') + @override_settings(REST_USE_JWT=True) def test_registration_with_jwt(self): user_count = get_user_model().objects.all().count() diff --git a/rest_auth/tests/test_base.py b/rest_auth/tests/test_base.py index 616ca9d..faaf7bb 100644 --- a/rest_auth/tests/test_base.py +++ b/rest_auth/tests/test_base.py @@ -37,7 +37,7 @@ class BaseAPITestCase(object): # check_headers = kwargs.pop('check_headers', True) if hasattr(self, 'token'): - if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': + if getattr(settings, 'REST_USE_JWT', False): kwargs['HTTP_AUTHORIZATION'] = 'JWT %s' % self.token else: kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token diff --git a/rest_auth/tests/test_social.py b/rest_auth/tests/test_social.py index 82a41e2..56bdace 100644 --- a/rest_auth/tests/test_social.py +++ b/rest_auth/tests/test_social.py @@ -280,7 +280,7 @@ class TestSocialAuth(TestCase, BaseAPITestCase): @responses.activate @override_settings( - REST_AUTH_TOKEN_APP='jwt' + REST_USE_JWT=True ) def test_jwt(self): resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true}' # noqa diff --git a/rest_auth/views.py b/rest_auth/views.py index 3a03084..0493a76 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -52,7 +52,7 @@ class LoginView(GenericAPIView): django_login(self.request, self.user) def get_response_serializer(self): - if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': + if getattr(settings, 'REST_USE_JWT', False): response_serializer = JWTSerializer else: response_serializer = TokenSerializer @@ -61,7 +61,7 @@ class LoginView(GenericAPIView): def login(self): self.user = self.serializer.validated_data['user'] - if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': + if getattr(settings, 'REST_USE_JWT', False): self.token = jwt_encode(self.user) else: self.token = create_token(self.token_model, self.user, @@ -73,7 +73,7 @@ class LoginView(GenericAPIView): def get_response(self): serializer_class = self.get_response_serializer() - if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'jwt': + if getattr(settings, 'REST_USE_JWT', False): data = { 'user': self.user, 'token': self.token From 958862741495412d02af84e8e1fa4d94c3063015 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 10:50:15 +0000 Subject: [PATCH 20/48] Missed a revert to REST_USE_KNOX --- rest_auth/registration/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 225ae0d..783ef5c 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -50,12 +50,11 @@ class RegisterView(CreateAPIView): 'token': self.token } return JWTSerializer(data).data - if getattr(settings, 'REST_AUTH_TOKEN_APP', False) is 'knox': + elif getattr(settings, 'REST_USE_KNOX', False): data = { 'token': self.token } return KnoxTokenSerializer(data).data - else: return TokenSerializer(self.token).data From b6c267cafc50f4aa5ee47e5d55303e33067a2671 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 10:50:33 +0000 Subject: [PATCH 21/48] Import KnoxTokenSerializer into views --- rest_auth/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rest_auth/views.py b/rest_auth/views.py index 0493a76..c42aac8 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -16,9 +16,10 @@ from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView from rest_framework.permissions import IsAuthenticated, AllowAny from .app_settings import ( - TokenSerializer, UserDetailsSerializer, LoginSerializer, - PasswordResetSerializer, PasswordResetConfirmSerializer, - PasswordChangeSerializer, JWTSerializer, create_token + TokenSerializer, KnoxTokenSerializer, UserDetailsSerializer, + LoginSerializer, PasswordResetSerializer, + PasswordResetConfirmSerializer, PasswordChangeSerializer, + JWTSerializer, create_token ) from .models import TokenModel from .utils import jwt_encode From 0db8c0dd952285b0535c65916d31ca5ed6d55942 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 12:04:42 +0000 Subject: [PATCH 22/48] Knox Login/Logout Rather than using the Knox views themselves, to respect Session and GET settings, and because Knox logout signal shouldn't be triggered --- rest_auth/views.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/rest_auth/views.py b/rest_auth/views.py index c42aac8..4eb7692 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -24,6 +24,12 @@ from .app_settings import ( from .models import TokenModel from .utils import jwt_encode +if getattr(settings, 'REST_USE_KNOX', False): + try: + from knox.auth import TokenAuthentication as KnoxTokenAuthentication + except ImportError: + raise ImportError("Install django-rest-knox to use REST_USE_KNOX = True") + sensitive_post_parameters_m = method_decorator( sensitive_post_parameters( 'password', 'old_password', 'new_password1', 'new_password2' @@ -55,6 +61,8 @@ class LoginView(GenericAPIView): def get_response_serializer(self): if getattr(settings, 'REST_USE_JWT', False): response_serializer = JWTSerializer + elif getattr(settings, 'REST_USE_KNOX', False): + response_serializer = KnoxTokenSerializer else: response_serializer = TokenSerializer return response_serializer @@ -81,6 +89,12 @@ class LoginView(GenericAPIView): } serializer = serializer_class(instance=data, context={'request': self.request}) + elif getattr(settings, 'REST_USE_KNOX', False): + data = { + 'token': self.token + } + serializer = serializer_class(instance=data, + context={'request': self.request}) else: serializer = serializer_class(instance=self.token, context={'request': self.request}) @@ -103,7 +117,11 @@ class LogoutView(APIView): Accepts/Returns nothing. """ - permission_classes = (AllowAny,) + if getattr(settings, 'REST_USE_KNOX', False): + authentication_classes = (KnoxTokenAuthentication,) + permission_classes = (IsAuthenticated,) + else: + permission_classes = (AllowAny,) def get(self, request, *args, **kwargs): if getattr(settings, 'ACCOUNT_LOGOUT_ON_GET', False): @@ -117,10 +135,13 @@ class LogoutView(APIView): return self.logout(request) def logout(self, request): - try: - request.user.auth_token.delete() - except (AttributeError, ObjectDoesNotExist): - pass + if getattr(settings, 'REST_USE_KNOX', False): + request._auth.delete() + else: + try: + request.user.auth_token.delete() + except (AttributeError, ObjectDoesNotExist): + pass django_logout(request) From fab9a7baa79b3be1212fed7a9eafb030b17d98fa Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 12:24:26 +0000 Subject: [PATCH 23/48] Try/except Knox Logout --- rest_auth/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rest_auth/views.py b/rest_auth/views.py index 4eb7692..4a676f1 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -135,13 +135,13 @@ class LogoutView(APIView): return self.logout(request) def logout(self, request): - if getattr(settings, 'REST_USE_KNOX', False): - request._auth.delete() - else: - try: + try: + if getattr(settings, 'REST_USE_KNOX', False): + request._auth.delete() + else: request.user.auth_token.delete() - except (AttributeError, ObjectDoesNotExist): - pass + except (AttributeError, ObjectDoesNotExist): + pass django_logout(request) From 053be68cdc4e997cc8b97b9b01993f14a0739d47 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 12:39:54 +0000 Subject: [PATCH 24/48] Removed blank line --- rest_auth/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 503cb76..28d4bc1 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -114,7 +114,6 @@ class TokenSerializer(serializers.ModelSerializer): """ Serializer for Token model. """ - class Meta: model = TokenModel fields = ('key',) From a262e59b0203649ffd2f6d351c2cd6979ef1480c Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 13:00:42 +0000 Subject: [PATCH 25/48] LogoutAllView deletes all Knox tokens --- rest_auth/urls.py | 9 +++++++-- rest_auth/views.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/rest_auth/urls.py b/rest_auth/urls.py index 7a35e9b..6578ba7 100644 --- a/rest_auth/urls.py +++ b/rest_auth/urls.py @@ -1,8 +1,8 @@ from django.conf.urls import url from rest_auth.views import ( - LoginView, LogoutView, UserDetailsView, PasswordChangeView, - PasswordResetView, PasswordResetConfirmView + LoginView, LogoutView, LogoutAllView, UserDetailsView, + PasswordChangeView, PasswordResetView, PasswordResetConfirmView ) urlpatterns = [ @@ -18,3 +18,8 @@ urlpatterns = [ url(r'^password/change/$', PasswordChangeView.as_view(), name='rest_password_change'), ] + +if getattr(settings, 'REST_USE_KNOX', False): + urlpatterns.append( + url(r'^logoutall/$' LogoutAllView.as_view(), name='rest_logout_all'), + ) diff --git a/rest_auth/views.py b/rest_auth/views.py index 4a676f1..147f987 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -149,6 +149,39 @@ class LogoutView(APIView): status=status.HTTP_200_OK) +class LogoutAllView(APIView): + """ + Calls Django logout method and deletes all the Knox tokens + assigned to the current User object. + + Accepts/Returns nothing. + """ + authentication_classes = (KnoxTokenAuthentication,) + permission_classes = (IsAuthenticated,) + + def get(self, request, *args, **kwargs): + if getattr(settings, 'ACCOUNT_LOGOUT_ON_GET', False): + response = self.logout(request) + else: + response = self.http_method_not_allowed(request, *args, **kwargs) + + return self.finalize_response(request, response, *args, **kwargs) + + def post(self, request): + return self.logout(request) + + def logout(self, request): + try: + request.user.auth_token_set.all().delete() + except (AttributeError, ObjectDoesNotExist): + pass + + django_logout(request) + + return Response({"detail": _("Successfully logged out.")}, + status=status.HTTP_200_OK) + + class UserDetailsView(RetrieveUpdateAPIView): """ Reads and updates UserModel fields From aa0b3caa8ba10ba84c30344c46e35d4853a30e1d Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 13:04:21 +0000 Subject: [PATCH 26/48] Extend urlpatterns --- rest_auth/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_auth/urls.py b/rest_auth/urls.py index 6578ba7..3debd02 100644 --- a/rest_auth/urls.py +++ b/rest_auth/urls.py @@ -20,6 +20,6 @@ urlpatterns = [ ] if getattr(settings, 'REST_USE_KNOX', False): - urlpatterns.append( + urlpatterns.extend([ url(r'^logoutall/$' LogoutAllView.as_view(), name='rest_logout_all'), - ) + ]) From b56e542e01f3ea6dd768e13238a6304e099287b5 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 13:10:51 +0000 Subject: [PATCH 27/48] Made Logout All clearer --- rest_auth/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_auth/views.py b/rest_auth/views.py index 147f987..eec6822 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -161,16 +161,16 @@ class LogoutAllView(APIView): def get(self, request, *args, **kwargs): if getattr(settings, 'ACCOUNT_LOGOUT_ON_GET', False): - response = self.logout(request) + response = self.logout_all(request) else: response = self.http_method_not_allowed(request, *args, **kwargs) return self.finalize_response(request, response, *args, **kwargs) def post(self, request): - return self.logout(request) + return self.logout_all(request) - def logout(self, request): + def logout_all(self, request): try: request.user.auth_token_set.all().delete() except (AttributeError, ObjectDoesNotExist): From f7cbbb084363edcbfaa6090d5c532ace77d530a2 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 13:11:05 +0000 Subject: [PATCH 28/48] Added logoutall to main urls list --- rest_auth/urls.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rest_auth/urls.py b/rest_auth/urls.py index 3debd02..eb6c67a 100644 --- a/rest_auth/urls.py +++ b/rest_auth/urls.py @@ -14,12 +14,8 @@ urlpatterns = [ url(r'^login/$', LoginView.as_view(), name='rest_login'), # URLs that require a user to be logged in with a valid session / token. url(r'^logout/$', LogoutView.as_view(), name='rest_logout'), + url(r'^logoutall/$' LogoutAllView.as_view(), name='rest_logout_all'), url(r'^user/$', UserDetailsView.as_view(), name='rest_user_details'), url(r'^password/change/$', PasswordChangeView.as_view(), name='rest_password_change'), ] - -if getattr(settings, 'REST_USE_KNOX', False): - urlpatterns.extend([ - url(r'^logoutall/$' LogoutAllView.as_view(), name='rest_logout_all'), - ]) From cddfc3d2ff72d730dd5c7a02acc846556f1572bf Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 13:13:13 +0000 Subject: [PATCH 29/48] It was missing a comma --- rest_auth/urls.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rest_auth/urls.py b/rest_auth/urls.py index eb6c67a..570b384 100644 --- a/rest_auth/urls.py +++ b/rest_auth/urls.py @@ -14,8 +14,12 @@ urlpatterns = [ url(r'^login/$', LoginView.as_view(), name='rest_login'), # URLs that require a user to be logged in with a valid session / token. url(r'^logout/$', LogoutView.as_view(), name='rest_logout'), - url(r'^logoutall/$' LogoutAllView.as_view(), name='rest_logout_all'), url(r'^user/$', UserDetailsView.as_view(), name='rest_user_details'), url(r'^password/change/$', PasswordChangeView.as_view(), name='rest_password_change'), ] + +if getattr(settings, 'REST_USE_KNOX', False): + urlpatterns.extend([ + url(r'^logoutall/$', LogoutAllView.as_view(), name='rest_logout_all'), + ]) From cc37193efda891dce79dd7e525a756207ffcabdb Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 13:14:28 +0000 Subject: [PATCH 30/48] Import settings --- rest_auth/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_auth/urls.py b/rest_auth/urls.py index 570b384..9f039e2 100644 --- a/rest_auth/urls.py +++ b/rest_auth/urls.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.conf.urls import url from rest_auth.views import ( From cb3c85991937a4d515dd762b4c05fb95378cd197 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 13:20:51 +0000 Subject: [PATCH 31/48] KnoxTokenAuthentication won't exist unless REST_USE_KNOX --- rest_auth/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_auth/views.py b/rest_auth/views.py index eec6822..84b2609 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -156,7 +156,8 @@ class LogoutAllView(APIView): Accepts/Returns nothing. """ - authentication_classes = (KnoxTokenAuthentication,) + if getattr(settings, 'REST_USE_KNOX', False): + authentication_classes = (KnoxTokenAuthentication,) permission_classes = (IsAuthenticated,) def get(self, request, *args, **kwargs): From 89dd206128a77d2508e629e2ee4ba463b550f302 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 15:43:26 +0000 Subject: [PATCH 32/48] Extend to Append --- rest_auth/urls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_auth/urls.py b/rest_auth/urls.py index 9f039e2..2d7b358 100644 --- a/rest_auth/urls.py +++ b/rest_auth/urls.py @@ -21,6 +21,6 @@ urlpatterns = [ ] if getattr(settings, 'REST_USE_KNOX', False): - urlpatterns.extend([ - url(r'^logoutall/$', LogoutAllView.as_view(), name='rest_logout_all'), - ]) + urlpatterns.append( + url(r'^logoutall/$', LogoutAllView.as_view(), name='rest_logout_all') + ) From abb0ae963eef23d848a441c565fc96e362e83fb5 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 16:00:58 +0000 Subject: [PATCH 33/48] Include User Details for Knox responses Knox includes User details after successful login. This should be replicated when using knox for Login and Registration. --- rest_auth/registration/views.py | 1 + rest_auth/serializers.py | 15 ++++++++------- rest_auth/views.py | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 783ef5c..cab45e8 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -52,6 +52,7 @@ class RegisterView(CreateAPIView): return JWTSerializer(data).data elif getattr(settings, 'REST_USE_KNOX', False): data = { + 'token': self.user, 'token': self.token } return KnoxTokenSerializer(data).data diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 28d4bc1..bf5cf01 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -119,13 +119,6 @@ class TokenSerializer(serializers.ModelSerializer): fields = ('key',) -class KnoxTokenSerializer(serializers.Serializer): - """ - Serializer for Knox AuthToken model. - """ - token = serializers.CharField() - - class UserDetailsSerializer(serializers.ModelSerializer): """ User model w/o password @@ -152,6 +145,14 @@ class JWTSerializer(serializers.Serializer): user = JWTUserDetailsSerializer() +class KnoxTokenSerializer(serializers.Serializer): + """ + Serializer for Knox AuthToken model. + """ + token = serializers.CharField() + user = JWTUserDetailsSerializer() + + class PasswordResetSerializer(serializers.Serializer): """ Serializer for requesting a password reset e-mail. diff --git a/rest_auth/views.py b/rest_auth/views.py index 84b2609..3ec81de 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -91,6 +91,7 @@ class LoginView(GenericAPIView): context={'request': self.request}) elif getattr(settings, 'REST_USE_KNOX', False): data = { + 'user': self.user, 'token': self.token } serializer = serializer_class(instance=data, From c1df248dbbad7472866c32999aaf7cb71fb3ebe2 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 16:19:02 +0000 Subject: [PATCH 34/48] Use custom User serializer with Knox as well as JWT --- rest_auth/registration/views.py | 2 +- rest_auth/serializers.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index cab45e8..4930e6f 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -52,7 +52,7 @@ class RegisterView(CreateAPIView): return JWTSerializer(data).data elif getattr(settings, 'REST_USE_KNOX', False): data = { - 'token': self.user, + 'token': user, 'token': self.token } return KnoxTokenSerializer(data).data diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index bf5cf01..15a6082 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -132,7 +132,7 @@ class UserDetailsSerializer(serializers.ModelSerializer): # Required to allow using custom USER_DETAILS_SERIALIZER in # JWTSerializer. Defining it here to avoid circular imports rest_auth_serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {}) -JWTUserDetailsSerializer = import_callable( +CustomUserDetailsSerializer = import_callable( rest_auth_serializers.get('USER_DETAILS_SERIALIZER', UserDetailsSerializer) ) @@ -142,15 +142,15 @@ class JWTSerializer(serializers.Serializer): Serializer for JWT authentication. """ token = serializers.CharField() - user = JWTUserDetailsSerializer() + user = CustomUserDetailsSerializer() class KnoxTokenSerializer(serializers.Serializer): """ - Serializer for Knox AuthToken model. + Serializer for Knox authentication. """ token = serializers.CharField() - user = JWTUserDetailsSerializer() + user = CustomUserDetailsSerializer() class PasswordResetSerializer(serializers.Serializer): From 1ce7b850d7f04f9f78a858df8c1029ecd682637a Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 17:29:16 +0000 Subject: [PATCH 35/48] Updated API endpoints documentation --- docs/api_endpoints.rst | 142 +++++++++++++++++++++++++++++------------ 1 file changed, 102 insertions(+), 40 deletions(-) diff --git a/docs/api_endpoints.rst b/docs/api_endpoints.rst index 46d2a05..c87670b 100644 --- a/docs/api_endpoints.rst +++ b/docs/api_endpoints.rst @@ -4,77 +4,139 @@ API endpoints Basic ----- -- /rest-auth/login/ (POST) +Typically, auth data is sent in the body, with a header ``Content-Type: application/x-www-form-urlencoded`` - - username - - email - - password +/rest-auth/login/ (POST) +************************ +**Request (standalone):** - Returns Token key +- ``username`` or ``email`` (email will be used to lookup username) +- ``password`` (required) -- /rest-auth/logout/ (POST) +**Request (using django-allauth):** - .. note:: ``ACCOUNT_LOGOUT_ON_GET = True`` to allow logout using GET - this is the exact same configuration from allauth. NOT recommended, see: http://django-allauth.readthedocs.io/en/latest/views.html#logout +- ``username`` (required when ``ACCOUNT_AUTHENTICATION_METHOD = 'username'`` or ``'username_email'``) +- ``email`` (required when ``ACCOUNT_AUTHENTICATION_METHOD = 'email'`` or ``'username_email'``) +- ``password`` (required) -- /rest-auth/password/reset/ (POST) +**Response:** - - email +- ``token`` +- ``user`` (when using django-rest-framework-jwt or django-rest-knox) -- /rest-auth/password/reset/confirm/ (POST) +/rest-auth/logout/ (POST) +************************* + +**Request (standalone):** - - uid - - token - - new_password1 - - new_password2 +- No values expected - .. note:: uid and token are sent in email after calling /rest-auth/password/reset/ +**Request (using django-rest-knox):** -- /rest-auth/password/change/ (POST) +- ``Authorization: Token TOKEN`` *(Header)* - - new_password1 - - new_password2 - - old_password +**Response:** - .. note:: ``OLD_PASSWORD_FIELD_ENABLED = True`` to use old_password. - .. note:: ``LOGOUT_ON_PASSWORD_CHANGE = False`` to keep the user logged in after password change +- No values -- /rest-auth/user/ (GET, PUT, PATCH) +.. note:: ``ACCOUNT_LOGOUT_ON_GET = True`` to allow logout using GET - this is the exact same configuration from allauth. NOT recommended, see: http://django-allauth.readthedocs.io/en/latest/views.html#logout - - username - - first_name - - last_name +/rest-auth/logoutall/ (POST) +**************************** - Returns pk, username, email, first_name, last_name +This endpoint deletes all Knox tokens, and will only be loaded when `REST_USE_KNOX = True`. + +| **Request (using django-rest-knox):** + +- `Authorization`: `Token TOKEN` (Header) + +**Response:** + +- No values + +.. note:: ``ACCOUNT_LOGOUT_ON_GET = True`` to allow logout using GET - this is the exact same configuration from allauth. NOT recommended, see: http://django-allauth.readthedocs.io/en/latest/views.html#logout + +/rest-auth/password/reset/ (POST) +********************************* + +- email + +/rest-auth/password/reset/confirm/ (POST) +***************************************** + +- uid +- token +- new_password1 +- new_password2 + +.. note:: uid and token are sent in email after calling /rest-auth/password/reset/ + +/rest-auth/password/change/ (POST) +********************************** +- new_password1 +- new_password2 +- old_password + +.. note:: ``OLD_PASSWORD_FIELD_ENABLED = True`` to use old_password. +.. note:: ``LOGOUT_ON_PASSWORD_CHANGE = False`` to keep the user logged in after password change + +/rest-auth/user/ (GET, PUT, PATCH) +********************************** +- username +- first_name +- last_name + +Returns pk, username, email, first_name, last_name Registration ------------ -- /rest-auth/registration/ (POST) +/rest-auth/registration/ (POST) +******************************* - - username - - password1 - - password2 - - email +**Request (using django-allauth):** -- /rest-auth/registration/verify-email/ (POST) +- ``username`` (required when ``ACCOUNT_AUTHENTICATION_METHOD = 'username'`` or ``'username_email'``, or when ``ACCOUNT_USERNAME_REQUIRED = True``) +- ``email`` (required when ``ACCOUNT_AUTHENTICATION_METHOD = 'email'`` or ``'username_email'``, or when ``ACCOUNT_EMAIL_REQUIRED = True``) +- ``password1`` (required) +- ``password2`` (required) - - key +**Response (using django-allauth):** +- No values + +**Response (using django-allauth and django-rest-knox)** + +- ``token`` +- ``user`` + +/rest-auth/registration/verify-email/ (POST) +******************************************** + +**Request (using django-allauth):** + +- ``key`` + +**Response (using django-allauth):** + +- No values Social Media Authentication --------------------------- -Basing on example from installation section :doc:`Installation ` +Based on the example from the installation section :doc:`Installation ` -- /rest-auth/facebook/ (POST) +/rest-auth/facebook/ (POST) +*************************** - - access_token - - code +- ``access_token`` +- ``code`` .. note:: ``access_token`` OR ``code`` can be used as standalone arguments, see https://github.com/Tivix/django-rest-auth/blob/master/rest_auth/registration/views.py -- /rest-auth/twitter/ (POST) +/rest-auth/twitter/ (POST) +************************** - - access_token - - token_secret +- ``access_token`` +- ``token_secret`` From c341b1b0ffdf848f2bfaba8166d91acd46a2ad6e Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 18:00:02 +0000 Subject: [PATCH 36/48] Renamed KnoxTokenSerializer to KnoxSerializer As it now returns both token and user --- rest_auth/app_settings.py | 6 +++--- rest_auth/serializers.py | 2 +- rest_auth/views.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index 59d3456..e0ab429 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -7,7 +7,7 @@ else: from rest_auth.serializers import ( TokenSerializer as DefaultTokenSerializer, - KnoxTokenSerializer as DefaultKnoxTokenSerializer, + KnoxSerializer as DefaultKnoxSerializer, JWTSerializer as DefaultJWTSerializer, UserDetailsSerializer as DefaultUserDetailsSerializer, LoginSerializer as DefaultLoginSerializer, @@ -27,8 +27,8 @@ TokenSerializer = import_callable( JWTSerializer = import_callable( serializers.get('JWT_SERIALIZER', DefaultJWTSerializer)) -KnoxTokenSerializer = import_callable( - serializers.get('KNOX_TOKEN_SERIALIZER', DefaultKnoxTokenSerializer)) +KnoxSerializer = import_callable( + serializers.get('KNOX_TOKEN_SERIALIZER', DefaultKnoxSerializer)) UserDetailsSerializer = import_callable( serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 15a6082..a1ddbc8 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -145,7 +145,7 @@ class JWTSerializer(serializers.Serializer): user = CustomUserDetailsSerializer() -class KnoxTokenSerializer(serializers.Serializer): +class KnoxSerializer(serializers.Serializer): """ Serializer for Knox authentication. """ diff --git a/rest_auth/views.py b/rest_auth/views.py index 3ec81de..ffb2f67 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -16,7 +16,7 @@ from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView from rest_framework.permissions import IsAuthenticated, AllowAny from .app_settings import ( - TokenSerializer, KnoxTokenSerializer, UserDetailsSerializer, + TokenSerializer, KnoxSerializer, UserDetailsSerializer, LoginSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer, PasswordChangeSerializer, JWTSerializer, create_token @@ -62,7 +62,7 @@ class LoginView(GenericAPIView): if getattr(settings, 'REST_USE_JWT', False): response_serializer = JWTSerializer elif getattr(settings, 'REST_USE_KNOX', False): - response_serializer = KnoxTokenSerializer + response_serializer = KnoxSerializer else: response_serializer = TokenSerializer return response_serializer From b3d85f23b26b8f67a2b6fbc2610c6dec37a93212 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 18:01:39 +0000 Subject: [PATCH 37/48] Added configuration and installation documentation --- docs/configuration.rst | 6 +++++- docs/installation.rst | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 1f5b40f..fdae9c4 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 ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.JWTSerializer`` + - KNOX_SERIALIZER - (Using REST_USE_KNOX=True) response for successful authentication in ``rest_auth.views.LoginView`` and successful registration in ``rest_auth.registration.views.RegisterView`` (using ``django-allauth`` and ``ACCOUNT_EMAIL_VERIFICATION = 'optional' or 'none'``), default value ``rest_auth.serializers.KnoxSerializer`` + - USER_DETAILS_SERIALIZER - serializer class in ``rest_auth.views.UserDetailsView``, default value ``rest_auth.serializers.UserDetailsSerializer`` - PASSWORD_RESET_SERIALIZER - serializer class in ``rest_auth.views.PasswordResetView``, default value ``rest_auth.serializers.PasswordResetSerializer`` @@ -48,6 +50,8 @@ Configuration - **REST_USE_JWT** - Enable JWT Authentication instead of Token/Session based. This is built on top of django-rest-framework-jwt http://getblimp.github.io/django-rest-framework-jwt/, which must also be installed. (default: False) +- **REST_USE_KNOX** - Use Knox token authentication instead of the built-in Django-Rest-Framework token authentication. Knox makes some significant security improvements, and supports multiple tokens per user. https://github.com/James1345/django-rest-knox/ must be installed. Not compatible with ``REST_USE_JWT`` (default: False) + - **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 +- **LOGOUT_ON_PASSWORD_CHANGE** - set to False if you want to keep the current user logged in after a password change (default: False) diff --git a/docs/installation.rst b/docs/installation.rst index 6144011..730a900 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -153,7 +153,7 @@ JWT Support (optional) By default, ``django-rest-auth`` uses Django's Token-based authentication. If you want to use JWT authentication, you need to install the following: -1. Install ``django-rest-framework-jwt`` http://getblimp.github.io/django-rest-framework-jwt/ . Right now this is the only supported JWT library. +1. Install ``django-rest-framework-jwt`` http://getblimp.github.io/django-rest-framework-jwt/ . 2. Add the following to your settings @@ -161,3 +161,19 @@ By default, ``django-rest-auth`` uses Django's Token-based authentication. If yo REST_USE_JWT = True + +Knox (optional) +--------------- +By default, ``django-rest-auth`` uses Django's Token-based authentication. ``django-rest-knox`` provides more secure token authentication with additional features, including multiple tokens per user. + +Knox and JWT cannot currently be used simultaneously. + +1. Install ``django-rest-knox`` https://james1345.github.io/django-rest-knox/installation/ . + +2. Configure ``django-rest-knox`` https://james1345.github.io/django-rest-knox/settings/ . ``REST_KNOX['USER_SERIALIZER']`` will not be used. + +3. Add the following to your settings + +.. code-block:: python + + REST_USE_KNOX = True From acb6a0d342405d34c6837d46a09f29b351210113 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 18:02:55 +0000 Subject: [PATCH 38/48] Missed a KnoxTokenSerializer to KnoxSerializer, --- rest_auth/registration/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 4930e6f..5d7c77d 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -16,7 +16,7 @@ from allauth.account import app_settings as allauth_settings from rest_auth.app_settings import (TokenSerializer, JWTSerializer, - KnoxTokenSerializer, + KnoxSerializer, create_token) from rest_auth.models import TokenModel from rest_auth.registration.serializers import (SocialLoginSerializer, From 2059de79dbb9e697c617c4f07a3633547f30bf0c Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Wed, 25 Jan 2017 18:03:23 +0000 Subject: [PATCH 39/48] Installed Knox for tests --- rest_auth/tests/requirements.pip | 1 + rest_auth/tests/settings.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_auth/tests/requirements.pip b/rest_auth/tests/requirements.pip index de66892..eb10284 100644 --- a/rest_auth/tests/requirements.pip +++ b/rest_auth/tests/requirements.pip @@ -2,3 +2,4 @@ django-allauth>=0.19.1 responses>=0.3.0 flake8==2.4.0 djangorestframework-jwt>=1.7.2 +django-rest-knox>=2.3.0 diff --git a/rest_auth/tests/settings.py b/rest_auth/tests/settings.py index 060cc89..6eb5e86 100644 --- a/rest_auth/tests/settings.py +++ b/rest_auth/tests/settings.py @@ -88,7 +88,9 @@ INSTALLED_APPS = [ 'rest_auth', 'rest_auth.registration', - 'rest_framework_jwt' + 'rest_framework_jwt', + + 'knox' ] SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd" From 638f53c65ed0d93045875bccf47a47ea88e7dbc8 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 12:14:31 +0000 Subject: [PATCH 40/48] Ignore Virtual Environment files --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 7d8d699..0684826 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,9 @@ db.sqlite3 # IntelliJ IDE files .idea + +# Virtual Environments +.vagrant/ +bash/ +cookbooks/ +tmp/ From 869de7d4bbc4033eeda1cb82e961b1c2ccd46924 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 12:15:01 +0000 Subject: [PATCH 41/48] Add missing dependencies for test setup --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 32ea6b7..6ff66d2 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,8 @@ setup( tests_require=[ 'responses>=0.5.0', 'django-allauth>=0.25.0', + 'djangorestframework-jwt>=1.9.0', + 'django-rest-knox>=2.3.0' ], test_suite='runtests.runtests', include_package_data=True, From 5e790098ed04a969d13de4c6a52501230cf3ace3 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 12:15:49 +0000 Subject: [PATCH 42/48] Bash shouldn't be there --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0684826..4b34032 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,5 @@ db.sqlite3 # Virtual Environments .vagrant/ -bash/ cookbooks/ tmp/ From fb8adc0aacbcdc0cb2f6c36758074b478c916b2f Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 18:27:13 +0000 Subject: [PATCH 43/48] Simplified logic for selections based on REST_USE_KNOX --- rest_auth/app_settings.py | 7 +------ rest_auth/models.py | 8 +------- rest_auth/registration/views.py | 4 +++- rest_auth/utils.py | 8 ++++++-- rest_auth/views.py | 4 +++- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index e0ab429..3843665 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -1,10 +1,5 @@ from django.conf import settings -if getattr(settings, 'REST_USE_KNOX', False): - from .utils import create_knox_token as default_create_token -else: - from .utils import default_create_token - from rest_auth.serializers import ( TokenSerializer as DefaultTokenSerializer, KnoxSerializer as DefaultKnoxSerializer, @@ -14,7 +9,7 @@ from rest_auth.serializers import ( PasswordResetSerializer as DefaultPasswordResetSerializer, PasswordResetConfirmSerializer as DefaultPasswordResetConfirmSerializer, PasswordChangeSerializer as DefaultPasswordChangeSerializer) -from .utils import import_callable +from .utils import import_callable, default_create_token create_token = import_callable( getattr(settings, 'REST_AUTH_TOKEN_CREATOR', default_create_token)) diff --git a/rest_auth/models.py b/rest_auth/models.py index 13d4f74..a132f9c 100644 --- a/rest_auth/models.py +++ b/rest_auth/models.py @@ -1,12 +1,6 @@ from django.conf import settings -if getattr(settings, 'REST_USE_KNOX', False): - try: - from knox.models import AuthToken as DefaultTokenModel - except ImportError: - raise ImportError("Install django-rest-knox to use REST_USE_KNOX = True") -else: - from rest_framework.authtoken.models import Token as DefaultTokenModel +from rest_framework.authtoken.models import Token as DefaultTokenModel from .utils import import_callable diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 5d7c77d..4f3beee 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -21,7 +21,7 @@ from rest_auth.app_settings import (TokenSerializer, from rest_auth.models import TokenModel from rest_auth.registration.serializers import (SocialLoginSerializer, VerifyEmailSerializer) -from rest_auth.utils import jwt_encode +from rest_auth.utils import create_knox_token, jwt_encode from rest_auth.views import LoginView from .app_settings import RegisterSerializer @@ -73,6 +73,8 @@ class RegisterView(CreateAPIView): user = serializer.save(self.request) if getattr(settings, 'REST_USE_JWT', False): self.token = jwt_encode(user) + elif getattr(settings, 'REST_USE_KNOX', False): + self.token = create_knox_token(user) else: self.token = create_token(self.token_model, user, serializer) diff --git a/rest_auth/utils.py b/rest_auth/utils.py index dbd7ee5..bf147aa 100644 --- a/rest_auth/utils.py +++ b/rest_auth/utils.py @@ -16,8 +16,12 @@ def default_create_token(token_model, user, serializer): return token -def create_knox_token(token_model, user, serializer): - token = token_model.objects.create(user=user) +def create_knox_token(user): + try: + from knox.models import AuthToken + except ImportError: + raise ImportError("django-rest-knox needs to be installed") + token = AuthToken.objects.create(user=user) return token diff --git a/rest_auth/views.py b/rest_auth/views.py index ffb2f67..3544edd 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -22,7 +22,7 @@ from .app_settings import ( JWTSerializer, create_token ) from .models import TokenModel -from .utils import jwt_encode +from .utils import create_knox_token, jwt_encode if getattr(settings, 'REST_USE_KNOX', False): try: @@ -72,6 +72,8 @@ class LoginView(GenericAPIView): if getattr(settings, 'REST_USE_JWT', False): self.token = jwt_encode(self.user) + elif getattr(settings, 'REST_USE_KNOX', False): + self.token = create_knox_token(self.user) else: self.token = create_token(self.token_model, self.user, self.serializer) From 84aa8965d7637785adee96ccc26dde30c1c82666 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 18:27:41 +0000 Subject: [PATCH 44/48] Added logout all URL for tests --- rest_auth/tests/urls.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rest_auth/tests/urls.py b/rest_auth/tests/urls.py index 6371218..e5f9d24 100644 --- a/rest_auth/tests/urls.py +++ b/rest_auth/tests/urls.py @@ -8,6 +8,7 @@ from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter from rest_framework.decorators import api_view from rest_auth.urls import urlpatterns +from rest_auth.views import LogoutAllView from rest_auth.registration.views import SocialLoginView from rest_auth.social_serializers import TwitterLoginSerializer @@ -49,5 +50,6 @@ urlpatterns += [ url(r'^social-login/twitter/$', TwitterLogin.as_view(), name='tw_login'), url(r'^social-login/twitter-no-view/$', twitter_login_view, name='tw_login_no_view'), url(r'^social-login/twitter-no-adapter/$', TwitterLoginNoAdapter.as_view(), name='tw_login_no_adapter'), - url(r'^accounts/', include('allauth.socialaccount.urls')) + url(r'^accounts/', include('allauth.socialaccount.urls')), + url(r'^logoutall/$', LogoutAllView.as_view(), name='rest_logout_all'), ] From bab9a18bcbf00b5011c4b8a7c99880f5b9d8916e Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 18:28:16 +0000 Subject: [PATCH 45/48] Wrong serializer and key --- rest_auth/registration/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 4f3beee..c8a6d83 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -52,10 +52,10 @@ class RegisterView(CreateAPIView): return JWTSerializer(data).data elif getattr(settings, 'REST_USE_KNOX', False): data = { - 'token': user, + 'user': user, 'token': self.token } - return KnoxTokenSerializer(data).data + return KnoxSerializer(data).data else: return TokenSerializer(self.token).data From 5961c48a0790afc0692a01150838f890e3301863 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 18:44:09 +0000 Subject: [PATCH 46/48] Added some knox tests --- rest_auth/tests/test_api.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index a24e9f0..e46726a 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -148,6 +148,24 @@ class APITestCase1(TestCase, BaseAPITestCase): self.assertEqual('token' in self.response.json.keys(), True) self.token = self.response.json['token'] + @override_settings(REST_USE_KNOX=True, REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication')},) + def test_login_knox(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + get_user_model().objects.create_user(self.USERNAME, '', self.PASS) + + self.post(self.login_url, data=payload, status_code=200) + self.assertEqual('token' in self.response.json.keys(), True) + self.token = self.response.json['token'] + self.assertEqual('user' in self.response.json.keys(), True) + self.user = self.response.json['user'] + + self.post(self.login_url, data=payload, status_code=200) + self.assertNotEqual(self.token, self.response.json['token']) + def test_login_by_email(self): # starting test without allauth app settings.INSTALLED_APPS.remove('allauth') @@ -382,6 +400,21 @@ class APITestCase1(TestCase, BaseAPITestCase): user = get_user_model().objects.get(pk=user.pk) self.assertEqual(user.email, self.response.json['email']) + @override_settings(REST_USE_KNOX=True, REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication')},) + def test_user_details_using_knox(self): + user = get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS) + payload = { + "username": self.USERNAME, + "password": self.PASS + } + self.post(self.login_url, data=payload, status_code=200) + self.token = self.response.json['token'] + self.get(self.user_url, status_code=200) + self.patch(self.user_url, data=self.BASIC_USER_DATA, status_code=200) + user = get_user_model().objects.get(pk=user.pk) + self.assertEqual(user.email, self.response.json['email']) + def test_registration(self): user_count = get_user_model().objects.all().count() @@ -411,6 +444,21 @@ class APITestCase1(TestCase, BaseAPITestCase): self._login() self._logout() + @override_settings(REST_USE_KNOX=True, REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication')},) + def test_registration_with_knox(self): + user_count = get_user_model().objects.all().count() + + self.post(self.register_url, data={}, status_code=400) + + result = self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201) + self.assertIn('token', result.data) + self.assertIn('user', result.data) + self.assertEqual(get_user_model().objects.all().count(), user_count + 1) + + self._login() + self._logout() + def test_registration_with_invalid_password(self): data = self.REGISTRATION_DATA.copy() data['password2'] = 'foobar' From eb2684fd2e0c0ad470f8609afe07eb79e5277660 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 18:44:31 +0000 Subject: [PATCH 47/48] Stuggling with the logout tests... --- rest_auth/tests/settings.py | 9 +++++++ rest_auth/tests/test_api.py | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/rest_auth/tests/settings.py b/rest_auth/tests/settings.py index 6eb5e86..0ef7f6c 100644 --- a/rest_auth/tests/settings.py +++ b/rest_auth/tests/settings.py @@ -66,6 +66,15 @@ REST_FRAMEWORK = { ) } +REST_FRAMEWORK_KNOX = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'knox.auth.TokenAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), +} + INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index e46726a..beb4563 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -1,15 +1,26 @@ from django.core.urlresolvers import reverse from django.test import TestCase, override_settings + from django.contrib.auth import get_user_model from django.core import mail from django.conf import settings from django.utils.encoding import force_text +from django.utils.six.moves import reload_module from rest_framework import status +from rest_framework.test import APIClient +from rest_framework import views as drf_views + +from unittest.mock import patch + from allauth.account import app_settings as account_app_settings +from knox.models import AuthToken + from .test_base import BaseAPITestCase +from .settings import REST_FRAMEWORK_KNOX +client = APIClient() @override_settings(ROOT_URLCONF="tests.urls") class APITestCase1(TestCase, BaseAPITestCase): """ @@ -541,3 +552,43 @@ class APITestCase1(TestCase, BaseAPITestCase): self.post(self.login_url, data=payload, status_code=status.HTTP_200_OK) self.get(self.logout_url, status_code=status.HTTP_405_METHOD_NOT_ALLOWED) + + # @override_settings(REST_USE_KNOX=True, REST_FRAMEWORK = { + # 'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication')},) + def test_logout_knox(self): + with override_settings(REST_USE_KNOX=True, REST_FRAMEWORK=REST_FRAMEWORK_KNOX): + reload_module(drf_views) + payload = { + "username": self.USERNAME, + "password": self.PASS + } + + get_user_model().objects.create_user(self.USERNAME, '', self.PASS) + + self.client = APIClient() + + response = self.client.post(self.login_url, data=payload, status_code=status.HTTP_200_OK) + self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % response.data['token'])) + self.client.post(self.logout_url, status_code=status.HTTP_200_OK) + self.assertEqual(AuthToken.objects.count(), 0) + reload_module(drf_views) + + @override_settings(REST_USE_KNOX=True, REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication')},) + def test_logout_all_knox(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + + get_user_model().objects.create_user(self.USERNAME, '', self.PASS) + + self.logout_all_url = reverse('rest_logout_all') + + self.client = APIClient() + + self.client.post(self.login_url, data=payload, status_code=status.HTTP_200_OK) + response = self.client.post(self.login_url, data=payload, status_code=status.HTTP_200_OK) + self.client.credentials(HTTP_AUTHORIZATION=('Token %s' % response.data['token'])) + self.client.post(self.logout_all_url, status_code=status.HTTP_200_OK) + self.assertEqual(AuthToken.objects.count(), 0) From ad61169dfafc36e7b62f6e96f46076bf51dfb284 Mon Sep 17 00:00:00 2001 From: Daniel Stanton Date: Thu, 26 Jan 2017 19:11:07 +0000 Subject: [PATCH 48/48] removed APIClient dupe --- rest_auth/tests/test_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index beb4563..2b4e122 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -20,7 +20,6 @@ from .test_base import BaseAPITestCase from .settings import REST_FRAMEWORK_KNOX -client = APIClient() @override_settings(ROOT_URLCONF="tests.urls") class APITestCase1(TestCase, BaseAPITestCase): """