from collections import namedtuple from django.conf import settings from rest_framework import generics from rest_framework import status from rest_framework.response import Response # JWT # Login base view mixin class JWTGenericViewMixin(generics.GenericAPIView): """JWT view mixin""" ACCESS_TOKEN_HTTP_ONLY = False ACCESS_TOKEN_SECURE = False REFRESH_TOKEN_HTTP_ONLY = False REFRESH_TOKEN_SECURE = False COOKIE = namedtuple('COOKIE', ['key', 'value', 'http_only', 'secure', 'max_age']) def _put_data_in_cookies(self, access_token: str = None, refresh_token: str = None, permanent: bool = None): """ cookies it is list that contain namedtuples cookies would contain key, value and secure parameters. """ COOKIES = list() # Write to cookie access and refresh token with secure flag if access_token and refresh_token: _access_token = self.COOKIE(key='access_token', value=access_token, http_only=self.ACCESS_TOKEN_HTTP_ONLY, secure=self.ACCESS_TOKEN_SECURE, max_age=settings.COOKIES_MAX_AGE if permanent else None) _refresh_token = self.COOKIE(key='refresh_token', value=refresh_token, http_only=self.REFRESH_TOKEN_HTTP_ONLY, secure=self.REFRESH_TOKEN_SECURE, max_age=settings.COOKIES_MAX_AGE if permanent else None) COOKIES.extend((_access_token, _refresh_token)) return COOKIES def _put_cookies_in_response(self, cookies: list, response: Response): """Update COOKIES in response from namedtuple""" for cookie in cookies: # todo: remove config for develop from os import environ configuration = environ.get('SETTINGS_CONFIGURATION', None) if configuration == 'development': response.set_cookie(key=cookie.key, value=cookie.value, secure=cookie.secure, httponly=cookie.http_only, max_age=cookie.max_age, domain='.id-east.ru') else: response.set_cookie(key=cookie.key, value=cookie.value, secure=cookie.secure, httponly=cookie.http_only, max_age=cookie.max_age,) return response def _get_tokens_from_cookies(self, request, cookies: dict = None): """Get user tokens from cookies and put in namedtuple""" _cookies = request.COOKIES or cookies return [self.COOKIE(key='access_token', value=_cookies.get('access_token'), http_only=self.ACCESS_TOKEN_HTTP_ONLY, secure=self.ACCESS_TOKEN_SECURE, max_age=_cookies.get('max_age')), self.COOKIE(key='refresh_token', value=_cookies.get('refresh_token'), http_only=self.REFRESH_TOKEN_HTTP_ONLY, secure=self.REFRESH_TOKEN_SECURE, max_age=_cookies.get('max_age'))] class JWTListAPIView(JWTGenericViewMixin, generics.ListAPIView): """ Concrete view for creating a model instance. """ def get(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) response = self.get_paginated_response(serializer.data) else: serializer = self.get_serializer(queryset, many=True) response = Response(serializer.data) access_token, refresh_token = self._get_tokens_from_cookies(request) return self._put_cookies_in_response( cookies=self._put_data_in_cookies(access_token=access_token.value, refresh_token=refresh_token.value), response=response) class JWTCreateAPIView(JWTGenericViewMixin, generics.CreateAPIView): """ Concrete view for creating a model instance. """ def post(self, request, *args, **kwargs): 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) return self._put_cookies_in_response( cookies=self._put_data_in_cookies(access_token=access_token.value, refresh_token=refresh_token.value), response=response) class JWTRetrieveAPIView(JWTGenericViewMixin, generics.RetrieveAPIView): """ Concrete view for retrieving a model instance. """ def get(self, request, *args, **kwargs): """Implement GET method""" queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) response = self.get_paginated_response(serializer.data) else: serializer = self.get_serializer(queryset, many=True) response = Response(serializer.data, status.HTTP_200_OK) access_token, refresh_token = self._get_tokens_from_cookies(request) return self._put_cookies_in_response( cookies=self._put_data_in_cookies(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): instance = self.get_object() instance.delete() response = Response(status=status.HTTP_204_NO_CONTENT) access_token, refresh_token = self._get_tokens_from_cookies(request) return self._put_cookies_in_response( cookies=self._put_data_in_cookies(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): 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) return self._put_cookies_in_response( cookies=self._put_data_in_cookies(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)