From 687b4d3a82e42d545c9442e1feb05bc9334e1eea Mon Sep 17 00:00:00 2001 From: Anatoly Date: Sat, 10 Aug 2019 13:43:00 +0300 Subject: [PATCH] version 0.0.5.5: added method to logout, refactored existed serializers and views a lot --- apps/account/models.py | 13 +++ apps/authorization/serializers/common.py | 7 +- apps/authorization/urls/common.py | 14 ++- apps/authorization/views/common.py | 105 +++++++++++++---------- 4 files changed, 86 insertions(+), 53 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index f203345c..f649f77b 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -55,6 +55,19 @@ class User(ImageMixin, AbstractUser): def remove_token(self): Token.objects.filter(user=self).delete() + def remove_access_tokens(self, source): + """Method to remove user access tokens""" + self.oauth2_provider_accesstoken.filter(application__source=source)\ + .delete() + + def revoke_refresh_tokens(self, source): + """Method to remove user refresh tokens""" + refresh_tokens = self.oauth2_provider_refreshtoken.filter( + application__source=source) + for token in refresh_tokens: + token.revoke() + @property def get_username(self): + """Get user username""" return self.username diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index 5250cf3d..00c43514 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -4,9 +4,8 @@ from rest_framework import serializers from rest_framework import validators as rest_validators from account import models as account_models -from utils import exceptions as utils_exceptions from authorization.models import Application -from django.utils.translation import gettext_lazy as _ +from utils import exceptions as utils_exceptions # Mixins @@ -85,6 +84,10 @@ class LoginSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer): fields = ('username', 'password', 'source') +class LogoutSerializer(BaseAuthSerializerMixin): + """Serializer for logout""" + + # OAuth class OAuth2Serialzier(BaseAuthSerializerMixin): """Serializer OAuth2 authorization""" diff --git a/apps/authorization/urls/common.py b/apps/authorization/urls/common.py index 06761575..706cd900 100644 --- a/apps/authorization/urls/common.py +++ b/apps/authorization/urls/common.py @@ -3,7 +3,7 @@ from django.conf import settings from django.conf.urls import url, include from django.urls import path from oauth2_provider.views import AuthorizationView -from rest_framework_social_oauth2.views import invalidate_sessions +from rest_framework_social_oauth2 import views as drf_social_oauth2_views from social_core.utils import setting_name from social_django import views as social_django_views @@ -30,15 +30,21 @@ urlpatterns_social_django = [ urlpatterns_rest_framework_social_oauth2 = [ url(r'^authorize/?$', AuthorizationView.as_view(), name="authorize"), url('', include('social_django.urls', namespace="social")), - url(r'^invalidate-sessions/?$', invalidate_sessions, name="invalidate_sessions") + url(r'^invalidate-sessions/?$', drf_social_oauth2_views.invalidate_sessions, + name="invalidate_sessions") ] urlpatterns_api = [ + # sign up path('social/signup/', views.SocialSignUpView.as_view(), name='signup-social'), + # sign in path('login/username/', views.LoginByUsernameView.as_view(), name='login-username'), path('login/email/', views.LoginByEmailView.as_view(), name='login-email'), - path('revoke-token/', views.RevokeTokenView.as_view(), name="revoke_token"), - path('token/', views.TokenView.as_view(), name="token"), # for admin login page + # for admin sign in page + path('token/', drf_social_oauth2_views .TokenView.as_view(), name="token"), + # logout + path('logout/', views.LogoutView.as_view(), name="logout"), + path('revoke-token/', views.RevokeTokenView.as_view(), name="revoke-token"), ] urlpatterns = urlpatterns_api + \ diff --git a/apps/authorization/views/common.py b/apps/authorization/views/common.py index 1aea4d78..3f6e45af 100644 --- a/apps/authorization/views/common.py +++ b/apps/authorization/views/common.py @@ -1,6 +1,7 @@ """Common views for application Account""" import json +from rest_framework import status from braces.views import CsrfExemptMixin from django.conf import settings from django.utils.translation import gettext_lazy as _ @@ -8,20 +9,20 @@ 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 permissions -from rest_framework.generics import GenericAPIView, CreateAPIView +from rest_framework.generics import GenericAPIView +from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer - +from account import models as account_models from authorization.models import Application from authorization.serializers import common as serializers from utils import exceptions as utils_exceptions # Mixins -class OAuth2ViewMixin(GenericAPIView): - """Basic mixin for OAuth2 views""" - +class BaseViewMixin(GenericAPIView): + """BaseMixin for classic auth views""" def get_client_id(self, source) -> str: """Get application client id""" qs = Application.objects.by_source(source=source) @@ -41,6 +42,13 @@ class OAuth2ViewMixin(GenericAPIView): raise utils_exceptions.SerivceError(data={ 'detail': _('Not found an application with this source')}) + +class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseViewMixin): + """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') @@ -60,9 +68,8 @@ class OAuth2ViewMixin(GenericAPIView): raise utils_exceptions.ServiceError() -# Create your views here. -class LoginByUsernameView(CsrfExemptMixin, OAuthLibMixin, - OAuth2ViewMixin, GenericAPIView): +# Login +class LoginByUsernameView(OAuth2ViewMixin, GenericAPIView): """ Implements an endpoint to provide access tokens @@ -72,9 +79,6 @@ class LoginByUsernameView(CsrfExemptMixin, OAuthLibMixin, * Password * Client credentials """ - server_class = oauth2_settings.OAUTH2_SERVER_CLASS - validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS - oauthlib_backend_class = OAuthLibCore permission_classes = (permissions.AllowAny,) serializer_class = serializers.LoginSerializer @@ -114,8 +118,49 @@ class LoginByEmailView(LoginByUsernameView): serializer_class = serializers.LoginByEmailSerializer -class SocialSignUpView(CsrfExemptMixin, OAuthLibMixin, - OAuth2ViewMixin, GenericAPIView): +# Logout +class LogoutView(GenericAPIView): + """Logout view""" + permission_classes = (permissions.IsAuthenticated, ) + serializer_class = serializers.LogoutSerializer + + def post(self, request, *args, **kwargs): + """Method to logout user by deleting access tokens by source""" + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + # Setup attributes + source = serializer.validated_data.get('source') + request.user.revoke_refresh_tokens(source=source) + return Response(status=status.HTTP_204_NO_CONTENT) + + +class RevokeTokenView(OAuth2ViewMixin, GenericAPIView): + """ + Implements an endpoint to revoke access or refresh tokens + """ + permission_classes = (permissions.AllowAny,) + serializer_class = serializers.OAuth2Serialzier + + def post(self, request, *args, **kwargs): + # Use the rest framework `.data` to fake the post body of the django request. + # 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) + # 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 + + url, headers, body, status = self.create_revocation_response(request._request) + response = Response(data=json.loads(body) if body else '', status=status if body else 204) + + for k, v in headers.items(): + response[k] = v + return response + + +class SocialSignUpView(OAuth2ViewMixin, GenericAPIView): """ Implements an endpoint to convert a provider token to an access token @@ -125,7 +170,6 @@ class SocialSignUpView(CsrfExemptMixin, OAuthLibMixin, * Client credentials """ server_class = SocialTokenServer - validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS oauthlib_backend_class = KeepRequestCore permission_classes = (permissions.AllowAny,) serializer_class = serializers.OAuth2Serialzier @@ -153,36 +197,6 @@ class SocialSignUpView(CsrfExemptMixin, OAuthLibMixin, return response -class RevokeTokenView(CsrfExemptMixin, OAuthLibMixin, - OAuth2ViewMixin, GenericAPIView): - """ - Implements an endpoint to revoke access or refresh tokens - """ - server_class = oauth2_settings.OAUTH2_SERVER_CLASS - validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS - oauthlib_backend_class = OAuthLibCore - permission_classes = (permissions.AllowAny,) - serializer_class = serializers.OAuth2Serialzier - - def post(self, request, *args, **kwargs): - # Use the rest framework `.data` to fake the post body of the django request. - # 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) - # 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 - - url, headers, body, status = self.create_revocation_response(request._request) - response = Response(data=json.loads(body) if body else '', status=status if body else 204) - - for k, v in headers.items(): - response[k] = v - return response - - class TokenView(CsrfExemptMixin, OAuthLibMixin, GenericAPIView): """ Implements an endpoint to provide access tokens @@ -193,9 +207,6 @@ class TokenView(CsrfExemptMixin, OAuthLibMixin, GenericAPIView): * Password * Client credentials """ - server_class = oauth2_settings.OAUTH2_SERVER_CLASS - validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS - oauthlib_backend_class = OAuthLibCore permission_classes = (permissions.AllowAny,) def post(self, request, *args, **kwargs):