From 3b3ca62879d17b6fdc016e4e5df6cbc6aeae51f5 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 26 Jul 2022 08:57:52 +0800 Subject: [PATCH] Raise exceptions in views instead of returning '400_BAD_REQUEST' Allows django-rest to manage its own exceptions. Fixes #56 --- authemail/views.py | 357 +++++++++++++++++++++------------------------ 1 file changed, 169 insertions(+), 188 deletions(-) diff --git a/authemail/views.py b/authemail/views.py index 5581152..f0fbf5e 100644 --- a/authemail/views.py +++ b/authemail/views.py @@ -4,20 +4,20 @@ from django.contrib.auth import authenticate, get_user_model from django.utils.translation import gettext as _ -from rest_framework import status +from rest_framework import status, exceptions from rest_framework.authtoken.models import Token from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView -from authemail.models import SignupCode, EmailChangeCode, PasswordResetCode -from authemail.models import send_multi_format_email -from authemail.serializers import SignupSerializer, LoginSerializer -from authemail.serializers import PasswordResetSerializer -from authemail.serializers import PasswordResetVerifiedSerializer -from authemail.serializers import EmailChangeSerializer -from authemail.serializers import PasswordChangeSerializer -from authemail.serializers import UserSerializer +from .models import SignupCode, EmailChangeCode, PasswordResetCode +from .models import send_multi_format_email +from .serializers import SignupSerializer, LoginSerializer +from .serializers import PasswordResetSerializer +from .serializers import PasswordResetVerifiedSerializer +from .serializers import EmailChangeSerializer +from .serializers import PasswordChangeSerializer +from .serializers import UserSerializer class Signup(APIView): @@ -25,54 +25,53 @@ class Signup(APIView): serializer_class = SignupSerializer def post(self, request, format=None): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - email = serializer.data['email'] - password = serializer.data['password'] - first_name = serializer.data['first_name'] - last_name = serializer.data['last_name'] + if not serializer.is_valid(): + raise exceptions.ValidationError(serializer.errors) - must_validate_email = getattr(settings, "AUTH_EMAIL_VERIFICATION", True) + email = serializer.data['email'] + password = serializer.data['password'] + first_name = serializer.data['first_name'] + last_name = serializer.data['last_name'] + + must_validate_email = getattr(settings, "AUTH_EMAIL_VERIFICATION", True) + + try: + user = get_user_model().objects.get(email=email) + if user.is_verified: + raise exceptions.ValidationError(_('Email address already taken.')) try: - user = get_user_model().objects.get(email=email) - if user.is_verified: - content = {'detail': _('Email address already taken.')} - return Response(content, status=status.HTTP_400_BAD_REQUEST) - - try: - # Delete old signup codes - signup_code = SignupCode.objects.get(user=user) - signup_code.delete() - except SignupCode.DoesNotExist: - pass - - except get_user_model().DoesNotExist: - user = get_user_model().objects.create_user(email=email) - - # Set user fields provided - user.set_password(password) - user.first_name = first_name - user.last_name = last_name - if not must_validate_email: - user.is_verified = True - send_multi_format_email('welcome_email', - {'email': user.email, }, - target_email=user.email) - user.save() - - if must_validate_email: - # Create and associate signup code - ipaddr = self.request.META.get('REMOTE_ADDR', '0.0.0.0') - signup_code = SignupCode.objects.create_signup_code(user, ipaddr) - signup_code.send_signup_email() - - content = {'email': email, 'first_name': first_name, - 'last_name': last_name} - return Response(content, status=status.HTTP_201_CREATED) - - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + # Delete old signup codes + signup_code = SignupCode.objects.get(user=user) + signup_code.delete() + except SignupCode.DoesNotExist: + pass + + except get_user_model().DoesNotExist: + user = get_user_model().objects.create_user(email=email) + + # Set user fields provided + user.set_password(password) + user.first_name = first_name + user.last_name = last_name + if not must_validate_email: + user.is_verified = True + send_multi_format_email('welcome_email', + {'email': user.email, }, + target_email=user.email) + user.save() + + if must_validate_email: + # Create and associate signup code + ipaddr = self.request.META.get('REMOTE_ADDR', '0.0.0.0') + signup_code = SignupCode.objects.create_signup_code(user, ipaddr) + signup_code.send_signup_email() + + content = {'email': email, 'first_name': first_name, + 'last_name': last_name} + return Response(content, status=status.HTTP_201_CREATED) class SignupVerify(APIView): @@ -91,8 +90,7 @@ def get(self, request, format=None): content = {'success': _('Email address verified.')} return Response(content, status=status.HTTP_200_OK) else: - content = {'detail': _('Unable to verify user.')} - return Response(content, status=status.HTTP_400_BAD_REQUEST) + raise exceptions.ValidationError(_('Unable to verify user.')) class Login(APIView): @@ -100,35 +98,28 @@ class Login(APIView): serializer_class = LoginSerializer def post(self, request, format=None): - serializer = self.serializer_class(data=request.data, context={'request': request}) - - if serializer.is_valid(): - email = serializer.data['email'] - password = serializer.data['password'] - user = authenticate(email=email, password=password) - - if user: - if user.is_verified: - if user.is_active: - token, created = Token.objects.get_or_create(user=user) - return Response({'token': token.key}, - status=status.HTTP_200_OK) - else: - content = {'detail': _('User account not active.')} - return Response(content, - status=status.HTTP_401_UNAUTHORIZED) - else: - content = {'detail': - _('User account not verified.')} - return Response(content, status=status.HTTP_401_UNAUTHORIZED) - else: - content = {'detail': - _('Unable to login with provided credentials.')} - return Response(content, status=status.HTTP_401_UNAUTHORIZED) + serializer = self.get_serializer(data=request.data) - else: - return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) + if not serializer.is_valid(): + raise exceptions.ValidationError(serializer.errors) + + email = serializer.data['email'] + password = serializer.data['password'] + user = authenticate(email=email, password=password) + + if not user: + raise exceptions.AuthenticationFailed(_('Unable to login with provided credentials.')) + + if not user.is_verified: + raise exceptions.AuthenticationFailed(_('User account not verified.')) + + if not user.is_active: + raise exceptions.ValidationError(_('User account not active.')) + + token, created = Token.objects.get_or_create(user=user) + return Response({'token': token.key}, + status=status.HTTP_200_OK) + class Logout(APIView): @@ -138,9 +129,7 @@ def get(self, request, format=None): """ Remove all auth tokens owned by request.user. """ - tokens = Token.objects.filter(user=request.user) - for token in tokens: - token.delete() + tokens = Token.objects.filter(user=request.user).delete() content = {'success': _('User logged out.')} return Response(content, status=status.HTTP_200_OK) @@ -150,34 +139,31 @@ class PasswordReset(APIView): serializer_class = PasswordResetSerializer def post(self, request, format=None): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - email = serializer.data['email'] + if not serializer.is_valid(): + raise exceptions.ValidationError(serializer.errors) + email = serializer.data['email'] - try: - user = get_user_model().objects.get(email=email) + try: + user = get_user_model().objects.get(email=email) - # Delete all unused password reset codes - PasswordResetCode.objects.filter(user=user).delete() + # Delete all unused password reset codes + PasswordResetCode.objects.filter(user=user).delete() - if user.is_verified and user.is_active: - password_reset_code = \ - PasswordResetCode.objects.create_password_reset_code(user) - password_reset_code.send_password_reset_email() - content = {'email': email} - return Response(content, status=status.HTTP_201_CREATED) + if user.is_verified and user.is_active: + password_reset_code = \ + PasswordResetCode.objects.create_password_reset_code(user) + password_reset_code.send_password_reset_email() + content = {'email': email} + return Response(content, status=status.HTTP_201_CREATED) - except get_user_model().DoesNotExist: - pass + except get_user_model().DoesNotExist: + pass - # Since this is AllowAny, don't give away error. - content = {'detail': _('Password reset not allowed.')} - return Response(content, status=status.HTTP_400_BAD_REQUEST) + # Since this is AllowAny, don't give away error. + raise exceptions.ValidationError(_('Password reset not allowed.')) - else: - return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) class PasswordResetVerify(APIView): @@ -198,8 +184,9 @@ def get(self, request, format=None): content = {'success': _('Email address verified.')} return Response(content, status=status.HTTP_200_OK) except PasswordResetCode.DoesNotExist: - content = {'detail': _('Unable to verify user.')} - return Response(content, status=status.HTTP_400_BAD_REQUEST) + pass + + raise exceptions.ValidationError(_('Unale to verify user.')) class PasswordResetVerified(APIView): @@ -207,29 +194,28 @@ class PasswordResetVerified(APIView): serializer_class = PasswordResetVerifiedSerializer def post(self, request, format=None): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - code = serializer.data['code'] - password = serializer.data['password'] + if not serializer.is_valid(): + raise exceptions.ValidationError(serializer.errors) + + code = serializer.data['code'] + password = serializer.data['password'] - try: - password_reset_code = PasswordResetCode.objects.get(code=code) - password_reset_code.user.set_password(password) - password_reset_code.user.save() + try: + password_reset_code = PasswordResetCode.objects.get(code=code) + password_reset_code.user.set_password(password) + password_reset_code.user.save() - # Delete password reset code just used - password_reset_code.delete() + # Delete password reset code just used + password_reset_code.delete() - content = {'success': _('Password reset.')} - return Response(content, status=status.HTTP_200_OK) - except PasswordResetCode.DoesNotExist: - content = {'detail': _('Unable to verify user.')} - return Response(content, status=status.HTTP_400_BAD_REQUEST) + content = {'success': _('Password reset.')} + return Response(content, status=status.HTTP_200_OK) + except PasswordResetCode.DoesNotExist: + pass - else: - return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) + raise exceptions.ValidationError(_('Unable to verify user.')) class EmailChange(APIView): @@ -237,37 +223,35 @@ class EmailChange(APIView): serializer_class = EmailChangeSerializer def post(self, request, format=None): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - user = request.user + if not serializer.is_valid(): + raise exceptions.ValidationError(serializer.errors) - # Delete all unused email change codes - EmailChangeCode.objects.filter(user=user).delete() + user = request.user - email_new = serializer.data['email'] + # Delete all unused email change codes + EmailChangeCode.objects.filter(user=user).delete() - try: - user_with_email = get_user_model().objects.get(email=email_new) - if user_with_email.is_verified: - content = {'detail': _('Email address already taken.')} - return Response(content, status=status.HTTP_400_BAD_REQUEST) - else: - # If the account with this email address is not verified, - # give this user a chance to verify and grab this email address - raise get_user_model().DoesNotExist + email_new = serializer.data['email'] - except get_user_model().DoesNotExist: - email_change_code = EmailChangeCode.objects.create_email_change_code(user, email_new) + try: + user_with_email = get_user_model().objects.get(email=email_new) + if user_with_email.is_verified: + raise exceptions.ValidationError(_('Email address already in use.')) + else: + # If the account with this email address is not verified, + # give this user a chance to verify and grab this email address + raise get_user_model().DoesNotExist - email_change_code.send_email_change_emails() + except get_user_model().DoesNotExist: + pass - content = {'email': email_new} - return Response(content, status=status.HTTP_201_CREATED) + email_change_code = EmailChangeCode.objects.create_email_change_code(user, email_new) + email_change_code.send_email_change_emails() - else: - return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) + content = {'email': email_new} + return Response(content, status=status.HTTP_201_CREATED) class EmailChangeVerify(APIView): @@ -286,35 +270,34 @@ def get(self, request, format=None): email_change_code.delete() raise EmailChangeCode.DoesNotExist() - # Check if the email address is being used by a verified user. - try: - user_with_email = get_user_model().objects.get(email=email_change_code.email) - if user_with_email.is_verified: - # Delete email change code since won't be used - email_change_code.delete() - - content = {'detail': _('Email address already taken.')} - return Response(content, status=status.HTTP_400_BAD_REQUEST) - else: - # If the account with this email address is not verified, - # delete the account (and signup code) because the email - # address will be used for the user who just verified. - user_with_email.delete() - except get_user_model().DoesNotExist: - pass + except EmailChangeCode.DoesNotExist: + # email change code doesn't exist or has expired + raise exceptions.ValidationError(_('Unable to verify user.')) - # If all is well, change the email address. - email_change_code.user.email = email_change_code.email - email_change_code.user.save() + # Check if the email address is already being used by a verified user. + try: + user_with_email = get_user_model().objects.get(email=email_change_code.email) + if user_with_email.is_verified: + # Delete email change code since won't be used + email_change_code.delete() + raise exceptions.ValidationError(_('Email address already taken.')) - # Delete email change code just used - email_change_code.delete() + # If the account with this email address is not verified, + # delete the account (and signup code) because the email + # address will be used for the user who just verified. + user_with_email.delete() + except get_user_model().DoesNotExist: + pass - content = {'success': _('Email address changed.')} - return Response(content, status=status.HTTP_200_OK) - except EmailChangeCode.DoesNotExist: - content = {'detail': _('Unable to verify user.')} - return Response(content, status=status.HTTP_400_BAD_REQUEST) + # If all is well, change the email address. + email_change_code.user.email = email_change_code.email + email_change_code.user.save() + + # Delete email change code just used + email_change_code.delete() + + content = {'success': _('Email address changed.')} + return Response(content, status=status.HTTP_200_OK) class PasswordChange(APIView): @@ -322,21 +305,19 @@ class PasswordChange(APIView): serializer_class = PasswordChangeSerializer def post(self, request, format=None): - serializer = self.serializer_class(data=request.data, context={'request': request}) + serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - user = request.user + if not serializer.is_valid(): + raise exceptions.ValidationError(serializer.errors) - password = serializer.data['password'] - user.set_password(password) - user.save() + user = request.user - content = {'success': _('Password changed.')} - return Response(content, status=status.HTTP_200_OK) + password = serializer.data['password'] + user.set_password(password) + user.save() - else: - return Response(serializer.errors, - status=status.HTTP_400_BAD_REQUEST) + content = {'success': _('Password changed.')} + return Response(content, status=status.HTTP_200_OK) class UserMe(APIView): @@ -344,4 +325,4 @@ class UserMe(APIView): serializer_class = UserSerializer def get(self, request, format=None): - return Response(self.serializer_class(request.user, context={'request': request}).data) + return Response(self.get_serializer(request.user).data)