gault-millau/apps/account/views/web.py
2019-09-02 15:44:37 +03:00

244 lines
8.9 KiB
Python

"""Web account views"""
from django.conf import settings
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.encoding import force_text
from django.utils.http import urlsafe_base64_decode
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import never_cache
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.edit import FormView
from rest_framework import permissions
from rest_framework import status
from rest_framework import views
from rest_framework.response import Response
from account import models
from account.forms import SetPasswordForm
from account.serializers import web as serializers
from utils import exceptions as utils_exceptions
from utils.models import GMTokenGenerator
from utils.views import (JWTCreateAPIView,
JWTUpdateAPIView,
JWTGenericViewMixin)
class PasswordResetView(JWTCreateAPIView):
"""View for resetting user password"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.PasswordResetSerializer
queryset = models.ResetPasswordToken.objects.valid()
class PasswordResetConfirmView(JWTGenericViewMixin):
"""View for confirmation new password"""
serializer_class = serializers.PasswordResetConfirmSerializer
permission_classes = (permissions.AllowAny,)
def get_queryset(self):
"""Override get_queryset method"""
return models.ResetPasswordToken.objects.valid()
def get_object(self):
"""Override get_object method
"""
queryset = self.filter_queryset(self.get_queryset())
uidb64 = self.kwargs.get('uidb64')
user_id = force_text(urlsafe_base64_decode(uidb64))
token = self.kwargs.get('token')
filter_kwargs = {'key': token, 'user_id': user_id}
obj = get_object_or_404(queryset, **filter_kwargs)
if not GMTokenGenerator(GMTokenGenerator.RESET_PASSWORD).check_token(
user=obj.user, token=token):
raise utils_exceptions.NotValidAccessTokenError()
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
def put(self, request, *args, **kwargs):
"""Implement PUT method"""
instance = self.get_object()
serializer = self.get_serializer(instance=instance,
data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(status=status.HTTP_200_OK)
class ChangePasswordView(JWTUpdateAPIView):
"""Change password view"""
serializer_class = serializers.ChangePasswordSerializer
queryset = models.User.objects.active()
def patch(self, request, *args, **kwargs):
"""Implement PUT method"""
serializer = self.get_serializer(instance=self.request.user,
data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(status=status.HTTP_200_OK)
class ChangeEmailView(JWTGenericViewMixin):
"""Change user email view"""
serializer_class = serializers.ChangeEmailSerializer
queryset = models.User.objects.all()
def patch(self, request, *args, **kwargs):
"""Implement POST-method"""
# Get user instance
instance = self.request.user
serializer = self.get_serializer(data=request.data, instance=instance)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(status=status.HTTP_200_OK)
class ChangeEmailConfirmView(JWTGenericViewMixin):
"""View for confirm changing email"""
permission_classes = (permissions.AllowAny,)
def get(self, request, *args, **kwargs):
"""Implement GET-method"""
uidb64 = kwargs.get('uidb64')
token = kwargs.get('token')
uid = force_text(urlsafe_base64_decode(uidb64))
user_qs = models.User.objects.filter(pk=uid)
if user_qs.exists():
user = user_qs.first()
if not GMTokenGenerator(GMTokenGenerator.CHANGE_EMAIL).check_token(
user, token):
raise utils_exceptions.NotValidTokenError()
# Approve email status
user.confirm_email()
return Response(status=status.HTTP_200_OK)
else:
raise utils_exceptions.UserNotFoundError()
class RefreshTokenView(JWTGenericViewMixin):
"""Refresh access_token"""
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.RefreshTokenSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
response = Response(serializer.data, status=status.HTTP_201_CREATED)
access_token = serializer.data.get('access_token')
refresh_token = serializer.data.get('refresh_token')
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token,
refresh_token=refresh_token),
response=response)
# Form view
class PasswordContextMixin:
extra_context = None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'title': self.title,
**(self.extra_context or {})
})
return context
class FormPasswordResetSuccessView(views.APIView):
"""View for successful reset password"""
permission_classes = (permissions.AllowAny, )
def get(self, request, *args, **kwargs):
"""Implement GET-method"""
return Response(status=status.HTTP_200_OK)
class FormPasswordResetConfirmView(PasswordContextMixin, FormView):
INTERNAL_RESET_URL_TOKEN = 'set-password'
INTERNAL_RESET_SESSION_TOKEN = '_password_reset_token'
form_class = SetPasswordForm
post_reset_login = False
post_reset_login_backend = None
success_url = reverse_lazy('web:account:form-password-reset-success')
template_name = settings.CONFIRMATION_PASSWORD_RESET_TEMPLATE
title = _('Enter new password')
token_generator = default_token_generator
@method_decorator(sensitive_post_parameters())
@method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
assert 'uidb64' in kwargs and 'token' in kwargs
self.validlink = False
self.user = self.get_user(kwargs['uidb64'])
if self.user is not None:
token = kwargs['token']
if token == self.INTERNAL_RESET_URL_TOKEN:
session_token = self.request.session.get(self.INTERNAL_RESET_SESSION_TOKEN)
if self.token_generator.check_token(self.user, session_token):
# If the token is valid, display the password reset form.
self.validlink = True
return super().dispatch(*args, **kwargs)
else:
if self.token_generator.check_token(self.user, token):
# Store the token in the session and redirect to the
# password reset form at a URL without the token. That
# avoids the possibility of leaking the token in the
# HTTP Referer header.
self.request.session[self.INTERNAL_RESET_SESSION_TOKEN] = token
redirect_url = self.request.path.replace(token, self.INTERNAL_RESET_URL_TOKEN)
return HttpResponseRedirect(redirect_url)
# Display the "Password reset unsuccessful" page.
return self.render_to_response(self.get_context_data())
def get_user(self, uidb64):
try:
# urlsafe_base64_decode() decodes to bytestring
uid = urlsafe_base64_decode(uidb64).decode()
user = models.User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, models.User.DoesNotExist, ValidationError):
user = None
return user
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.user
return kwargs
def form_valid(self, form):
# Saving form
form.save()
# Pop session token
del self.request.session[self.INTERNAL_RESET_SESSION_TOKEN]
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.validlink:
context['validlink'] = True
else:
context.update({
'form': None,
'title': _('Password reset unsuccessful'),
'validlink': False,
})
return context