version 0.0.12: updated JWT views

This commit is contained in:
Anatoly 2019-08-15 14:05:17 +03:00
parent d5a14ef8c2
commit 663c003119
5 changed files with 143 additions and 30 deletions

View File

@ -19,12 +19,16 @@ from account.models import User
from authorization.models import Application from authorization.models import Application
from authorization.serializers import common as serializers from authorization.serializers import common as serializers
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils.views import JWTViewMixin from utils.views import (JWTGenericViewMixin,
JWTCreateAPIView,
JWTDestroyAPIView,
JWTUpdateAPIView,
JWTRetrieveAPIView)
# Mixins # Mixins
# JWTAuthView mixin # JWTAuthView mixin
class JWTAuthViewMixin(JWTViewMixin): class JWTAuthViewMixin(JWTCreateAPIView):
"""Mixin for authentication views""" """Mixin for authentication views"""
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -98,7 +102,7 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
# Sign in via Facebook # Sign in via Facebook
class OAuth2SignUpView(OAuth2ViewMixin, JWTAuthViewMixin): class OAuth2SignUpView(OAuth2ViewMixin, JWTCreateAPIView):
""" """
Implements an endpoint to convert a provider token to an access token Implements an endpoint to convert a provider token to an access token
@ -176,7 +180,7 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTAuthViewMixin):
# JWT # JWT
# Sign in via username and password # Sign in via username and password
class SignUpView(JWTAuthViewMixin): class SignUpView(JWTCreateAPIView):
"""View for classic signup""" """View for classic signup"""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )
serializer_class = serializers.SignupSerializer serializer_class = serializers.SignupSerializer
@ -207,18 +211,37 @@ class SignUpView(JWTAuthViewMixin):
# Login by username|email + password # Login by username|email + password
class LoginByUsernameOrEmailView(JWTAuthViewMixin): class LoginByUsernameOrEmailView(JWTCreateAPIView):
"""Login by email and password""" """Login by email and password"""
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.LoginByUsernameOrEmailSerializer serializer_class = serializers.LoginByUsernameOrEmailSerializer
# Refresh access_token # Refresh access_token
class RefreshTokenView(JWTAuthViewMixin): class RefreshTokenView(JWTGenericViewMixin):
"""Refresh access_token""" """Refresh access_token"""
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.RefreshTokenSerializer serializer_class = serializers.RefreshTokenSerializer
def post(self, request, *args, **kwargs):
_locale = request.COOKIES.get('locale')
try:
locale = self._check_locale(locale=_locale)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
response = Response(serializer.data, status=status.HTTP_201_CREATED)
access_token, refresh_token = self._get_tokens_from_cookies(request)
except utils_exceptions.LocaleNotExisted:
raise utils_exceptions.LocaleNotExisted(locale=_locale)
else:
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(locale=locale,
access_token=access_token,
refresh_token=refresh_token),
response=response)
# Logout # Logout
class LogoutView(generics.CreateAPIView): class LogoutView(generics.CreateAPIView):

View File

@ -1,10 +1,10 @@
from rest_framework import generics, permissions from rest_framework import generics, permissions
from news.models import News from news.models import News
from news.serializers import common as serializers from news.serializers import common as serializers
from utils.views import JWTViewMixin from utils.views import JWTGenericViewMixin
class NewsList(JWTViewMixin, generics.ListAPIView): class NewsList(generics.ListAPIView):
"""News list view.""" """News list view."""
queryset = News.objects.all() queryset = News.objects.all()
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )

View File

@ -2,7 +2,7 @@ from rest_framework import generics
from translation import models from translation import models
from translation import serializers from translation import serializers
from rest_framework import permissions from rest_framework import permissions
from utils.views import JWTViewMixin from utils.views import JWTGenericViewMixin
# Mixins # Mixins
@ -13,7 +13,7 @@ class LanguageViewMixin(generics.GenericAPIView):
# Views # Views
class LanguageListView(LanguageViewMixin, JWTViewMixin, generics.ListAPIView): class LanguageListView(LanguageViewMixin, generics.ListAPIView):
"""List view for model Language""" """List view for model Language"""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )
serializer_class = serializers.LanguageSerializer serializer_class = serializers.LanguageSerializer

View File

@ -3,19 +3,20 @@ from collections import namedtuple
from translation import models as translation_models from translation import models as translation_models
from utils import exceptions from utils import exceptions
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status
# JWT # JWT
# Login base view mixin # Login base view mixin
class JWTViewMixin(generics.GenericAPIView): class JWTGenericViewMixin(generics.GenericAPIView):
"""JWT view mixin""" """JWT view mixin"""
ACCESS_TOKEN_HTTP = True ACCESS_TOKEN_HTTP_ONLY = True
ACCESS_TOKEN_SECURE = False ACCESS_TOKEN_SECURE = False
REFRESH_TOKEN_HTTP = True REFRESH_TOKEN_HTTP_ONLY = True
REFRESH_TOKEN_SECURE = False REFRESH_TOKEN_SECURE = False
COOKIE = namedtuple('COOKIE', ['key', 'value', 'http', 'secure']) COOKIE = namedtuple('COOKIE', ['key', 'value', 'http_only', 'secure'])
def _check_locale(self, locale: str): def _check_locale(self, locale: str):
@ -24,10 +25,7 @@ class JWTViewMixin(generics.GenericAPIView):
raise exceptions.LocaleNotExisted() raise exceptions.LocaleNotExisted()
return locale return locale
def _put_data_in_cookies(self, def _put_data_in_cookies(self, locale: str, access_token: str, refresh_token: str):
locale: str,
access_token: str,
refresh_token: str):
""" """
CHECK locale in cookies and PUT access and refresh tokens there. CHECK locale in cookies and PUT access and refresh tokens there.
cookies it is list that contain namedtuples cookies it is list that contain namedtuples
@ -36,22 +34,21 @@ class JWTViewMixin(generics.GenericAPIView):
COOKIES = list() COOKIES = list()
# Create locale namedtuple # Create locale namedtuple
locale = self.COOKIE(key='locale', _locale = self.COOKIE(key='locale',
value=locale, value=locale,
http=True, http_only=True,
secure=False) secure=False)
COOKIES.append(locale)
# Write to cookie access and refresh token with secure flag # Write to cookie access and refresh token with secure flag
_access_token = self.COOKIE(key='access_token', _access_token = self.COOKIE(key='access_token',
value=access_token, value=access_token,
http=self.ACCESS_TOKEN_HTTP, http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.ACCESS_TOKEN_SECURE) secure=self.ACCESS_TOKEN_SECURE)
_refresh_token = self.COOKIE(key='refresh_token', _refresh_token = self.COOKIE(key='refresh_token',
value=refresh_token, value=refresh_token,
http=self.REFRESH_TOKEN_HTTP, http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE) secure=self.REFRESH_TOKEN_SECURE)
COOKIES.extend((_access_token, _refresh_token)) COOKIES.extend((_locale, _access_token, _refresh_token))
return COOKIES return COOKIES
def _put_cookies_in_response(self, cookies: list, response: Response): def _put_cookies_in_response(self, cookies: list, response: Response):
@ -59,7 +56,8 @@ class JWTViewMixin(generics.GenericAPIView):
for cookie in cookies: for cookie in cookies:
response.set_cookie(key=cookie.key, response.set_cookie(key=cookie.key,
value=cookie.value, value=cookie.value,
secure=cookie.secure) secure=cookie.secure,
httponly=cookie.http_only)
return response return response
def _get_tokens_from_cookies(self, request, cookies: dict = None): def _get_tokens_from_cookies(self, request, cookies: dict = None):
@ -67,13 +65,43 @@ class JWTViewMixin(generics.GenericAPIView):
_cookies = request.COOKIES or cookies _cookies = request.COOKIES or cookies
return [self.COOKIE(key='access_token', return [self.COOKIE(key='access_token',
value=_cookies.get('access_token'), value=_cookies.get('access_token'),
http=self.ACCESS_TOKEN_HTTP, http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.ACCESS_TOKEN_SECURE), secure=self.ACCESS_TOKEN_SECURE),
self.COOKIE(key='refresh_token', self.COOKIE(key='refresh_token',
value=_cookies.get('refresh_token'), value=_cookies.get('refresh_token'),
http=self.REFRESH_TOKEN_HTTP, http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE)] secure=self.REFRESH_TOKEN_SECURE)]
class JWTCreateAPIView(JWTGenericViewMixin, generics.CreateAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
_locale = request.COOKIES.get('locale')
try:
locale = self._check_locale(locale=_locale)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
response = Response(serializer.data, status=status.HTTP_201_CREATED)
access_token, refresh_token = self._get_tokens_from_cookies(request)
except exceptions.LocaleNotExisted:
raise exceptions.LocaleNotExisted(locale=_locale)
else:
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(locale=locale,
access_token=access_token,
refresh_token=refresh_token),
response=response)
class JWTRetrieveAPIView(JWTGenericViewMixin, generics.RetrieveAPIView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Implement GET method""" """Implement GET method"""
_locale = request.COOKIES.get('locale') _locale = request.COOKIES.get('locale')
@ -87,7 +115,7 @@ class JWTViewMixin(generics.GenericAPIView):
response = self.get_paginated_response(serializer.data) response = self.get_paginated_response(serializer.data)
else: else:
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
response = Response(serializer.data) response = Response(serializer.data, status.HTTP_200_OK)
access_token, refresh_token = self._get_tokens_from_cookies(request) access_token, refresh_token = self._get_tokens_from_cookies(request)
@ -99,3 +127,62 @@ class JWTViewMixin(generics.GenericAPIView):
access_token=access_token, access_token=access_token,
refresh_token=refresh_token), refresh_token=refresh_token),
response=response) response=response)
class JWTDestroyAPIView(JWTGenericViewMixin, generics.DestroyAPIView):
"""
Concrete view for deleting a model instance.
"""
def delete(self, request, *args, **kwargs):
_locale = request.COOKIES.get('locale')
try:
locale = self._check_locale(locale=_locale)
instance = self.get_object()
instance.delete()
response = Response(status=status.HTTP_204_NO_CONTENT)
access_token, refresh_token = self._get_tokens_from_cookies(request)
except exceptions.LocaleNotExisted:
raise exceptions.LocaleNotExisted(locale=_locale)
else:
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(locale=locale,
access_token=access_token,
refresh_token=refresh_token),
response=response)
class JWTUpdateAPIView(JWTGenericViewMixin, generics.UpdateAPIView):
"""
Concrete view for updating a model instance.
"""
def put(self, request, *args, **kwargs):
_locale = request.COOKIES.get('locale')
try:
locale = self._check_locale(locale=_locale)
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
serializer.save()
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
response = Response(serializer.data)
access_token, refresh_token = self._get_tokens_from_cookies(request)
except exceptions.LocaleNotExisted:
raise exceptions.LocaleNotExisted(locale=_locale)
else:
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(locale=locale,
access_token=access_token,
refresh_token=refresh_token),
response=response)
def patch(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.put(request, *args, **kwargs)

View File

@ -7,3 +7,6 @@ SEND_SMS = False
SMS_CODE_SHOW = True SMS_CODE_SHOW = True
DOMAIN_URI = 'localhost:8000' DOMAIN_URI = 'localhost:8000'
# Increase access token lifetime for local deploy
SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(days=365)