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.serializers import common as serializers
from utils import exceptions as utils_exceptions
from utils.views import JWTViewMixin
from utils.views import (JWTGenericViewMixin,
JWTCreateAPIView,
JWTDestroyAPIView,
JWTUpdateAPIView,
JWTRetrieveAPIView)
# Mixins
# JWTAuthView mixin
class JWTAuthViewMixin(JWTViewMixin):
class JWTAuthViewMixin(JWTCreateAPIView):
"""Mixin for authentication views"""
def post(self, request, *args, **kwargs):
@ -98,7 +102,7 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
# Sign in via Facebook
class OAuth2SignUpView(OAuth2ViewMixin, JWTAuthViewMixin):
class OAuth2SignUpView(OAuth2ViewMixin, JWTCreateAPIView):
"""
Implements an endpoint to convert a provider token to an access token
@ -176,7 +180,7 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTAuthViewMixin):
# JWT
# Sign in via username and password
class SignUpView(JWTAuthViewMixin):
class SignUpView(JWTCreateAPIView):
"""View for classic signup"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.SignupSerializer
@ -207,18 +211,37 @@ class SignUpView(JWTAuthViewMixin):
# Login by username|email + password
class LoginByUsernameOrEmailView(JWTAuthViewMixin):
class LoginByUsernameOrEmailView(JWTCreateAPIView):
"""Login by email and password"""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.LoginByUsernameOrEmailSerializer
# Refresh access_token
class RefreshTokenView(JWTAuthViewMixin):
class RefreshTokenView(JWTGenericViewMixin):
"""Refresh access_token"""
permission_classes = (permissions.IsAuthenticated,)
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
class LogoutView(generics.CreateAPIView):

View File

@ -1,10 +1,10 @@
from rest_framework import generics, permissions
from news.models import News
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."""
queryset = News.objects.all()
permission_classes = (permissions.AllowAny, )

View File

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

View File

@ -3,19 +3,20 @@ from collections import namedtuple
from translation import models as translation_models
from utils import exceptions
from rest_framework.response import Response
from rest_framework import status
# JWT
# Login base view mixin
class JWTViewMixin(generics.GenericAPIView):
class JWTGenericViewMixin(generics.GenericAPIView):
"""JWT view mixin"""
ACCESS_TOKEN_HTTP = True
ACCESS_TOKEN_HTTP_ONLY = True
ACCESS_TOKEN_SECURE = False
REFRESH_TOKEN_HTTP = True
REFRESH_TOKEN_HTTP_ONLY = True
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):
@ -24,10 +25,7 @@ class JWTViewMixin(generics.GenericAPIView):
raise exceptions.LocaleNotExisted()
return locale
def _put_data_in_cookies(self,
locale: str,
access_token: str,
refresh_token: str):
def _put_data_in_cookies(self, locale: str, access_token: str, refresh_token: str):
"""
CHECK locale in cookies and PUT access and refresh tokens there.
cookies it is list that contain namedtuples
@ -36,22 +34,21 @@ class JWTViewMixin(generics.GenericAPIView):
COOKIES = list()
# Create locale namedtuple
locale = self.COOKIE(key='locale',
value=locale,
http=True,
secure=False)
COOKIES.append(locale)
_locale = self.COOKIE(key='locale',
value=locale,
http_only=True,
secure=False)
# Write to cookie access and refresh token with secure flag
_access_token = self.COOKIE(key='access_token',
value=access_token,
http=self.ACCESS_TOKEN_HTTP,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.ACCESS_TOKEN_SECURE)
_refresh_token = self.COOKIE(key='refresh_token',
value=refresh_token,
http=self.REFRESH_TOKEN_HTTP,
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE)
COOKIES.extend((_access_token, _refresh_token))
COOKIES.extend((_locale, _access_token, _refresh_token))
return COOKIES
def _put_cookies_in_response(self, cookies: list, response: Response):
@ -59,7 +56,8 @@ class JWTViewMixin(generics.GenericAPIView):
for cookie in cookies:
response.set_cookie(key=cookie.key,
value=cookie.value,
secure=cookie.secure)
secure=cookie.secure,
httponly=cookie.http_only)
return response
def _get_tokens_from_cookies(self, request, cookies: dict = None):
@ -67,13 +65,43 @@ class JWTViewMixin(generics.GenericAPIView):
_cookies = request.COOKIES or cookies
return [self.COOKIE(key='access_token',
value=_cookies.get('access_token'),
http=self.ACCESS_TOKEN_HTTP,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.ACCESS_TOKEN_SECURE),
self.COOKIE(key='refresh_token',
value=_cookies.get('refresh_token'),
http=self.REFRESH_TOKEN_HTTP,
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
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):
"""Implement GET method"""
_locale = request.COOKIES.get('locale')
@ -87,7 +115,7 @@ class JWTViewMixin(generics.GenericAPIView):
response = self.get_paginated_response(serializer.data)
else:
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)
@ -99,3 +127,62 @@ class JWTViewMixin(generics.GenericAPIView):
access_token=access_token,
refresh_token=refresh_token),
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
DOMAIN_URI = 'localhost:8000'
# Increase access token lifetime for local deploy
SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(days=365)