mirror of
				https://github.com/Tivix/django-rest-auth.git
				synced 2025-11-01 00:07:39 +03:00 
			
		
		
		
	Merge branch 'master' into jwt-support
# Conflicts: # docs/configuration.rst # rest_auth/registration/views.py # rest_auth/tests/test_api.py # rest_auth/utils.py # rest_auth/views.py
This commit is contained in:
		
						commit
						f8a9cc152d
					
				|  | @ -1,8 +1,8 @@ | |||
| from django.conf.urls import patterns, include, url | ||||
| from django.conf.urls import include, url | ||||
| from django.contrib import admin | ||||
| from django.views.generic import TemplateView, RedirectView | ||||
| 
 | ||||
| urlpatterns = patterns('', | ||||
| urlpatterns = [ | ||||
|     url(r'^$', TemplateView.as_view(template_name="home.html"), name='home'), | ||||
|     url(r'^signup/$', TemplateView.as_view(template_name="signup.html"), | ||||
|         name='signup'), | ||||
|  | @ -36,4 +36,4 @@ urlpatterns = patterns('', | |||
|     url(r'^account/', include('allauth.urls')), | ||||
|     url(r'^admin/', include(admin.site.urls)), | ||||
|     url(r'^accounts/profile/$', RedirectView.as_view(url='/', permanent=True), name='profile-redirect'), | ||||
| ) | ||||
| ] | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ Basic | |||
| - /rest-auth/login/ (POST) | ||||
| 
 | ||||
|     - username (string) | ||||
|     - email (string) | ||||
|     - password (string) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -55,16 +56,6 @@ Registration | |||
|     - password2 | ||||
|     - email | ||||
| 
 | ||||
|     .. note:: This endpoint is based on ``allauth.account.views.SignupView`` and uses the same form as in this view. To override fields you have to create custom Signup Form and define it in django settings: | ||||
| 
 | ||||
|         .. code-block:: python | ||||
| 
 | ||||
|             ACCOUNT_FORMS = { | ||||
|                 'signup': 'path.to.custom.SignupForm' | ||||
|             } | ||||
| 
 | ||||
|         See allauth documentation for more details. | ||||
| 
 | ||||
| - /rest-auth/registration/verify-email/ (POST) | ||||
| 
 | ||||
|     - key | ||||
|  |  | |||
|  | @ -29,12 +29,19 @@ Configuration | |||
|             ... | ||||
|         } | ||||
| 
 | ||||
| - **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True) | ||||
| - **REST_AUTH_REGISTRATION_SERIALIZERS** | ||||
| 
 | ||||
|     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`` | ||||
| 
 | ||||
| - **REST_AUTH_TOKEN_MODEL** - model class for tokens, default value ``rest_framework.authtoken.models`` | ||||
| 
 | ||||
| - **REST_AUTH_TOKEN_CREATOR** - callable to create tokens, default value ``rest_auth.utils.default_create_token``. | ||||
| 
 | ||||
| - **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 <http://getblimp.github.io/django-rest-framework-jwt/>` as a backend, and instead of session based tokens or Social Login keys, it will return a JWT.  | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,7 +17,12 @@ FAQ | |||
|     djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190 | ||||
| 
 | ||||
| 
 | ||||
| 2. How can I update UserProfile assigned to User model? | ||||
| 2. I get an error: Reverse for 'password_reset_confirm' not found. | ||||
| 
 | ||||
|     You need to add `password_reset_confirm` url into your ``urls.py`` (at the top of any other included urls). Please check the ``urls.py`` module inside demo app example for more details. | ||||
| 
 | ||||
| 
 | ||||
| 3. How can I update UserProfile assigned to User model? | ||||
| 
 | ||||
|     Assuming you already have UserProfile model defined like this | ||||
| 
 | ||||
|  |  | |||
|  | @ -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`` - see this doc for installation http://django-allauth.readthedocs.org/en/latest/installation.html. | ||||
| 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]``. | ||||
| 
 | ||||
| 2. Add ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py: | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,8 +8,10 @@ from rest_auth.serializers import ( | |||
|     PasswordResetSerializer as DefaultPasswordResetSerializer, | ||||
|     PasswordResetConfirmSerializer as DefaultPasswordResetConfirmSerializer, | ||||
|     PasswordChangeSerializer as DefaultPasswordChangeSerializer) | ||||
| from .utils import import_callable | ||||
| from .utils import import_callable, default_create_token | ||||
| 
 | ||||
| create_token = import_callable( | ||||
|     getattr(settings, 'REST_AUTH_TOKEN_CREATOR', default_create_token)) | ||||
| 
 | ||||
| serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {}) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,10 @@ | |||
| # from django.db import models | ||||
| from django.conf import settings | ||||
| 
 | ||||
| from rest_framework.authtoken.models import Token as DefaultTokenModel | ||||
| 
 | ||||
| from .utils import import_callable | ||||
| 
 | ||||
| # Register your models here. | ||||
| 
 | ||||
| TokenModel = import_callable( | ||||
|     getattr(settings, 'REST_AUTH_TOKEN_MODEL', DefaultTokenModel)) | ||||
|  |  | |||
							
								
								
									
										11
									
								
								rest_auth/registration/app_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								rest_auth/registration/app_settings.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| from django.conf import settings | ||||
| 
 | ||||
| from rest_auth.registration.serializers import ( | ||||
|     RegisterSerializer as DefaultRegisterSerializer) | ||||
| from ..utils import import_callable | ||||
| 
 | ||||
| 
 | ||||
| serializers = getattr(settings, 'REST_AUTH_REGISTER_SERIALIZERS', {}) | ||||
| 
 | ||||
| RegisterSerializer = import_callable( | ||||
|     serializers.get('REGISTER_SERIALIZER', DefaultRegisterSerializer)) | ||||
|  | @ -1,17 +1,26 @@ | |||
| from django.http import HttpRequest | ||||
| from django.conf import settings | ||||
| 
 | ||||
| try: | ||||
|     from allauth.account import app_settings as allauth_settings | ||||
|     from allauth.utils import (email_address_exists, | ||||
|                                get_username_max_length) | ||||
|     from allauth.account.adapter import get_adapter | ||||
|     from allauth.account.utils import setup_user_email | ||||
| except ImportError: | ||||
|     raise ImportError('allauth needs to be added to INSTALLED_APPS.') | ||||
| 
 | ||||
| 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: | ||||
|     raise ImportError('allauth.socialaccount needs to be installed.') | ||||
| 
 | ||||
| if 'allauth.socialaccount' not in settings.INSTALLED_APPS: | ||||
|     raise ImportError('allauth.socialaccount needs to be added to INSTALLED_APPS.') | ||||
| if 'allauth.socialaccount' in settings.INSTALLED_APPS: | ||||
|     try: | ||||
|         from allauth.socialaccount.helpers import complete_social_login | ||||
|     except ImportError: | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class SocialLoginSerializer(serializers.Serializer): | ||||
|  | @ -109,3 +118,57 @@ class SocialLoginSerializer(serializers.Serializer): | |||
|         attrs['user'] = login.account.user | ||||
| 
 | ||||
|         return attrs | ||||
| 
 | ||||
| 
 | ||||
| class RegisterSerializer(serializers.Serializer): | ||||
|     username = serializers.CharField( | ||||
|         max_length=get_username_max_length(), | ||||
|         min_length=allauth_settings.USERNAME_MIN_LENGTH, | ||||
|         required=allauth_settings.USERNAME_REQUIRED | ||||
|     ) | ||||
|     email = serializers.EmailField(required=allauth_settings.EMAIL_REQUIRED) | ||||
|     password1 = serializers.CharField(required=True, write_only=True) | ||||
|     password2 = serializers.CharField(required=True, write_only=True) | ||||
| 
 | ||||
|     def validate_username(self, username): | ||||
|         username = get_adapter().clean_username(username) | ||||
|         return username | ||||
| 
 | ||||
|     def validate_email(self, email): | ||||
|         email = get_adapter().clean_email(email) | ||||
|         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.") | ||||
|         return email | ||||
| 
 | ||||
|     def validate_password1(self, password): | ||||
|         return get_adapter().clean_password(password) | ||||
| 
 | ||||
|     def validate(self, data): | ||||
|         if data['password1'] != data['password2']: | ||||
|             raise serializers.ValidationError("The two password fields didn't match.") | ||||
|         return data | ||||
| 
 | ||||
|     def custom_signup(self, request, user): | ||||
|         pass | ||||
| 
 | ||||
|     def get_cleaned_data(self): | ||||
|         return { | ||||
|             'username': self.validated_data.get('username', ''), | ||||
|             'password1': self.validated_data.get('password1', ''), | ||||
|             'email': self.validated_data.get('email', '') | ||||
|         } | ||||
| 
 | ||||
|     def save(self, request): | ||||
|         adapter = get_adapter() | ||||
|         user = adapter.new_user(request) | ||||
|         self.cleaned_data = self.get_cleaned_data() | ||||
|         adapter.save_user(request, user, self) | ||||
|         self.custom_signup(request, user) | ||||
|         setup_user_email(request, user, []) | ||||
|         return user | ||||
| 
 | ||||
| 
 | ||||
| class VerifyEmailSerializer(serializers.Serializer): | ||||
|     key = serializers.CharField() | ||||
|  |  | |||
|  | @ -1,10 +1,9 @@ | |||
| from django.views.generic import TemplateView | ||||
| from django.conf.urls import patterns, url | ||||
| from django.conf.urls import url | ||||
| 
 | ||||
| from .views import RegisterView, VerifyEmailView | ||||
| 
 | ||||
| urlpatterns = patterns( | ||||
|     '', | ||||
| urlpatterns = [ | ||||
|     url(r'^$', RegisterView.as_view(), name='rest_register'), | ||||
|     url(r'^verify-email/$', VerifyEmailView.as_view(), name='rest_verify_email'), | ||||
| 
 | ||||
|  | @ -21,4 +20,4 @@ urlpatterns = patterns( | |||
|     # djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190 | ||||
|     url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(), | ||||
|         name='account_confirm_email'), | ||||
| ) | ||||
| ] | ||||
|  |  | |||
|  | @ -1,101 +1,53 @@ | |||
| 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 | ||||
| from rest_framework.generics import CreateAPIView | ||||
| from rest_framework import status | ||||
| from rest_framework.authtoken.models import Token | ||||
| from rest_framework.exceptions import MethodNotAllowed | ||||
| 
 | ||||
| from allauth.account.views import SignupView, ConfirmEmailView | ||||
| from allauth.account.views import ConfirmEmailView | ||||
| from allauth.account.utils import complete_signup | ||||
| from allauth.account import app_settings | ||||
| from allauth.account import app_settings as allauth_settings | ||||
| 
 | ||||
| from rest_auth.app_settings import TokenSerializer | ||||
| from rest_auth.registration.serializers import SocialLoginSerializer | ||||
| from rest_auth.app_settings import (TokenSerializer, | ||||
|                                     create_token) | ||||
| from rest_auth.registration.serializers import (SocialLoginSerializer, | ||||
|                                                 VerifyEmailSerializer) | ||||
| from rest_auth.views import LoginView | ||||
| from rest_auth.models import TokenModel | ||||
| from .app_settings import RegisterSerializer | ||||
| 
 | ||||
| from rest_auth.utils import jwt_encode | ||||
| 
 | ||||
| class RegisterView(CreateAPIView): | ||||
|     serializer_class = RegisterSerializer | ||||
|     permission_classes = (AllowAny, ) | ||||
|     token_model = TokenModel | ||||
| 
 | ||||
| class RegisterView(APIView, SignupView): | ||||
|     """ | ||||
|     Accepts the credentials and creates a new user | ||||
|     if user does not exist already | ||||
|     Return the REST Token if the credentials are valid and authenticated. | ||||
|     Calls allauth complete_signup method | ||||
|     def get_response_data(self, user): | ||||
|         if allauth_settings.EMAIL_VERIFICATION == \ | ||||
|                 allauth_settings.EmailVerificationMethod.MANDATORY: | ||||
|             return {} | ||||
| 
 | ||||
|     Accept the following POST parameters: username, email, password | ||||
|     Return the REST Framework Token Object's key. | ||||
|     """ | ||||
|         return TokenSerializer(user.auth_token).data | ||||
| 
 | ||||
|     permission_classes = (AllowAny,) | ||||
|     allowed_methods = ('POST', 'OPTIONS', 'HEAD') | ||||
|     token_model = Token | ||||
|     def create(self, request, *args, **kwargs): | ||||
|         serializer = self.get_serializer(data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|         user = self.perform_create(serializer) | ||||
|         headers = self.get_success_headers(serializer.data) | ||||
| 
 | ||||
|     def get_serializer_class(self): | ||||
|         if getattr(settings, 'REST_USE_JWT', False): | ||||
|             return JWTSerializer | ||||
|         else: | ||||
|             return TokenSerializer | ||||
|         return Response(self.get_response_data(user), status=status.HTTP_201_CREATED, headers=headers) | ||||
| 
 | ||||
|     def get(self, *args, **kwargs): | ||||
|         return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) | ||||
| 
 | ||||
|     def put(self, *args, **kwargs): | ||||
|         return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) | ||||
| 
 | ||||
|     def form_valid(self, form): | ||||
|         self.user = form.save(self.request) | ||||
| 
 | ||||
|         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: | ||||
|             request = self.request._request | ||||
|         return complete_signup(request, self.user, | ||||
|                                app_settings.EMAIL_VERIFICATION, | ||||
|                                self.get_success_url()) | ||||
| 
 | ||||
|     def get_form_kwargs(self, *args, **kwargs): | ||||
|         kwargs = super(RegisterView, self).get_form_kwargs(*args, **kwargs) | ||||
|         kwargs['data'] = self.request.data | ||||
|         return kwargs | ||||
| 
 | ||||
|     def post(self, request, *args, **kwargs): | ||||
|         self.initial = {} | ||||
|         form_class = self.get_form_class() | ||||
|         self.form = self.get_form(form_class) | ||||
|         if self.form.is_valid(): | ||||
|             self.form_valid(self.form) | ||||
|             return self.get_response() | ||||
|         else: | ||||
|             return self.get_response_with_errors() | ||||
| 
 | ||||
|     def get_response(self): | ||||
|         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): | ||||
|         return Response(self.form.errors, status=status.HTTP_400_BAD_REQUEST) | ||||
|     def perform_create(self, serializer): | ||||
|         user = serializer.save(self.request) | ||||
|         create_token(self.token_model, user, serializer) | ||||
|         complete_signup(self.request._request, user, | ||||
|                         allauth_settings.EMAIL_VERIFICATION, | ||||
|                         None) | ||||
|         return user | ||||
| 
 | ||||
| 
 | ||||
| class VerifyEmailView(APIView, ConfirmEmailView): | ||||
|  | @ -104,10 +56,12 @@ class VerifyEmailView(APIView, ConfirmEmailView): | |||
|     allowed_methods = ('POST', 'OPTIONS', 'HEAD') | ||||
| 
 | ||||
|     def get(self, *args, **kwargs): | ||||
|         return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) | ||||
|         raise MethodNotAllowed('GET') | ||||
| 
 | ||||
|     def post(self, request, *args, **kwargs): | ||||
|         self.kwargs['key'] = self.request.data.get('key', '') | ||||
|         serializer = VerifyEmailSerializer(data=request.data) | ||||
|         serializer.is_valid(raise_exception=True) | ||||
|         self.kwargs['key'] = serializer.validated_data['key'] | ||||
|         confirmation = self.get_object() | ||||
|         confirmation.confirm(self.request) | ||||
|         return Response({'message': 'ok'}, status=status.HTTP_200_OK) | ||||
|  |  | |||
|  | @ -6,8 +6,9 @@ from django.utils.http import urlsafe_base64_decode as uid_decoder | |||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import force_text | ||||
| 
 | ||||
| from .models import TokenModel | ||||
| 
 | ||||
| from rest_framework import serializers, exceptions | ||||
| from rest_framework.authtoken.models import Token | ||||
| from rest_framework.exceptions import ValidationError | ||||
| 
 | ||||
| # Get the UserModel | ||||
|  | @ -19,43 +20,73 @@ class LoginSerializer(serializers.Serializer): | |||
|     email = serializers.EmailField(required=False, allow_blank=True) | ||||
|     password = serializers.CharField(style={'input_type': 'password'}) | ||||
| 
 | ||||
|     def _validate_email(self, email, password): | ||||
|         user = None | ||||
| 
 | ||||
|         if email and password: | ||||
|             user = authenticate(email=email, password=password) | ||||
|         else: | ||||
|             msg = _('Must include "email" and "password".') | ||||
|             raise exceptions.ValidationError(msg) | ||||
| 
 | ||||
|         return user | ||||
| 
 | ||||
|     def _validate_username(self, username, password): | ||||
|         user = None | ||||
| 
 | ||||
|         if username and password: | ||||
|             user = authenticate(username=username, password=password) | ||||
|         else: | ||||
|             msg = _('Must include "username" and "password".') | ||||
|             raise exceptions.ValidationError(msg) | ||||
| 
 | ||||
|         return user | ||||
| 
 | ||||
|     def _validate_username_email(self, username, email, password): | ||||
|         user = None | ||||
| 
 | ||||
|         if email and password: | ||||
|             user = authenticate(email=email, password=password) | ||||
|         elif username and password: | ||||
|             user = authenticate(username=username, password=password) | ||||
|         else: | ||||
|             msg = _('Must include either "username" or "email" and "password".') | ||||
|             raise exceptions.ValidationError(msg) | ||||
| 
 | ||||
|         return user | ||||
| 
 | ||||
|     def validate(self, attrs): | ||||
|         username = attrs.get('username') | ||||
|         email = attrs.get('email') | ||||
|         password = attrs.get('password') | ||||
| 
 | ||||
|         user = None | ||||
| 
 | ||||
|         if 'allauth' in settings.INSTALLED_APPS: | ||||
|             from allauth.account import app_settings | ||||
| 
 | ||||
|             # Authentication through email | ||||
|             if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL: | ||||
|                 if email and password: | ||||
|                     user = authenticate(email=email, password=password) | ||||
|                 else: | ||||
|                     msg = _('Must include "email" and "password".') | ||||
|                     raise exceptions.ValidationError(msg) | ||||
|                 user = self._validate_email(email, password) | ||||
| 
 | ||||
|             # Authentication through username | ||||
|             elif app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME: | ||||
|                 if username and password: | ||||
|                     user = authenticate(username=username, password=password) | ||||
|                 else: | ||||
|                     msg = _('Must include "username" and "password".') | ||||
|                     raise exceptions.ValidationError(msg) | ||||
|             if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME: | ||||
|                 user = self._validate_username(username, password) | ||||
| 
 | ||||
|             # Authentication through either username or email | ||||
|             else: | ||||
|                 if email and password: | ||||
|                     user = authenticate(email=email, password=password) | ||||
|                 elif username and password: | ||||
|                     user = authenticate(username=username, password=password) | ||||
|                 else: | ||||
|                     msg = _('Must include either "username" or "email" and "password".') | ||||
|                     raise exceptions.ValidationError(msg) | ||||
| 
 | ||||
|         elif username and password: | ||||
|             user = authenticate(username=username, password=password) | ||||
|                 user = self._validate_username_email(username, email, password) | ||||
| 
 | ||||
|         else: | ||||
|             msg = _('Must include "username" and "password".') | ||||
|             raise exceptions.ValidationError(msg) | ||||
|             # Authentication without using allauth | ||||
|             if email: | ||||
|                 try: | ||||
|                     username = UserModel.objects.get(email__iexact=email).username | ||||
|                 except UserModel.DoesNotExist: | ||||
|                     pass | ||||
| 
 | ||||
|             if username: | ||||
|                 user = self._validate_username_email(username, '', password) | ||||
| 
 | ||||
|         # Did we get back an active user? | ||||
|         if user: | ||||
|  | @ -84,7 +115,7 @@ class TokenSerializer(serializers.ModelSerializer): | |||
|     """ | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = Token | ||||
|         model = TokenModel | ||||
|         fields = ('key',) | ||||
| 
 | ||||
| class UserDetailsSerializer(serializers.ModelSerializer): | ||||
|  | @ -114,15 +145,17 @@ class PasswordResetSerializer(serializers.Serializer): | |||
| 
 | ||||
|     password_reset_form_class = PasswordResetForm | ||||
| 
 | ||||
|     def get_email_options(self): | ||||
|         """ Override this method to change default e-mail options | ||||
|         """ | ||||
|         return {} | ||||
| 
 | ||||
|     def validate_email(self, value): | ||||
|         # 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')) | ||||
| 
 | ||||
|         if not UserModel.objects.filter(email__iexact=value).exists(): | ||||
|             raise serializers.ValidationError(_('Invalid e-mail address')) | ||||
| 
 | ||||
|         return value | ||||
| 
 | ||||
|     def save(self): | ||||
|  | @ -133,6 +166,8 @@ class PasswordResetSerializer(serializers.Serializer): | |||
|             'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'), | ||||
|             'request': request, | ||||
|         } | ||||
| 
 | ||||
|         opts.update(self.get_email_options()) | ||||
|         self.reset_form.save(**opts) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ from django.core.urlresolvers import reverse | |||
| from django.test import TestCase | ||||
| 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 | ||||
| 
 | ||||
|  | @ -90,18 +91,50 @@ 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): | ||||
|     def test_login_by_email(self): | ||||
|         # starting test without allauth app | ||||
|         settings.INSTALLED_APPS.remove('allauth') | ||||
| 
 | ||||
|         payload = { | ||||
|             "username": self.USERNAME, | ||||
|             "email": self.EMAIL.lower(), | ||||
|             "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) | ||||
|         # 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.USERNAME, self.EMAIL, self.PASS) | ||||
| 
 | ||||
|         # test auth by email | ||||
|         self.post(self.login_url, data=payload, status_code=200) | ||||
|         self.assertEqual('key' in self.response.json.keys(), True) | ||||
|         self.token = self.response.json['key'] | ||||
| 
 | ||||
|         # test auth by email in different case | ||||
|         payload = { | ||||
|             "email": self.EMAIL.upper(), | ||||
|             "password": self.PASS | ||||
|         } | ||||
|         self.post(self.login_url, data=payload, status_code=200) | ||||
|         self.assertEqual('key' in self.response.json.keys(), True) | ||||
|         self.token = self.response.json['key'] | ||||
| 
 | ||||
|         # test inactive user | ||||
|         user.is_active = False | ||||
|         user.save() | ||||
|         self.post(self.login_url, data=payload, status_code=400) | ||||
| 
 | ||||
|         # test wrong email/password | ||||
|         payload = { | ||||
|             "email": 't' + self.EMAIL, | ||||
|             "password": self.PASS | ||||
|         } | ||||
|         self.post(self.login_url, data=payload, status_code=400) | ||||
| 
 | ||||
|         # test empty payload | ||||
|         self.post(self.login_url, data={}, status_code=400) | ||||
| 
 | ||||
|     def test_password_change(self): | ||||
|         login_payload = { | ||||
|  | @ -238,7 +271,7 @@ class APITestCase1(TestCase, BaseAPITestCase): | |||
|         self.post(self.login_url, data=payload, status_code=200) | ||||
| 
 | ||||
|     def test_password_reset_with_email_in_different_case(self): | ||||
|         user = get_user_model().objects.create_user(self.USERNAME, self.EMAIL.lower(), self.PASS) | ||||
|         get_user_model().objects.create_user(self.USERNAME, self.EMAIL.lower(), self.PASS) | ||||
| 
 | ||||
|         # call password reset in upper case | ||||
|         mail_count = len(mail.outbox) | ||||
|  | @ -247,12 +280,15 @@ class APITestCase1(TestCase, BaseAPITestCase): | |||
|         self.assertEqual(len(mail.outbox), mail_count + 1) | ||||
| 
 | ||||
|     def test_password_reset_with_invalid_email(self): | ||||
|         """ | ||||
|         Invalid email should not raise error, as this would leak users | ||||
|         """ | ||||
|         get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS) | ||||
| 
 | ||||
|         # call password reset | ||||
|         mail_count = len(mail.outbox) | ||||
|         payload = {'email': 'nonexisting@email.com'} | ||||
|         self.post(self.password_reset_url, data=payload, status_code=400) | ||||
|         self.post(self.password_reset_url, data=payload, status_code=200) | ||||
|         self.assertEqual(len(mail.outbox), mail_count) | ||||
| 
 | ||||
|     def test_user_details(self): | ||||
|  | @ -271,37 +307,28 @@ 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() | ||||
| 
 | ||||
|         # test empty payload | ||||
|         self.post(self.register_url, data={}, status_code=400) | ||||
| 
 | ||||
|         self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201) | ||||
|         result = self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201) | ||||
|         self.assertIn('key', result.data) | ||||
|         self.assertEqual(get_user_model().objects.all().count(), user_count + 1) | ||||
| 
 | ||||
|         new_user = get_user_model().objects.latest('id') | ||||
|         self.assertEqual(new_user.username, self.REGISTRATION_DATA['username']) | ||||
| 
 | ||||
|         self._login() | ||||
|         self._logout() | ||||
| 
 | ||||
|     def test_registration_with_invalid_password(self): | ||||
|         data = self.REGISTRATION_DATA.copy() | ||||
|         data['password2'] = 'foobar' | ||||
| 
 | ||||
|         self.post(self.register_url, data=data, status_code=400) | ||||
| 
 | ||||
|     @override_settings( | ||||
|         ACCOUNT_EMAIL_VERIFICATION='mandatory', | ||||
|         ACCOUNT_EMAIL_REQUIRED=True | ||||
|  | @ -317,11 +344,12 @@ class APITestCase1(TestCase, BaseAPITestCase): | |||
|             status_code=status.HTTP_400_BAD_REQUEST | ||||
|         ) | ||||
| 
 | ||||
|         self.post( | ||||
|         result = self.post( | ||||
|             self.register_url, | ||||
|             data=self.REGISTRATION_DATA_WITH_EMAIL, | ||||
|             status_code=status.HTTP_201_CREATED | ||||
|         ) | ||||
|         self.assertNotIn('key', result.data) | ||||
|         self.assertEqual(get_user_model().objects.all().count(), user_count + 1) | ||||
|         self.assertEqual(len(mail.outbox), mail_count + 1) | ||||
|         new_user = get_user_model().objects.latest('id') | ||||
|  |  | |||
|  | @ -99,7 +99,6 @@ class TestSocialAuth(TestCase, BaseAPITestCase): | |||
| 
 | ||||
|         # test empty payload | ||||
|         self.post(self.register_url, data={}, status_code=400) | ||||
| 
 | ||||
|         self.post( | ||||
|             self.register_url, | ||||
|             data=self.REGISTRATION_DATA, | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| from django.conf.urls import patterns, url, include | ||||
| from django.conf.urls import url, include | ||||
| from django.views.generic import TemplateView | ||||
| from . import django_urls | ||||
| 
 | ||||
|  | @ -11,8 +11,7 @@ from rest_auth.registration.views import SocialLoginView | |||
| class FacebookLogin(SocialLoginView): | ||||
|     adapter_class = FacebookOAuth2Adapter | ||||
| 
 | ||||
| urlpatterns += patterns( | ||||
|     '', | ||||
| urlpatterns += [ | ||||
|     url(r'^rest-registration/', include('rest_auth.registration.urls')), | ||||
|     url(r'^test-admin/', include(django_urls)), | ||||
|     url(r'^account-email-verification-sent/$', TemplateView.as_view(), | ||||
|  | @ -21,4 +20,4 @@ urlpatterns += patterns( | |||
|         name='account_confirm_email'), | ||||
|     url(r'^social-login/facebook/$', FacebookLogin.as_view(), name='fb_login'), | ||||
|     url(r'^accounts/', include('allauth.socialaccount.urls')) | ||||
| ) | ||||
| ] | ||||
|  |  | |||
|  | @ -1,12 +1,11 @@ | |||
| from django.conf.urls import patterns, url | ||||
| from django.conf.urls import url | ||||
| 
 | ||||
| from rest_auth.views import ( | ||||
|     LoginView, LogoutView, UserDetailsView, PasswordChangeView, | ||||
|     PasswordResetView, PasswordResetConfirmView | ||||
| ) | ||||
| 
 | ||||
| urlpatterns = patterns( | ||||
|     '', | ||||
| urlpatterns = [ | ||||
|     # URLs that do not require a session or valid token | ||||
|     url(r'^password/reset/$', PasswordResetView.as_view(), | ||||
|         name='rest_password_reset'), | ||||
|  | @ -18,4 +17,4 @@ urlpatterns = patterns( | |||
|     url(r'^user/$', UserDetailsView.as_view(), name='rest_user_details'), | ||||
|     url(r'^password/change/$', PasswordChangeView.as_view(), | ||||
|         name='rest_password_change'), | ||||
| ) | ||||
| ] | ||||
|  |  | |||
|  | @ -10,6 +10,11 @@ def import_callable(path_or_callable): | |||
|         package, attr = path_or_callable.rsplit('.', 1) | ||||
|         return getattr(import_module(package), attr) | ||||
| 
 | ||||
| 
 | ||||
| def default_create_token(token_model, user, serializer): | ||||
|     token, _ = token_model.objects.get_or_create(user=user) | ||||
|     return token | ||||
| 
 | ||||
| def jwt_encode(user): | ||||
|     try:  | ||||
|         from rest_framework_jwt.settings import api_settings | ||||
|  |  | |||
|  | @ -7,14 +7,14 @@ from rest_framework.views import APIView | |||
| from rest_framework.response import Response | ||||
| from rest_framework.generics import GenericAPIView | ||||
| from rest_framework.permissions import IsAuthenticated, AllowAny | ||||
| from rest_framework.authtoken.models import Token | ||||
| from rest_framework.generics import RetrieveUpdateAPIView | ||||
| 
 | ||||
| from .app_settings import ( | ||||
|     TokenSerializer, UserDetailsSerializer, LoginSerializer, | ||||
|     PasswordResetSerializer, PasswordResetConfirmSerializer, | ||||
|     PasswordChangeSerializer, JWTSerializer | ||||
|     PasswordChangeSerializer, create_token | ||||
| ) | ||||
| from .models import TokenModel | ||||
| 
 | ||||
| from .utils import jwt_encode | ||||
| 
 | ||||
|  | @ -32,7 +32,7 @@ class LoginView(GenericAPIView): | |||
|     """ | ||||
|     permission_classes = (AllowAny,) | ||||
|     serializer_class = LoginSerializer | ||||
|     token_model = Token | ||||
|     token_model = TokenModel | ||||
| 
 | ||||
|     def get_response_serializer(self): | ||||
|         if getattr(settings, 'REST_USE_JWT', False): | ||||
|  | @ -43,6 +43,9 @@ 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) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user