"""Common views for application Account""" import json from braces.views import CsrfExemptMixin from django.conf import settings from django.urls import reverse from django.utils.encoding import force_text from django.utils.http import urlsafe_base64_decode from django.utils.translation import gettext_lazy as _ from oauth2_provider.oauth2_backends import OAuthLibCore from oauth2_provider.settings import oauth2_settings from oauth2_provider.views.mixins import OAuthLibMixin from rest_framework import generics from rest_framework import permissions from rest_framework import status from rest_framework.response import Response from rest_framework_simplejwt import tokens as jwt_tokens from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer from account.models import User from authorization import tasks from authorization.models import Application from authorization.serializers import common as serializers from utils import exceptions as utils_exceptions from utils.models import gm_token_generator from utils.views import (JWTGenericViewMixin, JWTCreateAPIView) # Mixins # JWTAuthView mixin class JWTAuthViewMixin(JWTCreateAPIView): """Mixin for authentication views""" def post(self, request, *args, **kwargs): """Implement POST method""" _locale = self._get_locale(request) 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_200_OK) access_token = serializer.data.get('access_token') refresh_token = serializer.data.get('refresh_token') 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) # OAuth2 class BaseOAuth2ViewMixin(generics.GenericAPIView): """BaseMixin for classic auth views""" def get_client_id(self, source) -> str: """Get application client id""" qs = Application.objects.by_source(source=source) if qs.exists(): return qs.first().client_id else: raise utils_exceptions.ServiceError(data={ 'detail': _('Application is not found')}) def get_client_secret(self, source) -> str: """Get application client id""" if source == Application.MOBILE: qs = Application.objects.by_source(source=source) if qs.exists: return qs.first().client_secret else: raise utils_exceptions.ServiceError(data={ 'detail': _('Not found an application with this source')}) class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin): """Basic mixin for OAuth2 views""" server_class = oauth2_settings.OAUTH2_SERVER_CLASS validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS oauthlib_backend_class = OAuthLibCore def prepare_request_data(self, validated_data: dict) -> dict: """Preparing request data""" source = validated_data.get('source') # Set OAuth2 request parameters _request_data = { 'client_id': self.get_client_id(source) } # Fill client secret parameter by platform if validated_data.get('source') == Application.MOBILE: _request_data['client_secret'] = self.get_client_secret(source) # Fill token parameter if transfer if validated_data.get('token'): _request_data['token'] = validated_data.get('token') if _request_data: return _request_data else: raise utils_exceptions.ServiceError() # Sign in via Facebook class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin): """ Implements an endpoint to convert a provider token to an access token The endpoint is used in the following flows: * Authorization code * Client credentials """ server_class = SocialTokenServer oauthlib_backend_class = KeepRequestCore permission_classes = (permissions.AllowAny, ) serializer_class = serializers.OAuth2Serialzier def get_jwt_token(self, user: User, oauth2_access_token: str, oauth2_refresh_token: str): """Get JWT token""" token = jwt_tokens.RefreshToken.for_user(user) # Adding additional information about user to payload token['user'] = user.get_user_info() # Adding OAuth2 tokens to payloads token['oauth2_fb'] = {'access_token': oauth2_access_token, 'refresh_token': oauth2_refresh_token} return token def post(self, request, *args, **kwargs): _locale = self._get_locale(request) try: locale = self._check_locale(locale=_locale) # Preparing request data serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) request_data = self.prepare_request_data(serializer.validated_data) request_data.update({ 'grant_type': settings.OAUTH2_SOCIAL_AUTH_GRANT_TYPE, 'backend': settings.OAUTH2_SOCIAL_AUTH_BACKEND_NAME }) # Use the rest framework `.data` to fake the post body of the django request. request._request.POST = request._request.POST.copy() for key, value in request_data.items(): request._request.POST[key] = value # OAuth2 authentication process url, headers, body, oauth2_status = self.create_token_response(request._request) body = json.loads(body) # Get JWT token if oauth2_status != status.HTTP_200_OK: raise ValueError('status isn\'t 200') # Get authenticated user user = User.objects.by_oauth2_access_token(token=body.get('access_token'))\ .first() # Create JWT token and put oauth2 token (access, refresh tokens) in payload token = self.get_jwt_token(user=user, oauth2_access_token=body.get('access_token'), oauth2_refresh_token=body.get('refresh_token')) access_token = str(token.access_token) refresh_token = str(token) response = Response(data={'access_token': access_token, 'refresh_token': refresh_token}, status=status.HTTP_200_OK) 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) # JWT # Sign in via username and password class SignUpView(JWTCreateAPIView): """View for classic signup""" permission_classes = (permissions.AllowAny, ) serializer_class = serializers.SignupSerializer def post(self, request, *args, **kwargs): """Implement POST-method""" _locale = self._get_locale(request) try: locale = self._check_locale(locale=_locale) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() response = Response(status=status.HTTP_201_CREATED) 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), response=response) # Login by username|email + password class LoginByUsernameOrEmailView(JWTAuthViewMixin): """Login by email and password""" permission_classes = (permissions.AllowAny,) serializer_class = serializers.LoginByUsernameOrEmailSerializer def post(self, request, *args, **kwargs): """Implement POST method""" _locale = self._get_locale(request) 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_200_OK) access_token = serializer.data.get('access_token') refresh_token = serializer.data.get('refresh_token') is_permanent = serializer.validated_data.get('remember') 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, permanent=is_permanent), response=response) # Refresh access_token class RefreshTokenView(JWTGenericViewMixin): """Refresh access_token""" permission_classes = (permissions.IsAuthenticated,) serializer_class = serializers.RefreshTokenSerializer def post(self, request, *args, **kwargs): _locale = self._get_locale(request) 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 = serializer.data.get('access_token') refresh_token = serializer.data.get('refresh_token') 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(JWTGenericViewMixin): """Logout user""" serializer_class = serializers.LogoutSerializer def create(self, request, *args, **kwargs): """Override create method""" _locale = self._get_locale(request) try: locale = self._check_locale(locale=_locale) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() response = Response(status=status.HTTP_200_OK) 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), response=response)