From 19e234d1dc3cfde37e3494faad4fd1ad29973cd3 Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Mon, 4 Jan 2016 12:45:33 -0500 Subject: [PATCH 01/52] * Added support for REST_USE_JWT * Added JWTSerializer * Added JWT encoding support, based on django-rest-framework-jwt * Tests for JWT authentication --- docs/configuration.rst | 4 +++- rest_auth/app_settings.py | 4 ++++ rest_auth/registration/views.py | 38 +++++++++++++++++++++++++------ rest_auth/serializers.py | 7 +++++- rest_auth/tests/requirements.pip | 1 + rest_auth/tests/settings.py | 11 ++++++++- rest_auth/tests/test_api.py | 30 ++++++++++++++++++++++++ rest_auth/tests/test_base.py | 5 +++- rest_auth/tests/test_social.py | 26 +++++++++++++++++++++ rest_auth/utils.py | 12 ++++++++++ rest_auth/views.py | 39 ++++++++++++++++++++++++-------- 11 files changed, 157 insertions(+), 20 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 282f326..040c1cf 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -29,10 +29,12 @@ Configuration ... } - - **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True) - **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 + +- **REST_USE_JWT** - If enabled, this will use `django-rest-framework-jwt ` as a backend, and instead of session based tokens or Social Login keys, it will return a JWT. + diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index e0340b7..b52064f 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, + JWTSerializer as DefaultJWTSerializer, UserDetailsSerializer as DefaultUserDetailsSerializer, LoginSerializer as DefaultLoginSerializer, PasswordResetSerializer as DefaultPasswordResetSerializer, @@ -15,6 +16,9 @@ serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {}) TokenSerializer = import_callable( serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer)) +JWTSerializer = import_callable( + serializers.get('JWT_SERIALIZER', DefaultJWTSerializer)) + UserDetailsSerializer = import_callable( serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer) ) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index e700706..ce68a86 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -1,4 +1,6 @@ from django.http import HttpRequest +from django.conf import settings + from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import AllowAny @@ -13,6 +15,8 @@ from rest_auth.app_settings import TokenSerializer from rest_auth.registration.serializers import SocialLoginSerializer from rest_auth.views import LoginView +from rest_auth.utils import jwt_encode + class RegisterView(APIView, SignupView): """ @@ -28,7 +32,12 @@ class RegisterView(APIView, SignupView): permission_classes = (AllowAny,) allowed_methods = ('POST', 'OPTIONS', 'HEAD') token_model = Token - serializer_class = TokenSerializer + + def get_serializer_class(self): + if getattr(settings, 'REST_USE_JWT', False): + return JWTSerializer + else: + return TokenSerializer def get(self, *args, **kwargs): return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) @@ -38,9 +47,14 @@ class RegisterView(APIView, SignupView): def form_valid(self, form): self.user = form.save(self.request) - self.token, created = self.token_model.objects.get_or_create( - user=self.user - ) + + if getattr(settings, 'REST_USE_JWT', False): + self.token = jwt_encode(self.user) + + else: + self.token, created = self.token_model.objects.get_or_create( + user=self.user + ) if isinstance(self.request, HttpRequest): request = self.request else: @@ -65,9 +79,19 @@ class RegisterView(APIView, SignupView): return self.get_response_with_errors() def get_response(self): - # serializer = self.user_serializer_class(instance=self.user) - serializer = self.serializer_class(instance=self.token, - context={'request': self.request}) + serializer_class = self.get_serializer_class() + + if getattr(settings, 'REST_USE_JWT', False): + data = { + 'user': self.user, + 'token': self.token + } + serializer = serializer_class(instance=data, + context={'request': self.request}) + else: + serializer = serializer_class(instance=self.token, + context={'request': self.request}) + return Response(serializer.data, status=status.HTTP_201_CREATED) def get_response_with_errors(self): diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index a2d1a82..1145826 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -87,7 +87,6 @@ class TokenSerializer(serializers.ModelSerializer): model = Token fields = ('key',) - class UserDetailsSerializer(serializers.ModelSerializer): """ @@ -98,6 +97,12 @@ class UserDetailsSerializer(serializers.ModelSerializer): fields = ('username', 'email', 'first_name', 'last_name') read_only_fields = ('email', ) +class JWTSerializer(serializers.Serializer): + """ + Serializer for JWT authentication. + """ + token = serializers.CharField() + user = UserDetailsSerializer() class PasswordResetSerializer(serializers.Serializer): diff --git a/rest_auth/tests/requirements.pip b/rest_auth/tests/requirements.pip index bb8d844..4040970 100644 --- a/rest_auth/tests/requirements.pip +++ b/rest_auth/tests/requirements.pip @@ -1,3 +1,4 @@ django-allauth>=0.19.1 responses>=0.3.0 flake8==2.4.0 +djangorestframework-jwt>=1.7.2 \ No newline at end of file diff --git a/rest_auth/tests/settings.py b/rest_auth/tests/settings.py index b09b496..aab38d3 100644 --- a/rest_auth/tests/settings.py +++ b/rest_auth/tests/settings.py @@ -45,6 +45,13 @@ TEMPLATE_CONTEXT_PROCESSORS = [ "allauth.socialaccount.context_processors.socialaccount", ] +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', + ) +} + INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', @@ -64,7 +71,9 @@ INSTALLED_APPS = [ 'rest_framework.authtoken', 'rest_auth', - 'rest_auth.registration' + 'rest_auth.registration', + + 'rest_framework_jwt' ] SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd" diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index b64cf8c..3f844f9 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -90,6 +90,19 @@ class APITestCase1(TestCase, BaseAPITestCase): # test empty payload self.post(self.login_url, data={}, status_code=400) + @override_settings(REST_USE_JWT=True) + def test_login_jwt(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + # no users in db so it should throw an error + user = get_user_model().objects.create_user(self.USERNAME, '', self.PASS) + self.post(self.login_url, data=payload, status_code=200) + self.assertEqual('user' in self.response.json.keys(), True) + self.assertEqual('token' in self.response.json.keys(), True) + + def test_password_change(self): login_payload = { "username": self.USERNAME, @@ -258,6 +271,23 @@ 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) + def test_user_details_jwt(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.first_name, self.response.json['first_name']) + self.assertEqual(user.last_name, self.response.json['last_name']) + self.assertEqual(user.email, self.response.json['email']) + def test_registration(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 ed8ffeb..97dfa31 100644 --- a/rest_auth/tests/test_base.py +++ b/rest_auth/tests/test_base.py @@ -37,7 +37,10 @@ class BaseAPITestCase(object): # check_headers = kwargs.pop('check_headers', True) if hasattr(self, 'token'): - kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token + if getattr(settings, 'REST_USE_JWT', False): + kwargs['HTTP_AUTHORIZATION'] = 'JWT %s' % self.token + else: + kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token self.response = request_func(*args, **kwargs) is_json = bool( diff --git a/rest_auth/tests/test_social.py b/rest_auth/tests/test_social.py index 0bb10f5..ad43b7d 100644 --- a/rest_auth/tests/test_social.py +++ b/rest_auth/tests/test_social.py @@ -126,3 +126,29 @@ class TestSocialAuth(TestCase, BaseAPITestCase): self.post(self.fb_login_url, data=payload, status_code=200) self.assertIn('key', self.response.json.keys()) + + @responses.activate + @override_settings( + 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 + responses.add( + responses.GET, + self.graph_api_url, + body=resp_body, + status=200, + content_type='application/json' + ) + + users_count = get_user_model().objects.all().count() + payload = { + 'access_token': 'abc123' + } + + self.post(self.fb_login_url, data=payload, status_code=200) + self.assertIn('token', self.response.json.keys()) + self.assertIn('user', self.response.json.keys()) + + self.assertEqual(get_user_model().objects.all().count(), users_count + 1) + diff --git a/rest_auth/utils.py b/rest_auth/utils.py index a32da60..8b6e8c1 100644 --- a/rest_auth/utils.py +++ b/rest_auth/utils.py @@ -9,3 +9,15 @@ def import_callable(path_or_callable): assert isinstance(path_or_callable, string_types) package, attr = path_or_callable.rsplit('.', 1) return getattr(import_module(package), attr) + +def jwt_encode(user): + try: + from rest_framework_jwt.settings import api_settings + except ImportError: + raise ImportError('rest_framework_jwt needs to be installed') + + jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER + jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER + + payload = jwt_payload_handler(user) + return jwt_encode_handler(payload) diff --git a/rest_auth/views.py b/rest_auth/views.py index 3af1557..16c6439 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -13,9 +13,11 @@ from rest_framework.generics import RetrieveUpdateAPIView from .app_settings import ( TokenSerializer, UserDetailsSerializer, LoginSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer, - PasswordChangeSerializer + PasswordChangeSerializer, JWTSerializer ) +from .utils import jwt_encode + class LoginView(GenericAPIView): @@ -31,19 +33,38 @@ class LoginView(GenericAPIView): permission_classes = (AllowAny,) serializer_class = LoginSerializer token_model = Token - response_serializer = TokenSerializer + + def get_response_serializer(self): + if getattr(settings, 'REST_USE_JWT', False): + response_serializer = JWTSerializer + else: + response_serializer = TokenSerializer + return response_serializer def login(self): self.user = self.serializer.validated_data['user'] - self.token, created = self.token_model.objects.get_or_create( - user=self.user) - if getattr(settings, 'REST_SESSION_LOGIN', True): - login(self.request, self.user) + + if getattr(settings, 'REST_USE_JWT', False): + self.token = jwt_encode(self.user) + else: + self.token, created = self.token_model.objects.get_or_create( + user=self.user) + if getattr(settings, 'REST_SESSION_LOGIN', True): + login(self.request, self.user) def get_response(self): - return Response( - self.response_serializer(self.token).data, status=status.HTTP_200_OK - ) + serializer_class = self.get_response_serializer() + + if getattr(settings, 'REST_USE_JWT', False): + data = { + 'user': self.user, + 'token': self.token + } + serializer = serializer_class(instance=data) + else: + serializer = serializer_class(instance=self.token) + + return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request, *args, **kwargs): self.serializer = self.get_serializer(data=self.request.data) From 317db1b811d633699ebe3006221b417a1b072e7a Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Mon, 4 Jan 2016 13:57:38 -0500 Subject: [PATCH 02/52] Bump version --- docs/changelog.rst | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 735b936..41e982d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,10 @@ Changelog ========= +0.7.0 +----- +- added support for django-rest-framework-jwt + 0.6.0 ----- - dropped support for Python 2.6 diff --git a/setup.py b/setup.py index 6b61e1c..bb83cde 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ f.close() setup( name='django-rest-auth', - version='0.6.0', + version='0.7.0', author='Sumit Chachra', author_email='chachra@tivix.com', url='http://github.com/Tivix/django-rest-auth', From 70a4dc9a13af6ecb1cd99681f056a05c0c780c84 Mon Sep 17 00:00:00 2001 From: Mateus Caruccio Date: Sat, 9 Jan 2016 01:11:35 -0200 Subject: [PATCH 03/52] Allow logout on GET --- docs/api_endpoints.rst | 4 +++- rest_auth/tests/test_api.py | 26 ++++++++++++++++++++++++++ rest_auth/views.py | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/docs/api_endpoints.rst b/docs/api_endpoints.rst index 1f9660f..2ff23d2 100644 --- a/docs/api_endpoints.rst +++ b/docs/api_endpoints.rst @@ -11,7 +11,9 @@ Basic - password (string) -- /rest-auth/logout/ (POST) +- /rest-auth/logout/ (POST, GET) + + .. note:: ``ACCOUNT_LOGOUT_ON_GET = True`` to allow logout using GET (this is the exact same conf from allauth) - /rest-auth/password/reset/ (POST) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index 222b3a7..63a941c 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -372,3 +372,29 @@ class APITestCase1(TestCase, BaseAPITestCase): # try to login again self._login() self._logout() + + @override_settings(ACCOUNT_LOGOUT_ON_GET=True) + def test_logout_on_get(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + + # create user + user = get_user_model().objects.create_user(self.USERNAME, '', self.PASS) + + self.post(self.login_url, data=payload, status_code=200) + self.get(self.logout_url, status=status.HTTP_200_OK) + + @override_settings(ACCOUNT_LOGOUT_ON_GET=False) + def test_logout_on_post_only(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + + # create user + user = get_user_model().objects.create_user(self.USERNAME, '', self.PASS) + + 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) diff --git a/rest_auth/views.py b/rest_auth/views.py index 3bb6f6b..6f2ce99 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -9,6 +9,8 @@ from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.generics import RetrieveUpdateAPIView +from allauth.account import app_settings as allauth_settings + from .app_settings import ( TokenSerializer, UserDetailsSerializer, LoginSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer, @@ -61,7 +63,23 @@ class LogoutView(APIView): """ permission_classes = (AllowAny,) + def get(self, request, *args, **kwargs): + try: + if allauth_settings.LOGOUT_ON_GET: + response = self.logout(request) + else: + response = self.http_method_not_allowed(request, *args, **kwargs) + except Exception as exc: + response = self.handle_exception(exc) + + return self.finalize_response(request, response, *args, **kwargs) + self.response = self.finalize_response(request, response, *args, **kwargs) + return self.response + def post(self, request): + return self.logout(request) + + def logout(self, request): try: request.user.auth_token.delete() except (AttributeError, ObjectDoesNotExist): From 1af16ae7ba68300ee562d8dc1685af7ba73331fa Mon Sep 17 00:00:00 2001 From: Tevin Joseph K O Date: Wed, 13 Jan 2016 12:43:12 +0530 Subject: [PATCH 04/52] Added a Serializer for Twitter oauth Added a serializer for twitter OAuth to work. If you are not using this it will cause an error ('TwitterOAuthAdapter' object has no attribute 'parse_token'). It happens because method parse_token() is implemented in OAuth2Adapter, but Twitter uses OAuth 1.0, so TwitterOAuthAdapter inherits from OAuthAdapter, which doesn't have parse_token() method. Example usage is given below: class TwitterLogin(LoginView): serializer_class = TwitterLoginSerializer adapter_class = TwitterOAuthAdapter --- rest_auth/social_serializers.py | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 rest_auth/social_serializers.py diff --git a/rest_auth/social_serializers.py b/rest_auth/social_serializers.py new file mode 100644 index 0000000..2aee4ea --- /dev/null +++ b/rest_auth/social_serializers.py @@ -0,0 +1,78 @@ +from django.http import HttpRequest +from rest_framework import serializers +from requests.exceptions import HTTPError +# Import is needed only if we are using social login, in which +# case the allauth.socialaccount will be declared +try: + from allauth.socialaccount.helpers import complete_social_login +except ImportError: + pass + +from allauth.socialaccount.models import SocialToken + + +class TwitterLoginSerializer(serializers.Serializer): + access_token = serializers.CharField(required=True) + token_secret = serializers.CharField(required=True) + + def _get_request(self): + request = self.context.get('request') + if not isinstance(request, HttpRequest): + request = request._request + return request + + def get_social_login(self, adapter, app, token, response): + """ + + :param adapter: allauth.socialaccount Adapter subclass. Usually OAuthAdapter or Auth2Adapter + :param app: `allauth.socialaccount.SocialApp` instance + :param token: `allauth.socialaccount.SocialToken` instance + :param response: Provider's response for OAuth1. Not used in the + :return: :return: A populated instance of the `allauth.socialaccount.SocialLoginView` instance + """ + request = self._get_request() + social_login = adapter.complete_login(request, app, token, response=response) + social_login.token = token + return social_login + + def validate(self, attrs): + view = self.context.get('view') + request = self._get_request() + + if not view: + raise serializers.ValidationError( + 'View is not defined, pass it as a context variable' + ) + + adapter_class = getattr(view, 'adapter_class', None) + if not adapter_class: + raise serializers.ValidationError('Define adapter_class in view') + + adapter = adapter_class() + app = adapter.get_provider().get_app(request) + + if('access_token' in attrs) and ('token_secret' in attrs): + access_token = attrs.get('access_token') + token_secret = attrs.get('token_secret') + else: + raise serializers.ValidationError('Incorrect input. access_token and token_secret are required.') + + request.session['oauth_api.twitter.com_access_token'] = { + 'oauth_token': access_token, + 'oauth_token_secret': token_secret, + } + token = SocialToken(token=access_token, token_secret=token_secret) + token.app = app + + try: + login = self.get_social_login(adapter, app, token, access_token) + complete_social_login(request, login) + except HTTPError: + raise serializers.ValidationError('Incorrect value') + + if not login.is_existing: + login.lookup() + login.save(request, connect=True) + attrs['user'] = login.account.user + + return attrs From 152b0a6fb622703b19e568df138fb96eaf1060af Mon Sep 17 00:00:00 2001 From: Nicola Hauke Date: Tue, 2 Feb 2016 15:29:16 +0100 Subject: [PATCH 05/52] Adds ugettext_lazy to more texts Also adds a first german translation. --- rest_auth/locale/de/LC_MESSAGES/django.po | 99 +++++++++++++++++++++++ rest_auth/registration/serializers.py | 17 ++-- rest_auth/registration/views.py | 2 +- rest_auth/serializers.py | 2 +- rest_auth/views.py | 12 ++- 5 files changed, 115 insertions(+), 17 deletions(-) create mode 100644 rest_auth/locale/de/LC_MESSAGES/django.po diff --git a/rest_auth/locale/de/LC_MESSAGES/django.po b/rest_auth/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..3ad22d0 --- /dev/null +++ b/rest_auth/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,99 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-02-02 14:11+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: registration/serializers.py:54 +msgid "View is not defined, pass it as a context variable" +msgstr "\"View\" ist nicht definiert, übergib es als Contextvariable" + +#: registration/serializers.py:59 +msgid "Define adapter_class in view" +msgstr "Definier \"adapter_class\" in view" + +#: registration/serializers.py:78 +msgid "Define callback_url in view" +msgstr "Definier \"callback_url\" in view" + +#: registration/serializers.py:82 +msgid "Define client_class in view" +msgstr "Definier \"client_class\" in view" + +#: registration/serializers.py:102 +msgid "Incorrect input. access_token or code is required." +msgstr "Falsche Eingabe. \"access_token\" oder \"code\" erforderlich." + +#: registration/serializers.py:111 +msgid "Incorrect value" +msgstr "Falscher Wert." + +#: registration/serializers.py:140 +msgid "A user is already registered with this e-mail address." +msgstr "Ein User mit dieser E-Mail Adresse ist schon registriert." + +#: registration/serializers.py:148 +msgid "The two password fields didn't match." +msgstr "Die beiden Passwörter sind nicht identisch." + +#: registration/views.py:64 +msgid "ok" +msgstr "Ok" + +#: serializers.py:29 +msgid "Must include \"email\" and \"password\"." +msgstr "Muss \"email\" und \"password\" enthalten." + +#: serializers.py:40 +msgid "Must include \"username\" and \"password\"." +msgstr "Muss \"username\" und \"password\" enthalten." + +#: serializers.py:53 +msgid "Must include either \"username\" or \"email\" and \"password\"." +msgstr "Muss entweder \"username\" oder \"email\" und password \"password\"" + +#: serializers.py:94 +msgid "User account is disabled." +msgstr "Der Useraccount ist deaktiviert." + +#: serializers.py:97 +msgid "Unable to log in with provided credentials." +msgstr "Kann nicht mit den angegeben Zugangsdaten anmelden." + +#: serializers.py:106 +msgid "E-mail is not verified." +msgstr "E-Mail Adresse ist nicht verifiziert." + +#: serializers.py:152 +msgid "Error" +msgstr "Fehler" + +#: views.py:71 +msgid "Successfully logged out." +msgstr "Erfolgreich ausgeloggt." + +#: views.py:111 +msgid "Password reset e-mail has been sent." +msgstr "Die E-Mail zum Zurücksetzen des Passwortes wurde verschickt." + +#: views.py:132 +msgid "Password has been reset with the new password." +msgstr "Das Passwort wurde mit dem neuen Passwort ersetzt." + +#: views.py:150 +msgid "New password has been saved." +msgstr "Das neue Passwort wurde gespeichert." diff --git a/rest_auth/registration/serializers.py b/rest_auth/registration/serializers.py index b27d7bd..444010b 100644 --- a/rest_auth/registration/serializers.py +++ b/rest_auth/registration/serializers.py @@ -1,5 +1,6 @@ from django.http import HttpRequest from django.conf import settings +from django.utils.translation import ugettext_lazy as _ try: from allauth.account import app_settings as allauth_settings @@ -53,12 +54,12 @@ class SocialLoginSerializer(serializers.Serializer): if not view: raise serializers.ValidationError( - 'View is not defined, pass it as a context variable' + _('View is not defined, pass it as a context variable') ) adapter_class = getattr(view, 'adapter_class', None) if not adapter_class: - raise serializers.ValidationError('Define adapter_class in view') + raise serializers.ValidationError(_('Define adapter_class in view')) adapter = adapter_class() app = adapter.get_provider().get_app(request) @@ -77,11 +78,11 @@ class SocialLoginSerializer(serializers.Serializer): if not self.callback_url: raise serializers.ValidationError( - 'Define callback_url in view' + _('Define callback_url in view') ) if not self.client_class: raise serializers.ValidationError( - 'Define client_class in view' + _('Define client_class in view') ) code = attrs.get('code') @@ -101,7 +102,7 @@ class SocialLoginSerializer(serializers.Serializer): access_token = token['access_token'] else: - raise serializers.ValidationError('Incorrect input. access_token or code is required.') + raise serializers.ValidationError(_('Incorrect input. access_token or code is required.')) token = adapter.parse_token({'access_token': access_token}) token.app = app @@ -110,7 +111,7 @@ class SocialLoginSerializer(serializers.Serializer): login = self.get_social_login(adapter, app, token, access_token) complete_social_login(request, login) except HTTPError: - raise serializers.ValidationError('Incorrect value') + raise serializers.ValidationError(_('Incorrect value')) if not login.is_existing: login.lookup() @@ -139,7 +140,7 @@ class RegisterSerializer(serializers.Serializer): if allauth_settings.UNIQUE_EMAIL: if email and email_address_exists(email): raise serializers.ValidationError( - "A user is already registered with this e-mail address.") + _("A user is already registered with this e-mail address.")) return email def validate_password1(self, password): @@ -147,7 +148,7 @@ class RegisterSerializer(serializers.Serializer): def validate(self, data): if data['password1'] != data['password2']: - raise serializers.ValidationError("The two password fields didn't match.") + raise serializers.ValidationError(_("The two password fields didn't match.")) return data def custom_signup(self, request, user): diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index fa95e7d..2b3e4ed 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -61,7 +61,7 @@ class VerifyEmailView(APIView, ConfirmEmailView): self.kwargs['key'] = serializer.validated_data['key'] confirmation = self.get_object() confirmation.confirm(self.request) - return Response({'message': 'ok'}, status=status.HTTP_200_OK) + return Response({'message': _('ok')}, status=status.HTTP_200_OK) class SocialLoginView(LoginView): diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 5f3541d..892a8d8 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -103,7 +103,7 @@ class LoginSerializer(serializers.Serializer): if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY: email_address = user.emailaddress_set.get(email=user.email) if not email_address.verified: - raise serializers.ValidationError('E-mail is not verified.') + raise serializers.ValidationError(_('E-mail is not verified.')) attrs['user'] = user return attrs diff --git a/rest_auth/views.py b/rest_auth/views.py index 3bb6f6b..b369456 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -1,6 +1,7 @@ from django.contrib.auth import login, logout from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from django.utils.translation import ugettext_lazy as _ from rest_framework import status from rest_framework.views import APIView @@ -69,12 +70,11 @@ class LogoutView(APIView): logout(request) - return Response({"success": "Successfully logged out."}, + return Response({"success": _("Successfully logged out.")}, status=status.HTTP_200_OK) class UserDetailsView(RetrieveUpdateAPIView): - """ Returns User's details in JSON format. @@ -111,13 +111,12 @@ class PasswordResetView(GenericAPIView): serializer.save() # Return the success message with OK HTTP status return Response( - {"success": "Password reset e-mail has been sent."}, + {"success": _("Password reset e-mail has been sent.")}, status=status.HTTP_200_OK ) class PasswordResetConfirmView(GenericAPIView): - """ Password reset e-mail link is confirmed, therefore this resets the user's password. @@ -133,11 +132,10 @@ class PasswordResetConfirmView(GenericAPIView): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return Response({"success": "Password has been reset with the new password."}) + return Response({"success": _("Password has been reset with the new password.")}) class PasswordChangeView(GenericAPIView): - """ Calls Django Auth SetPasswordForm save method. @@ -152,4 +150,4 @@ class PasswordChangeView(GenericAPIView): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return Response({"success": "New password has been saved."}) + return Response({"success": _("New password has been saved.")}) From 8d91e1881e41eefa8f28891a508b2ea2a4f280aa Mon Sep 17 00:00:00 2001 From: Billy Ferguson Date: Mon, 8 Feb 2016 17:14:25 -0500 Subject: [PATCH 06/52] Added logout functionality to demo project --- demo/demo/urls.py | 2 ++ demo/templates/base.html | 1 + demo/templates/fragments/logout_form.html | 20 ++++++++++++++++++++ demo/templates/logout.html | 8 ++++++++ 4 files changed, 31 insertions(+) create mode 100644 demo/templates/fragments/logout_form.html create mode 100644 demo/templates/logout.html diff --git a/demo/demo/urls.py b/demo/demo/urls.py index 54d06ad..3813e5a 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -11,6 +11,8 @@ urlpatterns = [ name='email-verification'), url(r'^login/$', TemplateView.as_view(template_name="login.html"), name='login'), + url(r'^logout/$', TemplateView.as_view(template_name="logout.html"), + name='logout'), url(r'^password-reset/$', TemplateView.as_view(template_name="password_reset.html"), name='password-reset'), diff --git a/demo/templates/base.html b/demo/templates/base.html index 8a0b0ed..03a2b73 100644 --- a/demo/templates/base.html +++ b/demo/templates/base.html @@ -40,6 +40,7 @@
  • User details
  • +
  • Logout
  • Password change
  • diff --git a/demo/templates/fragments/logout_form.html b/demo/templates/fragments/logout_form.html new file mode 100644 index 0000000..7fd281d --- /dev/null +++ b/demo/templates/fragments/logout_form.html @@ -0,0 +1,20 @@ +{% block content %} + +
    {% csrf_token %} +
    + +
    + +

    Token received after login

    +
    +
    + +
    +
    + +
    +
    + +
    +
    +{% endblock %} diff --git a/demo/templates/logout.html b/demo/templates/logout.html new file mode 100644 index 0000000..2ae28e2 --- /dev/null +++ b/demo/templates/logout.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} +
    +

    Logout


    + {% include "fragments/logout_form.html" %} +
    +{% endblock %} From 37c49e0c86b7c42c513dd4e8c3f0650485072623 Mon Sep 17 00:00:00 2001 From: Billy Ferguson Date: Mon, 8 Feb 2016 17:35:37 -0500 Subject: [PATCH 07/52] Adds token to logout API documentation --- docs/api_endpoints.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api_endpoints.rst b/docs/api_endpoints.rst index 1f9660f..7f22efd 100644 --- a/docs/api_endpoints.rst +++ b/docs/api_endpoints.rst @@ -13,6 +13,8 @@ Basic - /rest-auth/logout/ (POST) + - token + - /rest-auth/password/reset/ (POST) - email From dae38d4e108faf8d03441b11506dddb14bff64e8 Mon Sep 17 00:00:00 2001 From: Haos616 Date: Wed, 10 Feb 2016 18:45:32 +0200 Subject: [PATCH 08/52] Fixed errors messages for PasswordResetSerializer Fixed errors messages for PasswordResetSerializer --- 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 5f3541d..9d1854e 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -149,7 +149,7 @@ class PasswordResetSerializer(serializers.Serializer): # Create PasswordResetForm with the serializer self.reset_form = self.password_reset_form_class(data=self.initial_data) if not self.reset_form.is_valid(): - raise serializers.ValidationError(_('Error')) + raise serializers.ValidationError(self.reset_form.errors) return value From e3a1ba520ed316c87c7cfb6055e4a61b750cd09f Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Tue, 16 Feb 2016 00:42:18 -0500 Subject: [PATCH 09/52] Added tests for JWT, fixed merge issues --- docs/configuration.rst | 4 +++ docs/installation.rst | 15 ++++++++++++ rest_auth/registration/views.py | 15 ++++++++++-- rest_auth/tests/test_api.py | 43 +++++++++++++++++++++++++++++++++ rest_auth/views.py | 16 ++++++------ 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 3746234..d726a99 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -10,6 +10,8 @@ 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`` + - 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`` @@ -42,6 +44,8 @@ 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 much 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) - **LOGOUT_ON_PASSWORD_CHANGE** - set to False if you want to keep the current user logged in after a password change diff --git a/docs/installation.rst b/docs/installation.rst index 92251dc..1118826 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -109,3 +109,18 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati ) .. note:: Starting from v0.21.0, django-allauth has dropped support for context processors. Check out http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0 for more details. + + +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. + +2. Add the following to your settings + +.. code-block:: python + + REST_USE_JWT = True + diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index dc96a50..30a79bb 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -12,6 +12,7 @@ from allauth.account.utils import complete_signup from allauth.account import app_settings as allauth_settings from rest_auth.app_settings import (TokenSerializer, + JWTSerializer, create_token) from rest_auth.registration.serializers import (SocialLoginSerializer, VerifyEmailSerializer) @@ -31,7 +32,14 @@ class RegisterView(CreateAPIView): allauth_settings.EmailVerificationMethod.MANDATORY: return {} - return TokenSerializer(user.auth_token).data + if getattr(settings, 'REST_USE_JWT', False): + data = { + 'user': user, + 'token': self.token + } + return JWTSerializer(data).data + else: + return TokenSerializer(user.auth_token).data def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) @@ -43,7 +51,10 @@ class RegisterView(CreateAPIView): def perform_create(self, serializer): user = serializer.save(self.request) - create_token(self.token_model, user, serializer) + if getattr(settings, 'REST_USE_JWT', False): + self.token = jwt_encode(user) + else: + create_token(self.token_model, user, serializer) complete_signup(self.request._request, user, allauth_settings.EMAIL_VERIFICATION, None) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index f6cc839..9903c9d 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -91,6 +91,19 @@ class APITestCase1(TestCase, BaseAPITestCase): # test empty payload self.post(self.login_url, data={}, status_code=400) + @override_settings(REST_USE_JWT=True) + def test_login_jwt(self): + payload = { + "username": self.USERNAME, + "password": self.PASS + } + user = 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'] + + def test_login_by_email(self): # starting test without allauth app settings.INSTALLED_APPS.remove('allauth') @@ -307,6 +320,22 @@ 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) + def test_user_details_using_jwt(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() @@ -323,6 +352,20 @@ class APITestCase1(TestCase, BaseAPITestCase): self._login() self._logout() + @override_settings(REST_USE_JWT=True) + def test_registration_with_jwt(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.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' diff --git a/rest_auth/views.py b/rest_auth/views.py index 331642c..f1aa6a7 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -12,7 +12,7 @@ from rest_framework.generics import RetrieveUpdateAPIView from .app_settings import ( TokenSerializer, UserDetailsSerializer, LoginSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer, - PasswordChangeSerializer, create_token + PasswordChangeSerializer, JWTSerializer, create_token ) from .models import TokenModel @@ -43,17 +43,15 @@ class LoginView(GenericAPIView): def login(self): self.user = self.serializer.validated_data['user'] - self.token = create_token(self.token_model, self.user, self.serializer) - if getattr(settings, 'REST_SESSION_LOGIN', True): - login(self.request, self.user) - + if getattr(settings, 'REST_USE_JWT', False): self.token = jwt_encode(self.user) else: - self.token, created = self.token_model.objects.get_or_create( - user=self.user) - if getattr(settings, 'REST_SESSION_LOGIN', True): - login(self.request, self.user) + self.token = create_token(self.token_model, self.user, self.serializer) + + if getattr(settings, 'REST_SESSION_LOGIN', True): + login(self.request, self.user) + def get_response(self): serializer_class = self.get_response_serializer() From 8b5e5173d0a2d881101d82f56997398da23c86ab Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Tue, 16 Feb 2016 00:43:19 -0500 Subject: [PATCH 10/52] Added newline to pip file --- rest_auth/tests/requirements.pip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_auth/tests/requirements.pip b/rest_auth/tests/requirements.pip index 4040970..de66892 100644 --- a/rest_auth/tests/requirements.pip +++ b/rest_auth/tests/requirements.pip @@ -1,4 +1,4 @@ django-allauth>=0.19.1 responses>=0.3.0 flake8==2.4.0 -djangorestframework-jwt>=1.7.2 \ No newline at end of file +djangorestframework-jwt>=1.7.2 From 511329c30a768a47d016154732dc5aac5c64273f Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Tue, 16 Feb 2016 00:49:41 -0500 Subject: [PATCH 11/52] Fixing merge tool induced file endings --- rest_auth/registration/views.py | 2 +- rest_auth/utils.py | 2 +- rest_auth/views.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 30a79bb..2689ec2 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -102,4 +102,4 @@ class SocialLoginView(LoginView): ------------- """ - serializer_class = SocialLoginSerializer \ No newline at end of file + serializer_class = SocialLoginSerializer diff --git a/rest_auth/utils.py b/rest_auth/utils.py index 13df03e..5cb94d7 100644 --- a/rest_auth/utils.py +++ b/rest_auth/utils.py @@ -25,4 +25,4 @@ def jwt_encode(user): jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) - return jwt_encode_handler(payload) \ No newline at end of file + return jwt_encode_handler(payload) diff --git a/rest_auth/views.py b/rest_auth/views.py index f1aa6a7..5951a93 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -175,4 +175,4 @@ class PasswordChangeView(GenericAPIView): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - return Response({"success": "New password has been saved."}) \ No newline at end of file + return Response({"success": "New password has been saved."}) From 40ac97b847d11e0c0aa47b62a0a1ed282bf05920 Mon Sep 17 00:00:00 2001 From: Maciej Jaworski Date: Tue, 23 Feb 2016 10:20:52 +0000 Subject: [PATCH 12/52] Update index.rst --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 2ad4a05..dc25e83 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,7 @@ Welcome to django-rest-auth's documentation! ============================================ -.. warning:: Updating django-rest-auth to version **0.3.4** is highly recommended because of a security issue in PasswordResetConfirmation validation method. +.. warning:: Updating django-rest-auth from version **0.3.3** is highly recommended because of a security issue in PasswordResetConfirmation validation method. .. note:: django-rest-auth from v0.3.3 supports django-rest-framework v3.0 From 23221dc4d5e77a38aa6807cbff1bf326f30565d9 Mon Sep 17 00:00:00 2001 From: Maciej Jaworski Date: Tue, 23 Feb 2016 10:30:25 +0000 Subject: [PATCH 13/52] Update .travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5152e3c..f07f4a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,9 @@ language: python python: - "2.7" env: - - DJANGO=1.7.7 - - DJANGO=1.8 + - DJANGO=1.7.11 + - DJANGO=1.8.9 + - DJANGO=1.9.2 install: - pip install -q Django==$DJANGO --use-mirrors - pip install coveralls From 3bcabe6b173faf11c112b2d22ed1b05813c356fb Mon Sep 17 00:00:00 2001 From: Maciej Jaworski Date: Tue, 23 Feb 2016 10:35:41 +0000 Subject: [PATCH 14/52] Update serializers.py pepfix --- rest_auth/registration/serializers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_auth/registration/serializers.py b/rest_auth/registration/serializers.py index e3c83fb..e6d23f8 100644 --- a/rest_auth/registration/serializers.py +++ b/rest_auth/registration/serializers.py @@ -22,7 +22,6 @@ if 'allauth.socialaccount' in settings.INSTALLED_APPS: pass - class SocialLoginSerializer(serializers.Serializer): access_token = serializers.CharField(required=False, allow_blank=True) code = serializers.CharField(required=False, allow_blank=True) From aa839f97d218f8c84c7b4d8bf24b1fdd7eee245d Mon Sep 17 00:00:00 2001 From: Maciej Jaworski Date: Tue, 23 Feb 2016 10:59:47 +0000 Subject: [PATCH 15/52] Added missing import --- rest_auth/registration/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 2b3e4ed..1f9da3c 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -1,3 +1,5 @@ +from django.utils.translation import ugettext_lazy as _ + from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import AllowAny From e6c5be4b69759df8f9d8d94c3709a6255e9c8cbf Mon Sep 17 00:00:00 2001 From: Tevin Joseph K O Date: Tue, 23 Feb 2016 17:15:57 +0530 Subject: [PATCH 16/52] Updated index.rst with twitter login --- docs/installation.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 92251dc..7717fef 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -85,6 +85,8 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati ..., 'allauth.socialaccount', 'allauth.socialaccount.providers.facebook', + 'allauth.socialaccount.providers.twitter', + ) 2. Add Social Application in django admin panel @@ -108,4 +110,20 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login') ) +5. If you are using Twitter for your social authentication, it is a bit different from + Facebook since Twitter uses OAuth 1.0. + + +6. Create new view as a subclass of ``rest_auth.views.LoginView`` with ``TwitterOAuthAdapter`` adapter and ``TwitterLoginSerializer`` as an attribute: + +.. code-block:: python + + from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter + from rest_auth.views import LoginView + from rest_auth.social_serializers import TwitterLoginSerializer + + class TwitterLogin(LoginView): + serializer_class = TwitterLoginSerializer + adapter_class = TwitterOAuthAdapter + .. note:: Starting from v0.21.0, django-allauth has dropped support for context processors. Check out http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0 for more details. From 8f05f200514bf74f85b695724d08d04ea2720e5b Mon Sep 17 00:00:00 2001 From: Tevin Joseph K O Date: Tue, 23 Feb 2016 17:18:30 +0530 Subject: [PATCH 17/52] Updated api_endpoints.rst with twitter login --- docs/api_endpoints.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api_endpoints.rst b/docs/api_endpoints.rst index 1f9660f..40fd99f 100644 --- a/docs/api_endpoints.rst +++ b/docs/api_endpoints.rst @@ -70,3 +70,8 @@ Basing on example from installation section :doc:`Installation ` - access_token - code + +- /rest-auth/twitter/ (POST) + + - access_token + - token_secret From ee9e848694b5848761a30962b8a8c378d338d805 Mon Sep 17 00:00:00 2001 From: Tevin Joseph K O Date: Tue, 23 Feb 2016 17:31:11 +0530 Subject: [PATCH 18/52] URL for twitter login added. --- docs/installation.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 7717fef..a0723d7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -126,4 +126,12 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati serializer_class = TwitterLoginSerializer adapter_class = TwitterOAuthAdapter +7. Create url for TwitterLogin view: + +.. code-block:: python + + urlpatterns += pattern('', + ..., + url(r'^rest-auth/twitter/$', TwitterLogin.as_view(), name='twitter_login') + ) .. note:: Starting from v0.21.0, django-allauth has dropped support for context processors. Check out http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0 for more details. From bb7b1270b7c1a621e7f86f32dbe5e431162c2677 Mon Sep 17 00:00:00 2001 From: Maciej Jaworski Date: Tue, 23 Feb 2016 12:16:24 +0000 Subject: [PATCH 19/52] Adjusted phrasing and layout of social integration examples --- docs/installation.rst | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index a0723d7..60b47ca 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -65,11 +65,11 @@ Registration (optional) Social Authentication (optional) -------------------------------- -Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creating social media authentication view. Below is an example with Facebook authentication. +Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creating social media authentication view. -.. note:: Points 1, 2 and 3 are related with ``django-allauth`` configuration, so if you have already configured social authentication, then please go to step 4. See ``django-allauth`` documentation for more details. +.. note:: Points 1 and 2 are related to ``django-allauth`` configuration, so if you have already configured social authentication, then please go to step 3. See ``django-allauth`` documentation for more details. -1. Add ``allauth.socialaccount`` and ``allauth.socialaccount.providers.facebook`` apps to INSTALLED_APPS in your django settings.py: +1. Add ``allauth.socialaccount`` and ``allauth.socialaccount.providers.facebook`` or ``allauth.socialaccount.providers.twitter`` apps to INSTALLED_APPS in your django settings.py: .. code-block:: python @@ -91,6 +91,9 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati 2. Add Social Application in django admin panel +Facebook +######## + 3. Create new view as a subclass of ``rest_auth.registration.views.SocialLoginView`` with ``FacebookOAuth2Adapter`` adapter as an attribute: .. code-block:: python @@ -110,11 +113,13 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login') ) -5. If you are using Twitter for your social authentication, it is a bit different from - Facebook since Twitter uses OAuth 1.0. +Twitter +####### -6. Create new view as a subclass of ``rest_auth.views.LoginView`` with ``TwitterOAuthAdapter`` adapter and ``TwitterLoginSerializer`` as an attribute: +If you are using Twitter for your social authentication, it is a bit different since Twitter uses OAuth 1.0. + +3. Create new view as a subclass of ``rest_auth.views.LoginView`` with ``TwitterOAuthAdapter`` adapter and ``TwitterLoginSerializer`` as an attribute: .. code-block:: python @@ -126,7 +131,7 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati serializer_class = TwitterLoginSerializer adapter_class = TwitterOAuthAdapter -7. Create url for TwitterLogin view: +4. Create url for TwitterLogin view: .. code-block:: python From b2edfffc91380cc0554504a67ee2a556cf9c360c Mon Sep 17 00:00:00 2001 From: Tevin Joseph K O Date: Tue, 23 Feb 2016 17:52:44 +0530 Subject: [PATCH 20/52] fixed pep8 error in social_serializers fixed pep8 error in social_serializers which cause documentation build error --- rest_auth/social_serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_auth/social_serializers.py b/rest_auth/social_serializers.py index 2aee4ea..087e6e1 100644 --- a/rest_auth/social_serializers.py +++ b/rest_auth/social_serializers.py @@ -58,8 +58,8 @@ class TwitterLoginSerializer(serializers.Serializer): raise serializers.ValidationError('Incorrect input. access_token and token_secret are required.') request.session['oauth_api.twitter.com_access_token'] = { - 'oauth_token': access_token, - 'oauth_token_secret': token_secret, + 'oauth_token': access_token, + 'oauth_token_secret': token_secret, } token = SocialToken(token=access_token, token_secret=token_secret) token.app = app From f01ed78d59243034b80c21ceece19ce5b21d41ea Mon Sep 17 00:00:00 2001 From: Maciej Jaworski Date: Tue, 23 Feb 2016 15:30:53 +0000 Subject: [PATCH 21/52] Update changelog.rst --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 735b936..e3e9064 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog ========= +0.7.0 +----- +- Wrapped API returned strings in ugettext_lazy +- Fixed not using ``get_username`` which caused issues when using custom user model without username field +- Django 1.9 support +- Added ``TwitterLoginSerializer`` + 0.6.0 ----- - dropped support for Python 2.6 From ed42925053b7e3d17e8a3f633dea1a555129b5ae Mon Sep 17 00:00:00 2001 From: Maciej Jaworski Date: Tue, 23 Feb 2016 15:39:26 +0000 Subject: [PATCH 22/52] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d85b2d2..49deb9a 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ f.close() setup( name='django-rest-auth', - version='0.6.0', + version='0.7.0', author='Sumit Chachra', author_email='chachra@tivix.com', url='http://github.com/Tivix/django-rest-auth', From 0850fb47ae279d9a8b34b0d66475a0347f922751 Mon Sep 17 00:00:00 2001 From: Maciej Jaworski Date: Tue, 23 Feb 2016 15:46:34 +0000 Subject: [PATCH 23/52] Update requirements.pip --- demo/requirements.pip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/requirements.pip b/demo/requirements.pip index f1c5057..4949275 100644 --- a/demo/requirements.pip +++ b/demo/requirements.pip @@ -1,4 +1,4 @@ django>=1.7.0 -django-rest-auth==0.6.0 +django-rest-auth==0.7.0 django-allauth==0.24.1 six==1.9.0 From ff0664a66e3d675a2b55aa18f171ef7bbc127af4 Mon Sep 17 00:00:00 2001 From: Nicola Hauke Date: Tue, 23 Feb 2016 18:24:55 +0100 Subject: [PATCH 24/52] Unignores translation files --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 85446ba..7d8d699 100644 --- a/.gitignore +++ b/.gitignore @@ -35,9 +35,6 @@ nosetests.xml coverage.xml coverage_html -# Translations -*.mo - # Mr Developer .mr.developer.cfg .project From 340f0651c931f089b77b713923f7b84b7932257e Mon Sep 17 00:00:00 2001 From: Nicola Hauke Date: Tue, 23 Feb 2016 18:27:29 +0100 Subject: [PATCH 25/52] Adds the german mo file --- rest_auth/locale/de/LC_MESSAGES/django.mo | Bin 0 -> 2300 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rest_auth/locale/de/LC_MESSAGES/django.mo diff --git a/rest_auth/locale/de/LC_MESSAGES/django.mo b/rest_auth/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..07861ff6d19caf35d4f2e04407f02f6af7402bd5 GIT binary patch literal 2300 zcmai#&u<$=6vqc>`C)z+5Jd&?FfE}7sPkgn{E<{y7Ktr*?I5HoA=&4|9tGo z7Xr^S7|&z;i17kOau2@n{0g1{e*@k6@q2|h0lr!CUGQ1VN8nNL6L1dv4165?0Xzx* z1Wti}fp38)?wi=#0Da62=;XcvkAt_sQ{XS)Ggl{X)D5UMe{NoxM*%e8kuIa`NATkAZ)JuYmu6r@>PX2(bY6z?0zTpxgJG^8N3i zJNL^E3NZuDgRg@Fkiaj&dGLGC`S~{pH;V^b6!CttoI5;$0g>W)1m8GL;n&lXR|4A= z@q7q$_MXB(Xv8s$y{A_GaCvwX<6$>%QEu6gvLqA3GP+KNV`Vco9MO)-29yo3$lge* zgk%&Mw$>MI-cp(|mt%-t4HIcCDNWmoclH)TnIwG~4zK1W`OjLSSZ9ayg2^V>PNx}d zvr$`$BVPo@q(&_1Fg1q547SF3=93IV_;NK%hpZ_z6s8fogBboWS8(6or?M@RoJBXc zPCHC;NY>dMsZLTyR3F3tozz5i|B#T2j+P_7iyR|T2xGkxheQ9j!!qyC-i9=gmimkn zSh?+dUMW4P77%gH_Hx0b_i67FpGcK?s*EDazF5nPP;KQ&a-EVij*;6GAr$Karfesz zyg|T|$Wo*tJ(bZ&X5qjW8(Q`g2&PWgWwNM?u}Qa81WO@ejct@nP#7Donvj_^*9f(< zWSue&wu#bkZ(MGpqI{y6Vq;PnAqWx))EQqx=A}_iNu)!uV$u(gb8ji~HrZH}>L#tU zE?jP13}`c0U0doc*H(F)n#}9kSVi7Dd2GG)v`Mv<-nw@IHJw4pYs-wAG&kEgIT*M8vbnUf{TU5cGt)7+lA$|aUzwTr*I$*3SwbnebGBx$vL;S*mwc1Nu+e#q_cgM z)mD<+$i!Pr&B!)MkBg~Yn471`&xKbty|h4$)AKbEAhkvQM#xB{P zdSzt8L8{9(VH6wGQPB?Ylj;XLLjB;iF9h~?g*&eA>kQTU9o*0=9PD+8-H98EJV@Ax zMO0#l%EozO*=HYK?Q0cvLZ5)Au3glL%@(dbn*`SUJxxRJ6~{uqu(xkV0z!w@ zi*)j+(ACuR0j@6C2?Q zyVoPMC>QoNthsWnx{Yfra+oRn3%RPSyOx6*Ld{M#EZUA+o1vRG`fOs=C!7i8LdAW% LmfdbWy`lIQLlBxH literal 0 HcmV?d00001 From 42b860b8bf739cd92da6889e15bcd6060d2f1cbc Mon Sep 17 00:00:00 2001 From: Nicola Date: Thu, 25 Feb 2016 21:37:08 +0100 Subject: [PATCH 26/52] Change the variable name in the doc --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 3746234..e00d2b5 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -29,7 +29,7 @@ Configuration ... } -- **REST_AUTH_REGISTRATION_SERIALIZERS** +- **REST_AUTH_REGISTER_SERIALIZERS** You can define your custom serializers for registration endpoint. Possible key values: From 9a0cbc81ebb2da08978732b98d1fbc2f210b24d4 Mon Sep 17 00:00:00 2001 From: Andreas Bissinger Date: Mon, 29 Feb 2016 12:53:08 +0100 Subject: [PATCH 27/52] typo fix --- rest_auth/registration/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_auth/registration/urls.py b/rest_auth/registration/urls.py index 9e56c3b..d5504c5 100644 --- a/rest_auth/registration/urls.py +++ b/rest_auth/registration/urls.py @@ -17,7 +17,7 @@ urlpatterns = [ # with proper key. # If you don't want to use API on that step, then just use ConfirmEmailView # view from: - # djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190 + # django-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190 url(r'^account-confirm-email/(?P\w+)/$', TemplateView.as_view(), name='account_confirm_email'), ] From b4c122ee5da818ea2cc2874be6cbf5e343024a5d Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Mon, 29 Feb 2016 07:47:47 -0500 Subject: [PATCH 28/52] Bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 49deb9a..9b33b7f 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ f.close() setup( name='django-rest-auth', - version='0.7.0', + version='0.8.0', author='Sumit Chachra', author_email='chachra@tivix.com', url='http://github.com/Tivix/django-rest-auth', From 1eb4b45e509810f2cbeefffbdb5105da5f9a636c Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Mon, 29 Feb 2016 07:57:54 -0500 Subject: [PATCH 29/52] Fixing tests --- rest_auth/registration/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 40316dd..2862c60 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -1,4 +1,5 @@ from django.utils.translation import ugettext_lazy as _ +from django.conf import settings from rest_framework.views import APIView from rest_framework.response import Response From 4ba9841bc5700f5874eaa49b21f1bceed195a03d Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Mon, 29 Feb 2016 08:01:39 -0500 Subject: [PATCH 30/52] Merge changes --- docs/changelog.rst | 2 +- rest_auth/registration/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index d1f64a3..043a943 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog 0.8.0 ----- - added support for django-rest-framework-jwt + 0.7.0 ----- - Wrapped API returned strings in ugettext_lazy @@ -67,4 +68,3 @@ Changelog - changed password reset confim url - uid and token should be sent in POST - increase test coverage - made compatibile with django 1.7 -- removed user profile support \ No newline at end of file diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 2862c60..d1d8d37 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -103,4 +103,4 @@ class SocialLoginView(LoginView): ------------- """ - serializer_class = SocialLoginSerializer \ No newline at end of file + serializer_class = SocialLoginSerializer From 9baeb2883b10131852738084a38a07f046885e08 Mon Sep 17 00:00:00 2001 From: Jon Gregorowicz Date: Mon, 29 Feb 2016 08:02:50 -0500 Subject: [PATCH 31/52] Docs comment --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 043a943..a24d7f5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -68,3 +68,4 @@ Changelog - changed password reset confim url - uid and token should be sent in POST - increase test coverage - made compatibile with django 1.7 +- removed user profile support From 25724759719bb08dd49cc0956757160e97c7b009 Mon Sep 17 00:00:00 2001 From: mjaworski Date: Tue, 1 Mar 2016 11:51:01 +0000 Subject: [PATCH 32/52] fixed code quality --- rest_auth/registration/views.py | 1 + rest_auth/serializers.py | 3 +++ rest_auth/tests/test_api.py | 9 +++------ rest_auth/tests/test_social.py | 3 +-- rest_auth/utils.py | 3 ++- rest_auth/views.py | 3 +-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index d1d8d37..41ca856 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -23,6 +23,7 @@ from .app_settings import RegisterSerializer from rest_auth.utils import jwt_encode + class RegisterView(CreateAPIView): serializer_class = RegisterSerializer permission_classes = (AllowAny, ) diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 7daf17c..3d4faae 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -118,6 +118,7 @@ class TokenSerializer(serializers.ModelSerializer): model = TokenModel fields = ('key',) + class UserDetailsSerializer(serializers.ModelSerializer): """ @@ -128,6 +129,7 @@ class UserDetailsSerializer(serializers.ModelSerializer): fields = ('username', 'email', 'first_name', 'last_name') read_only_fields = ('email', ) + class JWTSerializer(serializers.Serializer): """ Serializer for JWT authentication. @@ -135,6 +137,7 @@ class JWTSerializer(serializers.Serializer): token = serializers.CharField() user = UserDetailsSerializer() + class PasswordResetSerializer(serializers.Serializer): """ diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index f75322c..a74e409 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -97,13 +97,12 @@ class APITestCase1(TestCase, BaseAPITestCase): "username": self.USERNAME, "password": self.PASS } - user = get_user_model().objects.create_user(self.USERNAME, '', 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'] - def test_login_by_email(self): # starting test without allauth app settings.INSTALLED_APPS.remove('allauth') @@ -335,7 +334,6 @@ class APITestCase1(TestCase, BaseAPITestCase): 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() @@ -365,7 +363,6 @@ class APITestCase1(TestCase, BaseAPITestCase): self._login() self._logout() - def test_registration_with_invalid_password(self): data = self.REGISTRATION_DATA.copy() data['password2'] = 'foobar' @@ -430,7 +427,7 @@ class APITestCase1(TestCase, BaseAPITestCase): } # create user - user = get_user_model().objects.create_user(self.USERNAME, '', self.PASS) + get_user_model().objects.create_user(self.USERNAME, '', self.PASS) self.post(self.login_url, data=payload, status_code=200) self.get(self.logout_url, status=status.HTTP_200_OK) @@ -443,7 +440,7 @@ class APITestCase1(TestCase, BaseAPITestCase): } # create user - user = get_user_model().objects.create_user(self.USERNAME, '', self.PASS) + get_user_model().objects.create_user(self.USERNAME, '', self.PASS) 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) diff --git a/rest_auth/tests/test_social.py b/rest_auth/tests/test_social.py index 34c912e..a06e365 100644 --- a/rest_auth/tests/test_social.py +++ b/rest_auth/tests/test_social.py @@ -148,6 +148,5 @@ class TestSocialAuth(TestCase, BaseAPITestCase): self.post(self.fb_login_url, data=payload, status_code=200) self.assertIn('token', self.response.json.keys()) self.assertIn('user', self.response.json.keys()) - - self.assertEqual(get_user_model().objects.all().count(), users_count + 1) + self.assertEqual(get_user_model().objects.all().count(), users_count + 1) diff --git a/rest_auth/utils.py b/rest_auth/utils.py index 5cb94d7..99d80b8 100644 --- a/rest_auth/utils.py +++ b/rest_auth/utils.py @@ -15,8 +15,9 @@ def default_create_token(token_model, user, serializer): token, _ = token_model.objects.get_or_create(user=user) return token + def jwt_encode(user): - try: + try: from rest_framework_jwt.settings import api_settings except ImportError: raise ImportError('rest_framework_jwt needs to be installed') diff --git a/rest_auth/views.py b/rest_auth/views.py index 25f47f2..5e168cd 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -46,7 +46,7 @@ class LoginView(GenericAPIView): def login(self): self.user = self.serializer.validated_data['user'] - + if getattr(settings, 'REST_USE_JWT', False): self.token = jwt_encode(self.user) else: @@ -55,7 +55,6 @@ class LoginView(GenericAPIView): if getattr(settings, 'REST_SESSION_LOGIN', True): login(self.request, self.user) - def get_response(self): serializer_class = self.get_response_serializer() From 86a487fe2137826a19a32b883bb89ffcb9d3b2d6 Mon Sep 17 00:00:00 2001 From: Mario Rodas Date: Wed, 2 Mar 2016 14:33:27 -0500 Subject: [PATCH 33/52] Import allauth.socialaccount only when declared in INSTALLED_APPS Don't silently ignore ImportError --- rest_auth/registration/serializers.py | 6 +----- rest_auth/social_serializers.py | 8 +++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/rest_auth/registration/serializers.py b/rest_auth/registration/serializers.py index 2390b05..33d6698 100644 --- a/rest_auth/registration/serializers.py +++ b/rest_auth/registration/serializers.py @@ -15,12 +15,8 @@ from rest_framework import serializers from requests.exceptions import HTTPError # Import is needed only if we are using social login, in which # case the allauth.socialaccount will be declared - if 'allauth.socialaccount' in settings.INSTALLED_APPS: - try: - from allauth.socialaccount.helpers import complete_social_login - except ImportError: - pass + from allauth.socialaccount.helpers import complete_social_login class SocialLoginSerializer(serializers.Serializer): diff --git a/rest_auth/social_serializers.py b/rest_auth/social_serializers.py index 087e6e1..5bd2f0f 100644 --- a/rest_auth/social_serializers.py +++ b/rest_auth/social_serializers.py @@ -1,14 +1,12 @@ +from django.conf import settings from django.http import HttpRequest from rest_framework import serializers from requests.exceptions import HTTPError # Import is needed only if we are using social login, in which # case the allauth.socialaccount will be declared -try: +if 'allauth.socialaccount' in settings.INSTALLED_APPS: from allauth.socialaccount.helpers import complete_social_login -except ImportError: - pass - -from allauth.socialaccount.models import SocialToken + from allauth.socialaccount.models import SocialToken class TwitterLoginSerializer(serializers.Serializer): From 0737da0077ad063eb56a9b832a32ece2af7ea32b Mon Sep 17 00:00:00 2001 From: Mario Rodas Date: Wed, 2 Mar 2016 14:59:01 -0500 Subject: [PATCH 34/52] Capture OAuthError in TwitterLoginSerializer --- rest_auth/social_serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_auth/social_serializers.py b/rest_auth/social_serializers.py index 5bd2f0f..de6223f 100644 --- a/rest_auth/social_serializers.py +++ b/rest_auth/social_serializers.py @@ -1,12 +1,12 @@ from django.conf import settings from django.http import HttpRequest from rest_framework import serializers -from requests.exceptions import HTTPError # Import is needed only if we are using social login, in which # case the allauth.socialaccount will be declared if 'allauth.socialaccount' in settings.INSTALLED_APPS: from allauth.socialaccount.helpers import complete_social_login from allauth.socialaccount.models import SocialToken + from allauth.socialaccount.providers.oauth.client import OAuthError class TwitterLoginSerializer(serializers.Serializer): @@ -65,8 +65,8 @@ class TwitterLoginSerializer(serializers.Serializer): try: login = self.get_social_login(adapter, app, token, access_token) complete_social_login(request, login) - except HTTPError: - raise serializers.ValidationError('Incorrect value') + except OAuthError as e: + raise serializers.ValidationError(str(e)) if not login.is_existing: login.lookup() From a661f02b8f75d6e1a1de7c407824421ac22c7c34 Mon Sep 17 00:00:00 2001 From: Christopher Grebs Date: Mon, 7 Mar 2016 18:43:18 +0100 Subject: [PATCH 35/52] Fix typo in configuration.rst --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 0728e11..b583d3a 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -44,7 +44,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 much also be installed. (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) From e8cd780ae22a3b42ff272987d2bbf8ce8732e542 Mon Sep 17 00:00:00 2001 From: Maxim Kukhtenkov Date: Mon, 7 Mar 2016 18:21:11 -0500 Subject: [PATCH 36/52] Remove unreachable code in LogoutView --- rest_auth/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rest_auth/views.py b/rest_auth/views.py index 5e168cd..8420c32 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -96,8 +96,6 @@ class LogoutView(APIView): response = self.handle_exception(exc) return self.finalize_response(request, response, *args, **kwargs) - self.response = self.finalize_response(request, response, *args, **kwargs) - return self.response def post(self, request): return self.logout(request) From adf22bcc78656cc16cceae1ca33a77d572364492 Mon Sep 17 00:00:00 2001 From: Ross Patterson Date: Wed, 9 Mar 2016 11:51:52 -0800 Subject: [PATCH 37/52] Fix reference to missing `extras_require` --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 9e47a47..e070c73 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -38,7 +38,7 @@ You're good to go now! Registration (optional) ----------------------- -1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[extras]`` or ``pip install django-rest-auth[with_social]``. +1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[with_social]``. 2. Add ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py: From 4463fb8afaf6cde9e8f1e14e00bdba3a009daffd Mon Sep 17 00:00:00 2001 From: Ross Patterson Date: Thu, 10 Mar 2016 14:12:24 -0800 Subject: [PATCH 38/52] Fix docs settings default value --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index b583d3a..c65b4f7 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -36,7 +36,7 @@ Configuration You can define your custom serializers for registration endpoint. Possible key values: - - REGISTER_SERIALIZER - serializer class in ``rest_auth.register.views.RegisterView``, default value ``rest_auth.register.serializers.RegisterSerializer`` + - REGISTER_SERIALIZER - serializer class in ``rest_auth.register.views.RegisterView``, default value ``rest_auth.registration.serializers.RegisterSerializer`` - **REST_AUTH_TOKEN_MODEL** - model class for tokens, default value ``rest_framework.authtoken.models`` From 50087549e82c8f2b42ea8a6748cc66cbe3f3e472 Mon Sep 17 00:00:00 2001 From: Christopher Grebs Date: Mon, 14 Mar 2016 13:19:06 +0100 Subject: [PATCH 39/52] Fix social-adatper tests for allauth>=0.25.0 See https://github.com/pennersr/django-allauth/commit/742d114abf57917d69fbdfd9babf7fd806ddba44 for more details. This unfortunately requires bumping up the version to 0.25.0 --- rest_auth/registration/serializers.py | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_auth/registration/serializers.py b/rest_auth/registration/serializers.py index 2390b05..b25f543 100644 --- a/rest_auth/registration/serializers.py +++ b/rest_auth/registration/serializers.py @@ -60,7 +60,7 @@ class SocialLoginSerializer(serializers.Serializer): if not adapter_class: raise serializers.ValidationError(_('Define adapter_class in view')) - adapter = adapter_class() + adapter = adapter_class(request) app = adapter.get_provider().get_app(request) # More info on code vs access_token diff --git a/setup.py b/setup.py index 9b33b7f..e4e6c1f 100644 --- a/setup.py +++ b/setup.py @@ -33,11 +33,11 @@ setup( 'six>=1.9.0', ], extras_require={ - 'with_social': ['django-allauth>=0.24.1'], + 'with_social': ['django-allauth>=0.25.0'], }, tests_require=[ 'responses>=0.5.0', - 'django-allauth>=0.24.1', + 'django-allauth>=0.25.0', ], test_suite='runtests.runtests', include_package_data=True, From 22667230bb9a3267dbd34837a35cf8473daec217 Mon Sep 17 00:00:00 2001 From: Girish Date: Thu, 14 Apr 2016 12:54:50 +0100 Subject: [PATCH 40/52] passing on the context/request to serializer --- rest_auth/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_auth/views.py b/rest_auth/views.py index 8420c32..55f767d 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -63,9 +63,9 @@ class LoginView(GenericAPIView): 'user': self.user, 'token': self.token } - serializer = serializer_class(instance=data) + serializer = serializer_class(instance=data, context={'request': self.request}) else: - serializer = serializer_class(instance=self.token) + serializer = serializer_class(instance=self.token, context={'request': self.request}) return Response(serializer.data, status=status.HTTP_200_OK) From 2a0fa1ab4eff07b5ec5169b7fe98a0f2c97a3e94 Mon Sep 17 00:00:00 2001 From: vsevolod kolchinsky Date: Thu, 5 May 2016 09:03:34 +0300 Subject: [PATCH 41/52] allows registration throttle control --- rest_auth/registration/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 41ca856..de31f38 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -28,6 +28,7 @@ class RegisterView(CreateAPIView): serializer_class = RegisterSerializer permission_classes = (AllowAny, ) token_model = TokenModel + throttle_scope = 'register_view' def get_response_data(self, user): if allauth_settings.EMAIL_VERIFICATION == \ From 8ffb292b70edb077227226a6e068d1f3290be5c0 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Mon, 11 Apr 2016 14:18:27 -0400 Subject: [PATCH 42/52] Update Travis CI config to run on Python 3.5. --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index f07f4a3..fcc572a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: - "2.7" + - "3.5" env: - DJANGO=1.7.11 - DJANGO=1.8.9 @@ -9,6 +10,10 @@ install: - pip install -q Django==$DJANGO --use-mirrors - pip install coveralls - pip install -r rest_auth/tests/requirements.pip +matrix: + exclude: + - python: "3.5" + env: DJANGO=1.7.11 script: - coverage run --source=rest_auth setup.py test after_success: From 8a004bb48adfdee12c055838ee1b496a72107a65 Mon Sep 17 00:00:00 2001 From: mariodev Date: Tue, 28 Jun 2016 20:14:05 +0200 Subject: [PATCH 43/52] Increased test coverage + minor fixes --- .coveragerc | 26 +++++++++++++ rest_auth/registration/views.py | 3 -- rest_auth/tests/django_urls.py | 68 --------------------------------- rest_auth/tests/settings.py | 21 ++++++++++ rest_auth/tests/test_api.py | 58 +++++++++++++++++++++++++--- rest_auth/tests/test_base.py | 6 ++- rest_auth/tests/test_social.py | 3 +- 7 files changed, 105 insertions(+), 80 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..70d6d0d --- /dev/null +++ b/.coveragerc @@ -0,0 +1,26 @@ +# .coveragerc to control coverage.py +[run] +omit=*site-packages*,*distutils*,*migrations* + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + +ignore_errors = True + +[html] +directory = coverage_html \ No newline at end of file diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 41ca856..c38f317 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -68,9 +68,6 @@ class VerifyEmailView(APIView, ConfirmEmailView): permission_classes = (AllowAny,) allowed_methods = ('POST', 'OPTIONS', 'HEAD') - def get(self, *args, **kwargs): - raise MethodNotAllowed('GET') - def post(self, request, *args, **kwargs): serializer = VerifyEmailSerializer(data=request.data) serializer.is_valid(raise_exception=True) diff --git a/rest_auth/tests/django_urls.py b/rest_auth/tests/django_urls.py index 2193f1b..9fb6424 100644 --- a/rest_auth/tests/django_urls.py +++ b/rest_auth/tests/django_urls.py @@ -13,67 +13,11 @@ from django.template import RequestContext, Template from django.views.decorators.cache import never_cache -class CustomRequestAuthenticationForm(AuthenticationForm): - def __init__(self, request, *args, **kwargs): - assert isinstance(request, HttpRequest) - super(CustomRequestAuthenticationForm, self).__init__(request, *args, **kwargs) - - -@never_cache -def remote_user_auth_view(request): - """ - Dummy view for remote user tests - """ - t = Template("Username is {{ user }}.") - c = RequestContext(request, {}) - return HttpResponse(t.render(c)) - - -def auth_processor_no_attr_access(request): - render(request, 'context_processors/auth_attrs_no_access.html') - # *After* rendering, we check whether the session was accessed - return render(request, - 'context_processors/auth_attrs_test_access.html', - {'session_accessed': request.session.accessed}) - - -def auth_processor_attr_access(request): - render(request, 'context_processors/auth_attrs_access.html') - return render(request, - 'context_processors/auth_attrs_test_access.html', - {'session_accessed': request.session.accessed}) - - -def auth_processor_user(request): - return render(request, 'context_processors/auth_attrs_user.html') - - -def auth_processor_perms(request): - return render(request, 'context_processors/auth_attrs_perms.html') - - -def auth_processor_perm_in_perms(request): - return render(request, 'context_processors/auth_attrs_perm_in_perms.html') - - -def auth_processor_messages(request): - info(request, "Message 1") - return render(request, 'context_processors/auth_attrs_messages.html') - - -def userpage(request): - pass - - -def custom_request_auth_login(request): - return views.login(request, authentication_form=CustomRequestAuthenticationForm) - # special urls for auth test cases urlpatterns += [ url(r'^logout/custom_query/$', views.logout, dict(redirect_field_name='follow')), url(r'^logout/next_page/$', views.logout, dict(next_page='/somewhere/')), url(r'^logout/next_page/named/$', views.logout, dict(next_page='password_reset')), - url(r'^remote_user/$', remote_user_auth_view), url(r'^password_reset_from_email/$', views.password_reset, dict(from_email='staffmember@example.com')), url(r'^password_reset/custom_redirect/$', views.password_reset, dict(post_reset_redirect='/custom/')), url(r'^password_reset/custom_redirect/named/$', views.password_reset, dict(post_reset_redirect='password_reset')), @@ -90,16 +34,4 @@ urlpatterns += [ url(r'^admin_password_reset/$', views.password_reset, dict(is_admin_site=True)), url(r'^login_required/$', login_required(views.password_reset)), url(r'^login_required_login_url/$', login_required(views.password_reset, login_url='/somewhere/')), - - url(r'^auth_processor_no_attr_access/$', auth_processor_no_attr_access), - url(r'^auth_processor_attr_access/$', auth_processor_attr_access), - url(r'^auth_processor_user/$', auth_processor_user), - url(r'^auth_processor_perms/$', auth_processor_perms), - url(r'^auth_processor_perm_in_perms/$', auth_processor_perm_in_perms), - url(r'^auth_processor_messages/$', auth_processor_messages), - url(r'^custom_request_auth_login/$', custom_request_auth_login), - url(r'^userpage/(.+)/$', userpage, name="userpage"), - - # This line is only required to render the password reset with is_admin=True - url(r'^admin/', include(admin.site.urls)), ] diff --git a/rest_auth/tests/settings.py b/rest_auth/tests/settings.py index aab38d3..743759c 100644 --- a/rest_auth/tests/settings.py +++ b/rest_auth/tests/settings.py @@ -45,6 +45,20 @@ TEMPLATE_CONTEXT_PROCESSORS = [ "allauth.socialaccount.context_processors.socialaccount", ] +# avoid deprecation warnings during tests +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + # insert your TEMPLATE_DIRS here + ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': TEMPLATE_CONTEXT_PROCESSORS, + }, + }, +] + REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', @@ -79,3 +93,10 @@ INSTALLED_APPS = [ SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd" ACCOUNT_ACTIVATION_DAYS = 1 SITE_ID = 1 + +AUTHENTICATION_BACKENDS = ( + # Needed to login by username in Django admin, regardless of `allauth` + 'django.contrib.auth.backends.ModelBackend', + # `allauth` specific authentication methods, such as login by e-mail + 'allauth.account.auth_backends.AuthenticationBackend', +) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index a74e409..b3daec4 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -1,16 +1,16 @@ from django.core.urlresolvers import reverse -from django.test import TestCase +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.test.utils import override_settings from django.utils.encoding import force_text from rest_framework import status - +from allauth.account import app_settings as account_app_settings from .test_base import BaseAPITestCase +@override_settings(ROOT_URLCONF="tests.urls") class APITestCase1(TestCase, BaseAPITestCase): """ Case #1: @@ -18,7 +18,7 @@ class APITestCase1(TestCase, BaseAPITestCase): - custom registration: backend defined """ - urls = 'tests.urls' + # urls = 'tests.urls' USERNAME = 'person' PASS = 'person' @@ -57,7 +57,36 @@ class APITestCase1(TestCase, BaseAPITestCase): result['token'] = default_token_generator.make_token(user) return result - def test_login(self): + @override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.EMAIL) + def test_login_failed_email_validation(self): + payload = { + "email": '', + "password": self.PASS + } + + resp = self.post(self.login_url, data=payload, status_code=400) + self.assertEqual(resp.json['non_field_errors'][0], u'Must include "email" and "password".') + + @override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.USERNAME) + def test_login_failed_username_validation(self): + payload = { + "username": '', + "password": self.PASS + } + + resp = self.post(self.login_url, data=payload, status_code=400) + self.assertEqual(resp.json['non_field_errors'][0], u'Must include "username" and "password".') + + @override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.USERNAME_EMAIL) + def test_login_failed_username_email_validation(self): + payload = { + "password": self.PASS + } + + resp = self.post(self.login_url, data=payload, status_code=400) + self.assertEqual(resp.json['non_field_errors'][0], u'Must include either "username" or "email" and "password".') + + def test_allauth_login_with_username(self): payload = { "username": self.USERNAME, "password": self.PASS @@ -91,6 +120,22 @@ class APITestCase1(TestCase, BaseAPITestCase): # test empty payload self.post(self.login_url, data={}, status_code=400) + @override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.EMAIL) + def test_allauth_login_with_email(self): + payload = { + "email": self.EMAIL, + "password": self.PASS + } + # there is no users in db so it should throw error (400) + self.post(self.login_url, data=payload, status_code=400) + + self.post(self.password_change_url, status_code=403) + + # create user + user = get_user_model().objects.create_user(self.EMAIL, email=self.EMAIL, password=self.PASS) + + self.post(self.login_url, data=payload, status_code=200) + @override_settings(REST_USE_JWT=True) def test_login_jwt(self): payload = { @@ -148,6 +193,9 @@ class APITestCase1(TestCase, BaseAPITestCase): # test empty payload self.post(self.login_url, data={}, status_code=400) + # bring back allauth + settings.INSTALLED_APPS.append('allauth') + def test_password_change(self): login_payload = { "username": self.USERNAME, diff --git a/rest_auth/tests/test_base.py b/rest_auth/tests/test_base.py index 97dfa31..0c18afb 100644 --- a/rest_auth/tests/test_base.py +++ b/rest_auth/tests/test_base.py @@ -45,12 +45,14 @@ class BaseAPITestCase(object): self.response = request_func(*args, **kwargs) is_json = bool( [x for x in self.response._headers['content-type'] if 'json' in x]) + + self.response.json = {} if is_json and self.response.content: self.response.json = json.loads(force_text(self.response.content)) - else: - self.response.json = {} + if status_code: self.assertEqual(self.response.status_code, status_code) + return self.response def post(self, *args, **kwargs): diff --git a/rest_auth/tests/test_social.py b/rest_auth/tests/test_social.py index a06e365..ac25977 100644 --- a/rest_auth/tests/test_social.py +++ b/rest_auth/tests/test_social.py @@ -12,10 +12,9 @@ from rest_framework import status from .test_base import BaseAPITestCase +@override_settings(ROOT_URLCONF="tests.urls") class TestSocialAuth(TestCase, BaseAPITestCase): - urls = 'tests.urls' - USERNAME = 'person' PASS = 'person' EMAIL = "person1@world.com" From c4e7bdc77f03b2e1dc4a5f98a78f8133c53719c4 Mon Sep 17 00:00:00 2001 From: mariodev Date: Tue, 28 Jun 2016 20:20:53 +0200 Subject: [PATCH 44/52] pep8 --- rest_auth/registration/views.py | 1 - rest_auth/tests/django_urls.py | 9 +-------- rest_auth/tests/test_api.py | 2 +- rest_auth/tests/test_base.py | 2 +- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index c38f317..713c46e 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -6,7 +6,6 @@ from rest_framework.response import Response from rest_framework.permissions import AllowAny from rest_framework.generics import CreateAPIView from rest_framework import status -from rest_framework.exceptions import MethodNotAllowed from allauth.account.views import ConfirmEmailView from allauth.account.utils import complete_signup diff --git a/rest_auth/tests/django_urls.py b/rest_auth/tests/django_urls.py index 9fb6424..c1fb050 100644 --- a/rest_auth/tests/django_urls.py +++ b/rest_auth/tests/django_urls.py @@ -1,16 +1,9 @@ # Moved in Django 1.8 from django to tests/auth_tests/urls.py -from django.conf.urls import include, url -from django.contrib import admin +from django.conf.urls import url from django.contrib.auth import views from django.contrib.auth.decorators import login_required -from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.urls import urlpatterns -from django.contrib.messages.api import info -from django.http import HttpRequest, HttpResponse -from django.shortcuts import render -from django.template import RequestContext, Template -from django.views.decorators.cache import never_cache # special urls for auth test cases diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index b3daec4..e966d3f 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -132,7 +132,7 @@ class APITestCase1(TestCase, BaseAPITestCase): self.post(self.password_change_url, status_code=403) # create user - user = get_user_model().objects.create_user(self.EMAIL, email=self.EMAIL, password=self.PASS) + get_user_model().objects.create_user(self.EMAIL, email=self.EMAIL, password=self.PASS) self.post(self.login_url, data=payload, status_code=200) diff --git a/rest_auth/tests/test_base.py b/rest_auth/tests/test_base.py index 0c18afb..992b158 100644 --- a/rest_auth/tests/test_base.py +++ b/rest_auth/tests/test_base.py @@ -49,7 +49,7 @@ class BaseAPITestCase(object): self.response.json = {} if is_json and self.response.content: self.response.json = json.loads(force_text(self.response.content)) - + if status_code: self.assertEqual(self.response.status_code, status_code) From 8812ba11af884424abc7f0106f44f069d1680216 Mon Sep 17 00:00:00 2001 From: Henk Kraal Date: Thu, 9 Jun 2016 11:03:59 +0200 Subject: [PATCH 45/52] Added ``django.contrib.sites`` and SITE_ID setting requirements of ``allauth`` to installation instructions Fixes #218 --- docs/installation.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index e070c73..5a87028 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -40,16 +40,21 @@ Registration (optional) 1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[with_social]``. -2. Add ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py: +2. Add ``django.contrib.sites``, ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py: + +3. Add ``SITE_ID = 1`` to your django settings.py .. code-block:: python INSTALLED_APPS = ( ..., + 'django.contrib.sites', 'allauth', 'allauth.account', 'rest_auth.registration', ) + + SITE_ID = 1 3. Add rest_auth.registration urls: @@ -79,6 +84,7 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati 'rest_framework.authtoken', 'rest_auth' ..., + 'django.contrib.sites', 'allauth', 'allauth.account', 'rest_auth.registration', From 8635cec373a57569d03c324124f9044ab9c50aa2 Mon Sep 17 00:00:00 2001 From: Omid Raha Date: Sat, 16 Jul 2016 16:37:49 +0430 Subject: [PATCH 46/52] Fix patterns function name on the installation pgae. It's `patterns`, That used as `from django.conf.urls import patterns` --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 5a87028..6144011 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -114,7 +114,7 @@ Facebook .. code-block:: python - urlpatterns += pattern('', + urlpatterns += patterns('', ..., url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login') ) @@ -141,7 +141,7 @@ If you are using Twitter for your social authentication, it is a bit different s .. code-block:: python - urlpatterns += pattern('', + urlpatterns += patterns('', ..., url(r'^rest-auth/twitter/$', TwitterLogin.as_view(), name='twitter_login') ) From c5e0382d2504193d5eac2f316dc4f9bd9d9243c4 Mon Sep 17 00:00:00 2001 From: mariodev Date: Mon, 18 Jul 2016 07:06:28 +0200 Subject: [PATCH 47/52] Increased test coverage (#229) * Added twitter login test * pep8 * Fixes missing backend attr issue * Refactored login process * pep8 * Added more tests for twitter social login --- rest_auth/registration/views.py | 4 + rest_auth/social_serializers.py | 9 +- rest_auth/tests/settings.py | 1 + rest_auth/tests/test_base.py | 3 + rest_auth/tests/test_social.py | 160 +++++++++++++++++++++++++++++++- rest_auth/tests/urls.py | 30 ++++++ rest_auth/views.py | 14 ++- 7 files changed, 208 insertions(+), 13 deletions(-) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 713c46e..40f895e 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -7,6 +7,7 @@ from rest_framework.permissions import AllowAny from rest_framework.generics import CreateAPIView from rest_framework import status +from allauth.account.adapter import get_adapter from allauth.account.views import ConfirmEmailView from allauth.account.utils import complete_signup from allauth.account import app_settings as allauth_settings @@ -101,3 +102,6 @@ class SocialLoginView(LoginView): """ serializer_class = SocialLoginSerializer + + def process_login(self): + get_adapter(self.request).login(self.request, self.user) diff --git a/rest_auth/social_serializers.py b/rest_auth/social_serializers.py index de6223f..6e06be5 100644 --- a/rest_auth/social_serializers.py +++ b/rest_auth/social_serializers.py @@ -46,14 +46,11 @@ class TwitterLoginSerializer(serializers.Serializer): if not adapter_class: raise serializers.ValidationError('Define adapter_class in view') - adapter = adapter_class() + adapter = adapter_class(request) app = adapter.get_provider().get_app(request) - if('access_token' in attrs) and ('token_secret' in attrs): - access_token = attrs.get('access_token') - token_secret = attrs.get('token_secret') - else: - raise serializers.ValidationError('Incorrect input. access_token and token_secret are required.') + access_token = attrs.get('access_token') + token_secret = attrs.get('token_secret') request.session['oauth_api.twitter.com_access_token'] = { 'oauth_token': access_token, diff --git a/rest_auth/tests/settings.py b/rest_auth/tests/settings.py index 743759c..060cc89 100644 --- a/rest_auth/tests/settings.py +++ b/rest_auth/tests/settings.py @@ -80,6 +80,7 @@ INSTALLED_APPS = [ 'allauth.account', 'allauth.socialaccount', 'allauth.socialaccount.providers.facebook', + 'allauth.socialaccount.providers.twitter', 'rest_framework', 'rest_framework.authtoken', diff --git a/rest_auth/tests/test_base.py b/rest_auth/tests/test_base.py index 992b158..faaf7bb 100644 --- a/rest_auth/tests/test_base.py +++ b/rest_auth/tests/test_base.py @@ -99,6 +99,9 @@ class BaseAPITestCase(object): self.user_url = reverse('rest_user_details') self.veirfy_email_url = reverse('rest_verify_email') self.fb_login_url = reverse('fb_login') + self.tw_login_url = reverse('tw_login') + self.tw_login_no_view_url = reverse('tw_login_no_view') + self.tw_login_no_adapter_url = reverse('tw_login_no_adapter') def _login(self): payload = { diff --git a/rest_auth/tests/test_social.py b/rest_auth/tests/test_social.py index ac25977..9356acd 100644 --- a/rest_auth/tests/test_social.py +++ b/rest_auth/tests/test_social.py @@ -1,3 +1,5 @@ +import json + from django.test import TestCase from django.contrib.auth import get_user_model from django.test.utils import override_settings @@ -34,9 +36,19 @@ class TestSocialAuth(TestCase, BaseAPITestCase): client_id='123123123', secret='321321321', ) + + twitter_social_app = SocialApp.objects.create( + provider='twitter', + name='Twitter', + client_id='11223344', + secret='55667788', + ) + site = Site.objects.get_current() social_app.sites.add(site) + twitter_social_app.sites.add(site) self.graph_api_url = GRAPH_API_URL + '/me' + self.twitter_url = 'http://twitter.com/foobarme' @responses.activate def test_failed_social_auth(self): @@ -57,11 +69,24 @@ class TestSocialAuth(TestCase, BaseAPITestCase): @responses.activate def test_social_auth(self): # fake response for facebook call - 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 + 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 + } + responses.add( responses.GET, self.graph_api_url, - body=resp_body, + body=json.dumps(resp_body), status=200, content_type='application/json' ) @@ -80,6 +105,119 @@ class TestSocialAuth(TestCase, BaseAPITestCase): self.assertIn('key', self.response.json.keys()) self.assertEqual(get_user_model().objects.all().count(), users_count + 1) + def _twitter_social_auth(self): + # fake response for twitter call + resp_body = { + "id": "123123123123", + } + + responses.add( + responses.GET, + 'https://api.twitter.com/1.1/account/verify_credentials.json', + body=json.dumps(resp_body), + status=200, + content_type='application/json' + ) + + users_count = get_user_model().objects.all().count() + payload = { + 'access_token': 'abc123', + 'token_secret': '1111222233334444' + } + + self.post(self.tw_login_url, data=payload) + + self.assertIn('key', self.response.json.keys()) + self.assertEqual(get_user_model().objects.all().count(), users_count + 1) + + # make sure that second request will not create a new user + self.post(self.tw_login_url, data=payload, status_code=200) + self.assertIn('key', self.response.json.keys()) + self.assertEqual(get_user_model().objects.all().count(), users_count + 1) + + @responses.activate + @override_settings(SOCIALACCOUNT_AUTO_SIGNUP=True) + def test_twitter_social_auth(self): + self._twitter_social_auth() + + @responses.activate + @override_settings(SOCIALACCOUNT_AUTO_SIGNUP=False) + def test_twitter_social_auth_without_auto_singup(self): + self._twitter_social_auth() + + @responses.activate + def test_twitter_social_auth_request_error(self): + # fake response for twitter call + resp_body = { + "id": "123123123123", + } + + responses.add( + responses.GET, + 'https://api.twitter.com/1.1/account/verify_credentials.json', + body=json.dumps(resp_body), + status=400, + content_type='application/json' + ) + + users_count = get_user_model().objects.all().count() + payload = { + 'access_token': 'abc123', + 'token_secret': '1111222233334444' + } + + self.post(self.tw_login_url, data=payload, status_code=400) + self.assertNotIn('key', self.response.json.keys()) + self.assertEqual(get_user_model().objects.all().count(), users_count) + + @responses.activate + def test_twitter_social_auth_no_view_in_context(self): + # fake response for twitter call + resp_body = { + "id": "123123123123", + } + + responses.add( + responses.GET, + 'https://api.twitter.com/1.1/account/verify_credentials.json', + body=json.dumps(resp_body), + status=400, + content_type='application/json' + ) + + users_count = get_user_model().objects.all().count() + payload = { + 'access_token': 'abc123', + 'token_secret': '1111222233334444' + } + + self.post(self.tw_login_no_view_url, data=payload, status_code=400) + self.assertEqual(get_user_model().objects.all().count(), users_count) + + @responses.activate + def test_twitter_social_auth_no_adapter(self): + # fake response for twitter call + resp_body = { + "id": "123123123123", + } + + responses.add( + responses.GET, + 'https://api.twitter.com/1.1/account/verify_credentials.json', + body=json.dumps(resp_body), + status=400, + content_type='application/json' + ) + + users_count = get_user_model().objects.all().count() + payload = { + 'access_token': 'abc123', + 'token_secret': '1111222233334444' + } + + self.post(self.tw_login_no_adapter_url, data=payload, status_code=400) + self.assertEqual(get_user_model().objects.all().count(), users_count) + @responses.activate @override_settings( ACCOUNT_EMAIL_VERIFICATION='mandatory', @@ -87,11 +225,25 @@ class TestSocialAuth(TestCase, BaseAPITestCase): REST_SESSION_LOGIN=False ) def test_edge_case(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,"email":"%s"}' # noqa + 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, + "email": self.EMAIL + } + responses.add( responses.GET, self.graph_api_url, - body=resp_body % self.EMAIL, + body=json.dumps(resp_body), status=200, content_type='application/json' ) diff --git a/rest_auth/tests/urls.py b/rest_auth/tests/urls.py index d922f7f..c33d390 100644 --- a/rest_auth/tests/urls.py +++ b/rest_auth/tests/urls.py @@ -3,14 +3,41 @@ from django.views.generic import TemplateView from . import django_urls from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter +from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter + +from rest_framework.decorators import api_view from rest_auth.urls import urlpatterns from rest_auth.registration.views import SocialLoginView +from rest_auth.social_serializers import TwitterLoginSerializer class FacebookLogin(SocialLoginView): adapter_class = FacebookOAuth2Adapter + +class TwitterLogin(SocialLoginView): + adapter_class = TwitterOAuthAdapter + serializer_class = TwitterLoginSerializer + + +class TwitterLoginSerializerFoo(TwitterLoginSerializer): + pass + + +@api_view(['POST']) +def twitter_login_view(request): + serializer = TwitterLoginSerializerFoo( + data={'access_token': '11223344', 'token_secret': '55667788'}, + context={'request': request} + ) + serializer.is_valid(raise_exception=True) + + +class TwitterLoginNoAdapter(SocialLoginView): + serializer_class = TwitterLoginSerializer + + urlpatterns += [ url(r'^rest-registration/', include('rest_auth.registration.urls')), url(r'^test-admin/', include(django_urls)), @@ -19,5 +46,8 @@ urlpatterns += [ url(r'^account-confirm-email/(?P\w+)/$', TemplateView.as_view(), name='account_confirm_email'), url(r'^social-login/facebook/$', FacebookLogin.as_view(), name='fb_login'), + 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')) ] diff --git a/rest_auth/views.py b/rest_auth/views.py index 55f767d..0761600 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -1,4 +1,7 @@ -from django.contrib.auth import login, logout +from django.contrib.auth import ( + login as django_login, + logout as django_logout +) from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import ugettext_lazy as _ @@ -37,6 +40,9 @@ class LoginView(GenericAPIView): serializer_class = LoginSerializer token_model = TokenModel + def process_login(self): + django_login(self.request, self.user) + def get_response_serializer(self): if getattr(settings, 'REST_USE_JWT', False): response_serializer = JWTSerializer @@ -53,7 +59,7 @@ class LoginView(GenericAPIView): self.token = create_token(self.token_model, self.user, self.serializer) if getattr(settings, 'REST_SESSION_LOGIN', True): - login(self.request, self.user) + self.process_login() def get_response(self): serializer_class = self.get_response_serializer() @@ -70,8 +76,10 @@ class LoginView(GenericAPIView): return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request, *args, **kwargs): + self.request = request self.serializer = self.get_serializer(data=self.request.data) self.serializer.is_valid(raise_exception=True) + self.login() return self.get_response() @@ -106,7 +114,7 @@ class LogoutView(APIView): except (AttributeError, ObjectDoesNotExist): pass - logout(request) + django_logout(request) return Response({"success": _("Successfully logged out.")}, status=status.HTTP_200_OK) From 235efa4ec1a61a25ecb147a131789780f252f1fd Mon Sep 17 00:00:00 2001 From: mario Date: Mon, 18 Jul 2016 07:20:02 +0200 Subject: [PATCH 48/52] Dropping django 1.7 support --- .travis.yml | 7 +++---- demo/requirements.pip | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index fcc572a..61a3dd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,8 @@ python: - "2.7" - "3.5" env: - - DJANGO=1.7.11 - - DJANGO=1.8.9 - - DJANGO=1.9.2 + - DJANGO=1.8.13 + - DJANGO=1.9.7 install: - pip install -q Django==$DJANGO --use-mirrors - pip install coveralls @@ -13,7 +12,7 @@ install: matrix: exclude: - python: "3.5" - env: DJANGO=1.7.11 + env: DJANGO=1.8.13 script: - coverage run --source=rest_auth setup.py test after_success: diff --git a/demo/requirements.pip b/demo/requirements.pip index 4949275..353576e 100644 --- a/demo/requirements.pip +++ b/demo/requirements.pip @@ -1,4 +1,4 @@ -django>=1.7.0 +django>=1.8.0 django-rest-auth==0.7.0 django-allauth==0.24.1 six==1.9.0 diff --git a/setup.py b/setup.py index e4e6c1f..5a90ae7 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ setup( keywords='django rest auth registration rest-framework django-registration api', zip_safe=False, install_requires=[ - 'Django>=1.7.0', + 'Django>=1.8.0', 'djangorestframework>=3.1.0', 'six>=1.9.0', ], From ff641cf31c041da3b508252f242872228f117e49 Mon Sep 17 00:00:00 2001 From: vsevolod kolchinsky Date: Mon, 18 Jul 2016 09:09:51 +0300 Subject: [PATCH 49/52] Throttling documentation added --- docs/configuration.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/configuration.rst b/docs/configuration.rst index c65b4f7..7e4f280 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -49,3 +49,20 @@ Configuration - **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 + + +Throttling +============= + +You may specify custom throttling for ``rest_auth.register.views.RegisterView`` by specifying DRF settings: + + .. code-block:: python + + REST_FRAMEWORK = { + 'DEFAULT_THROTTLE_RATES': { + 'anon': '6/m', + 'register_view':'1/h', + }, + } + + From 9df528f482871673107de38649bb621090ca66f1 Mon Sep 17 00:00:00 2001 From: mario Date: Thu, 28 Jul 2016 20:14:26 +0200 Subject: [PATCH 50/52] Changed confirm email url path + test fixes --- rest_auth/registration/urls.py | 2 +- rest_auth/registration/views.py | 1 + rest_auth/tests/test_api.py | 3 ++- rest_auth/tests/test_social.py | 3 ++- rest_auth/tests/urls.py | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/rest_auth/registration/urls.py b/rest_auth/registration/urls.py index d5504c5..8f4d0a2 100644 --- a/rest_auth/registration/urls.py +++ b/rest_auth/registration/urls.py @@ -18,6 +18,6 @@ urlpatterns = [ # If you don't want to use API on that step, then just use ConfirmEmailView # view from: # django-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190 - url(r'^account-confirm-email/(?P\w+)/$', TemplateView.as_view(), + url(r'^account-confirm-email/(?P[-:\w]+)/$', TemplateView.as_view(), name='account_confirm_email'), ] diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index a5bf2f4..3a205aa 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -58,6 +58,7 @@ class RegisterView(CreateAPIView): self.token = jwt_encode(user) else: create_token(self.token_model, user, serializer) + complete_signup(self.request._request, user, allauth_settings.EMAIL_VERIFICATION, None) diff --git a/rest_auth/tests/test_api.py b/rest_auth/tests/test_api.py index e966d3f..a24e9f0 100644 --- a/rest_auth/tests/test_api.py +++ b/rest_auth/tests/test_api.py @@ -419,7 +419,8 @@ class APITestCase1(TestCase, BaseAPITestCase): @override_settings( ACCOUNT_EMAIL_VERIFICATION='mandatory', - ACCOUNT_EMAIL_REQUIRED=True + ACCOUNT_EMAIL_REQUIRED=True, + ACCOUNT_EMAIL_CONFIRMATION_HMAC=False ) def test_registration_with_email_verification(self): user_count = get_user_model().objects.all().count() diff --git a/rest_auth/tests/test_social.py b/rest_auth/tests/test_social.py index 9356acd..56bdace 100644 --- a/rest_auth/tests/test_social.py +++ b/rest_auth/tests/test_social.py @@ -222,7 +222,8 @@ class TestSocialAuth(TestCase, BaseAPITestCase): @override_settings( ACCOUNT_EMAIL_VERIFICATION='mandatory', ACCOUNT_EMAIL_REQUIRED=True, - REST_SESSION_LOGIN=False + REST_SESSION_LOGIN=False, + ACCOUNT_EMAIL_CONFIRMATION_HMAC=False ) def test_edge_case(self): resp_body = { diff --git a/rest_auth/tests/urls.py b/rest_auth/tests/urls.py index c33d390..6371218 100644 --- a/rest_auth/tests/urls.py +++ b/rest_auth/tests/urls.py @@ -43,7 +43,7 @@ urlpatterns += [ url(r'^test-admin/', include(django_urls)), url(r'^account-email-verification-sent/$', TemplateView.as_view(), name='account_email_verification_sent'), - url(r'^account-confirm-email/(?P\w+)/$', TemplateView.as_view(), + url(r'^account-confirm-email/(?P[-:\w]+)/$', TemplateView.as_view(), name='account_confirm_email'), url(r'^social-login/facebook/$', FacebookLogin.as_view(), name='fb_login'), url(r'^social-login/twitter/$', TwitterLogin.as_view(), name='tw_login'), From 18f8178ce62111fb6b4c5d73d3c6d4efc009514f Mon Sep 17 00:00:00 2001 From: mario Date: Thu, 28 Jul 2016 21:16:35 +0200 Subject: [PATCH 51/52] More release changes (0.8.0) --- demo/requirements.pip | 4 ++-- docs/changelog.rst | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/demo/requirements.pip b/demo/requirements.pip index 353576e..a4ea311 100644 --- a/demo/requirements.pip +++ b/demo/requirements.pip @@ -1,4 +1,4 @@ django>=1.8.0 -django-rest-auth==0.7.0 -django-allauth==0.24.1 +django-rest-auth==0.8.0 +django-allauth>=0.24.1 six==1.9.0 diff --git a/docs/changelog.rst b/docs/changelog.rst index a24d7f5..09201f4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog 0.8.0 ----- - added support for django-rest-framework-jwt +- bugfixes 0.7.0 ----- From e9215f41057de358a7335c54cc9893a6dcf5dfcd Mon Sep 17 00:00:00 2001 From: mario Date: Thu, 28 Jul 2016 22:45:39 +0200 Subject: [PATCH 52/52] Bump to v0.8.1 --- demo/requirements.pip | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/requirements.pip b/demo/requirements.pip index a4ea311..c68a521 100644 --- a/demo/requirements.pip +++ b/demo/requirements.pip @@ -1,4 +1,4 @@ django>=1.8.0 -django-rest-auth==0.8.0 +django-rest-auth==0.8.1 django-allauth>=0.24.1 six==1.9.0 diff --git a/setup.py b/setup.py index 5a90ae7..7bada0d 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ f.close() setup( name='django-rest-auth', - version='0.8.0', + version='0.8.1', author='Sumit Chachra', author_email='chachra@tivix.com', url='http://github.com/Tivix/django-rest-auth',