version 0.0.5.12: refactored authorization app (added JWT support)

This commit is contained in:
Anatoly 2019-08-13 14:14:18 +03:00
parent 0a35f06ff5
commit cc04a50709
7 changed files with 302 additions and 233 deletions

View File

@ -40,6 +40,16 @@ class UserQuerySet(models.QuerySet):
"""Filter only active users.""" """Filter only active users."""
return self.filter(is_active=switcher) return self.filter(is_active=switcher)
def by_access_token(self, token):
"""Find user by access token"""
return self.filter(oauth2_provider_accesstoken__token=token,
oauth2_provider_accesstoken__expires__gt=timezone.now())
def by_refresh_token(self, token):
"""Find user by access token"""
return self.filter(oauth2_provider_refreshtoken__token=token,
oauth2_provider_refreshtoken__expires__gt=timezone.now())
class User(ImageMixin, AbstractUser): class User(ImageMixin, AbstractUser):
"""Base user model.""" """Base user model."""
@ -62,6 +72,17 @@ class User(ImageMixin, AbstractUser):
"""String method.""" """String method."""
return "%s:%s" % (self.email, self.get_short_name()) return "%s:%s" % (self.email, self.get_short_name())
def get_user_info(self):
"""Get information about user"""
return {
"username": self.username,
"first_name": self.first_name if self.first_name else None,
"last_name": self.last_name if self.last_name else None,
"email": self.email if self.email else None,
"newsletter": self.newsletter,
"is_active": self.is_active
}
def change_status(self, switcher: bool = False): def change_status(self, switcher: bool = False):
"""Method to set user status to active or inactive""" """Method to set user status to active or inactive"""
self.is_active = switcher self.is_active = switcher

View File

@ -2,11 +2,19 @@
from django.contrib.auth import password_validation as password_validators from django.contrib.auth import password_validation as password_validators
from rest_framework import serializers from rest_framework import serializers
from rest_framework import validators as rest_validators from rest_framework import validators as rest_validators
from django.contrib.auth import authenticate
from django.conf import settings
from account import models as account_models from account import models as account_models
from authorization.models import Application from authorization.models import Application
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
# JWT
from rest_framework_simplejwt.tokens import RefreshToken, SlidingToken, UntypedToken
JWT_SETTINGS = settings.SIMPLE_JWT
# Mixins # Mixins
class BaseAuthSerializerMixin(serializers.Serializer): class BaseAuthSerializerMixin(serializers.Serializer):
@ -14,6 +22,30 @@ class BaseAuthSerializerMixin(serializers.Serializer):
source = serializers.ChoiceField(choices=Application.SOURCES) source = serializers.ChoiceField(choices=Application.SOURCES)
class JWTBaseMixin(serializers.Serializer):
"""
Mixin for JWT authentication.
Uses in serializers when need give in response access and refresh token
"""
# RESPONSE
refresh_token = serializers.CharField(read_only=True)
access_token = serializers.CharField(read_only=True)
def get_token(self):
"""Create JWT token"""
user = self.instance
token = RefreshToken.for_user(user)
token['user'] = user.get_user_info()
return token
def to_representation(self, instance):
"""Override to_representation method"""
token = self.get_token()
setattr(instance, 'refresh_token', str(token))
setattr(instance, 'access_token', str(token.access_token))
return super().to_representation(instance)
class LoginSerializerMixin(BaseAuthSerializerMixin): class LoginSerializerMixin(BaseAuthSerializerMixin):
"""Mixin for login serializers""" """Mixin for login serializers"""
password = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
@ -26,7 +58,7 @@ class ClassicAuthSerializerMixin(BaseAuthSerializerMixin):
# Serializers # Serializers
class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer): class SignupSerializer(JWTBaseMixin, serializers.ModelSerializer):
"""Signup serializer serializer mixin""" """Signup serializer serializer mixin"""
# REQUEST # REQUEST
username = serializers.CharField( username = serializers.CharField(
@ -35,13 +67,13 @@ class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer):
) )
password = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
email = serializers.EmailField(write_only=True) email = serializers.EmailField(write_only=True)
newsletter = serializers.BooleanField() newsletter = serializers.BooleanField(write_only=True)
class Meta: class Meta:
model = account_models.User model = account_models.User
fields = ( fields = (
'username', 'first_name', 'last_name', 'password', 'username', 'password', 'email', 'newsletter',
'newsletter', 'email', 'source' 'access_token', 'refresh_token',
) )
def validate_password(self, data): def validate_password(self, data):
@ -64,47 +96,93 @@ class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer):
return obj return obj
class LoginSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer): class LoginByUsernameSerializer(JWTBaseMixin, serializers.ModelSerializer):
"""Serializer for login user""" """Serializer for login user by username and password"""
username = serializers.CharField(write_only=True) username = serializers.CharField(write_only=True)
password = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
class Meta: class Meta:
"""Meta-class""" """Meta-class"""
model = account_models.User model = account_models.User
fields = ('username', 'password', 'source') fields = (
'username', 'password', 'refresh_token', 'access_token'
)
def validate(self, attrs):
"""Override validate method"""
username = attrs.pop('username')
password = attrs.pop('password')
user = authenticate(username=username,
password=password)
if not user:
raise utils_exceptions.UserNotFoundError()
self.instance = user
return attrs
class RefreshTokenSerializer(BaseAuthSerializerMixin): class LoginByEmailSerializer(JWTBaseMixin, serializers.ModelSerializer):
"""Serializer for refresh token view""" """Serializer for login user"""
refresh_token = serializers.CharField(write_only=True)
class LoginByEmailSerializer(LoginSerializerMixin, serializers.ModelSerializer):
"""Serializer for signing up user by email"""
email = serializers.EmailField(write_only=True) email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
class Meta: class Meta:
"""Meta-class""" """Meta-class"""
model = account_models.User model = account_models.User
fields = ('email', 'password', 'source') fields = (
'email', 'password', 'refresh_token', 'access_token'
)
def validate(self, attrs): def validate(self, attrs):
"""Override validate method""" """Override validate method"""
email = attrs.pop('email')
password = attrs.pop('password')
try: try:
user = account_models.User.objects.get(email=attrs.get('email')) user = account_models.User.objects.get(email=email)
attrs['username'] = user.get_username()
except account_models.User.DoesNotExist: except account_models.User.DoesNotExist:
raise utils_exceptions.UserNotFoundError() raise utils_exceptions.UserNotFoundError()
else: else:
user = authenticate(username=user.get_username(),
password=password)
if not user:
raise utils_exceptions.UserNotFoundError()
self.instance = user
return attrs return attrs
class LogoutSerializer(BaseAuthSerializerMixin): class RefreshTokenSerializer(serializers.Serializer):
"""Serializer for logout""" """Serializer for refresh token view"""
refresh_token = serializers.CharField()
access_token = serializers.CharField(read_only=True)
def validate(self, attrs):
"""Override validate method"""
token = RefreshToken(attrs['refresh_token'])
data = {'access_token': str(token.access_token)}
if JWT_SETTINGS.get('ROTATE_REFRESH_TOKENS'):
if JWT_SETTINGS.get('BLACKLIST_AFTER_ROTATION'):
try:
# Attempt to blacklist the given refresh token
token.blacklist()
except AttributeError:
# If blacklist app not installed, `blacklist` method will
# not be present
pass
token.set_jti()
token.set_exp()
data['refresh_token'] = str(token)
return data
# OAuth # OAuth
class OAuth2Serialzier(BaseAuthSerializerMixin): class OAuth2Serialzier(BaseAuthSerializerMixin):
"""Serializer OAuth2 authorization""" """Serializer OAuth2 authorization"""
token = serializers.CharField(max_length=255) token = serializers.CharField(max_length=255)
class OAuth2LogoutSerializer(BaseAuthSerializerMixin):
"""Serializer for logout"""

View File

@ -1,55 +1,51 @@
"""Common url routing for application authorization""" """Common url routing for application authorization"""
from django.conf import settings from django.conf import settings
from django.conf.urls import url, include
from django.urls import path from django.urls import path
from django.conf.urls import url
from oauth2_provider.views import AuthorizationView from oauth2_provider.views import AuthorizationView
from social_django import views as social_django_views
from rest_framework_social_oauth2 import views as drf_social_oauth2_views from rest_framework_social_oauth2 import views as drf_social_oauth2_views
from social_core.utils import setting_name from social_core.utils import setting_name
from social_django import views as social_django_views
from authorization.views import common as views from authorization.views import common as views
extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or '' extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or ''
app_name = 'oauth2' app_name = 'auth'
urlpatterns_social_django = [ urlpatterns_social_django = [
# authentication / association url(r'^authorize/?$', AuthorizationView.as_view(), name="authorize"),
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), social_django_views.auth,
name='begin'),
url(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), social_django_views.complete, url(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), social_django_views.complete,
name='complete'), name='complete'),
# disconnection
url(r'^disconnect/(?P<backend>[^/]+){0}$'.format(extra), social_django_views.disconnect,
name='disconnect'),
url(r'^disconnect/(?P<backend>[^/]+)/(?P<association_id>\d+){0}$'
.format(extra), social_django_views.disconnect, name='disconnect_individual'),
] ]
urlpatterns_rest_framework_social_oauth2 = [ urlpatterns_oauth2 = [
url(r'^authorize/?$', AuthorizationView.as_view(), name="authorize"), path('oauth2/signup/facebook/', views.OAuth2SignUpView.as_view(),
url('', include('social_django.urls', namespace="social")), name='oauth2-signup-facebook'),
url(r'^invalidate-sessions/?$', drf_social_oauth2_views.invalidate_sessions,
name="invalidate_sessions")
]
urlpatterns_api = [
# sign up
path('signup/facebook/', views.SocialSignUpView.as_view(), name='signup-facebook'),
path('signup/', views.SignUpView.as_view(), name='signup'),
# sign in
path('login/username/', views.LoginByUsernameView.as_view(), name='login-username'),
path('login/email/', views.LoginByEmailView.as_view(), name='login-email'),
# for admin sign in page # for admin sign in page
path('token/', drf_social_oauth2_views .TokenView.as_view(), name="token"), path('oauth2/token/', drf_social_oauth2_views .TokenView.as_view(),
# logout name="token"),
path('logout/', views.LogoutView.as_view(), name="logout"),
path('revoke-token/', views.RevokeTokenView.as_view(), name="revoke-token"),
# refresh token
path('refresh-token/', views.RefreshTokenView.as_view(), name="refresh-token"),
] ]
urlpatterns = urlpatterns_api + \ urlpatterns_jwt = [
urlpatterns_social_django + \ path('signup/', views.SignUpView.as_view(),
urlpatterns_rest_framework_social_oauth2 name='signup'),
# sign in
path('login/username/', views.LoginByUsernameView.as_view(),
name='login-username'),
path('login/email/', views.LoginByEmailView.as_view(),
name='login-email'),
# refresh token
path('refresh-token/', views.RefreshTokenView.as_view(),
name="refresh-token"),
# logout
# path('logout/', views.LogoutView.as_view(),
# name="logout"),
]
urlpatterns = urlpatterns_jwt + \
urlpatterns_oauth2 + \
urlpatterns_social_django # for social oauth2

View File

@ -7,21 +7,29 @@ from django.utils.translation import gettext_lazy as _
from oauth2_provider.oauth2_backends import OAuthLibCore from oauth2_provider.oauth2_backends import OAuthLibCore
from oauth2_provider.settings import oauth2_settings from oauth2_provider.settings import oauth2_settings
from oauth2_provider.views.mixins import OAuthLibMixin from oauth2_provider.views.mixins import OAuthLibMixin
from rest_framework import generics
from rest_framework import permissions from rest_framework import permissions
from rest_framework import status from rest_framework import status
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore
from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer
from rest_framework_simplejwt import tokens as jwt_tokens
from rest_framework.settings import settings as rest_settings
from django.utils import timezone
from rest_framework_simplejwt.utils import datetime_to_epoch
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 import permissions as utils_permissions
# JWT
# Mixins # Mixins
class BaseViewMixin(GenericAPIView): # OAuth2
class BaseOAuth2ViewMixin(generics.GenericAPIView):
"""BaseMixin for classic auth views""" """BaseMixin for classic auth views"""
def get_client_id(self, source) -> str: def get_client_id(self, source) -> str:
"""Get application client id""" """Get application client id"""
@ -43,7 +51,7 @@ class BaseViewMixin(GenericAPIView):
'detail': _('Not found an application with this source')}) 'detail': _('Not found an application with this source')})
class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseViewMixin): class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
"""Basic mixin for OAuth2 views""" """Basic mixin for OAuth2 views"""
server_class = oauth2_settings.OAUTH2_SERVER_CLASS server_class = oauth2_settings.OAUTH2_SERVER_CLASS
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
@ -68,8 +76,31 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseViewMixin):
raise utils_exceptions.ServiceError() raise utils_exceptions.ServiceError()
# Sign in # JWT
class SocialSignUpView(OAuth2ViewMixin, GenericAPIView): # Login base view mixin
class JWTViewMixin(generics.GenericAPIView):
"""JWT view mixin"""
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
response = Response(serializer.data, status=status.HTTP_200_OK)
if 'locale' in request.COOKIES:
# Write locale in cookie
key, value = 'locale', request.COOKIES.get('locale')
response.set_cookie(key=key, value=value)
# Write to cookie access and refresh token with secure flag
response.set_cookie(key='access_token',
value=serializer.data.get('access_token'),
secure=True)
response.set_cookie(key='refresh_token',
value=serializer.data.get('refresh_token'),
secure=True)
return response
# Serializers
# Sign in via Facebook
class OAuth2SignUpView(OAuth2ViewMixin, generics.GenericAPIView):
""" """
Implements an endpoint to convert a provider token to an access token Implements an endpoint to convert a provider token to an access token
@ -83,6 +114,18 @@ class SocialSignUpView(OAuth2ViewMixin, GenericAPIView):
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )
serializer_class = serializers.OAuth2Serialzier serializer_class = serializers.OAuth2Serialzier
def get_jwt_token(self, user: User,
access_token: str,
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': access_token,
'refresh_token': refresh_token}
return token
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""Override POST method""" """Override POST method"""
# Preparing request data # Preparing request data
@ -99,191 +142,93 @@ class SocialSignUpView(OAuth2ViewMixin, GenericAPIView):
request._request.POST[key] = value request._request.POST[key] = value
url, headers, body, oauth2_status = self.create_token_response(request._request) url, headers, body, oauth2_status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=oauth2_status) body = json.loads(body)
# Get JWT token
for k, v in headers.items(): if oauth2_status != status.HTTP_200_OK:
response[k] = v raise ValueError('status isn\'t 200')
return response user = User.objects.by_access_token(token=body.get('access_token'))\
.first()
token = self.get_jwt_token(user=user,
access_token=body.get('access_token'),
refresh_token=body.get('refresh_token'))
return Response(data={'refresh': str(token),
'access': str(token.access_token)},
status=status.HTTP_200_OK)
class SignUpView(OAuth2ViewMixin, GenericAPIView): # JWT
# Sign in via username and password
class SignUpView(JWTViewMixin):
"""View for classic signup""" """View for classic signup"""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )
serializer_class = serializers.SignupSerializer serializer_class = serializers.SignupSerializer
def post(self, request): def post(self, request, *args, **kwargs):
"""Post-method to sign up new user"""
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
serializer.save() serializer.save()
request_data = self.prepare_request_data(serializer.validated_data) response = Response(serializer.data, status=status.HTTP_201_CREATED)
request_data.update({ if 'locale' in request.COOKIES:
'grant_type': 'password', # Write locale in cookie
'username': serializer.validated_data.get('username'), key, value = 'locale', request.COOKIES.get('locale')
'password': serializer.validated_data.get('password'), response.set_cookie(key=key, value=value)
}) # Write to cookie access and refresh token with secure flag
# Use the rest framework `.data` to fake the post body of the django request. response.set_cookie(key='access_token',
request._request.POST = request._request.POST.copy() value=serializer.data.get('access_token'),
for key, value in request_data.items(): secure=True)
request._request.POST[key] = value response.set_cookie(key='refresh_token',
value=serializer.data.get('refresh_token'),
url, headers, body, oauth2_status = self.create_token_response(request._request) secure=True)
response = Response(data=json.loads(body), status=oauth2_status)
for k, v in headers.items():
response[k] = v
return response return response
# Login # Login by username + password
class LoginByUsernameView(OAuth2ViewMixin, GenericAPIView): class LoginByUsernameView(JWTViewMixin):
""" """Login by username"""
Implements an endpoint to provide access tokens
The endpoint is used in the following flows:
* Authorization code
* Password
* Client credentials
"""
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.LoginSerializer serializer_class = serializers.LoginByUsernameSerializer
def post(self, request, *args, **kwargs):
# 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': 'password',
'username': serializer.validated_data.get('username'),
'password': serializer.validated_data.get('password'),
})
# 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, oauth2_status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=oauth2_status)
for k, v in headers.items():
response[k] = v
return response
class LoginByEmailView(LoginByUsernameView): # Login by email + password
""" class LoginByEmailView(JWTViewMixin):
Implements an endpoint to provide access tokens """Login by email and password"""
permission_classes = (permissions.AllowAny,)
The endpoint is used in the following flows:
* Authorization code
* Password
* Client credentials
"""
serializer_class = serializers.LoginByEmailSerializer serializer_class = serializers.LoginByEmailSerializer
# Logout # Refresh access_token
class LogoutView(GenericAPIView): class RefreshTokenView(generics.GenericAPIView):
"""Logout view""" """Refresh access_token"""
permission_classes = (permissions.IsAuthenticated, ) 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, oauth2_status = self.create_revocation_response(request._request)
response = Response(data=json.loads(body) if body else '',
status=oauth2_status if body else status.HTTP_204_NO_CONTENT)
for k, v in headers.items():
response[k] = v
return response
class RefreshTokenView(OAuth2ViewMixin, GenericAPIView):
"""
Implements an endpoint to provide access tokens
The endpoint is used in the following flows:
* Authorization code
* Password
* Client credentials
"""
permission_classes = (utils_permissions.IsAuthenticatedAndHasRefreshToken, )
serializer_class = serializers.RefreshTokenSerializer serializer_class = serializers.RefreshTokenSerializer
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
# Preparing request data """POST method"""
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
request_data = self.prepare_request_data(serializer.validated_data) response = Response(serializer.validated_data, status=status.HTTP_200_OK)
request_data.update({ if 'locale' in request.COOKIES:
'grant_type': 'refresh_token', # Write locale in cookie
'refresh_token': serializer.validated_data.get('refresh_token') key, value = 'locale', request.COOKIES.get('locale')
}) response.set_cookie(key=key, value=value)
# Use the rest framework `.data` to fake the post body of the django request. # Write to cookie access and refresh token with secure flag
request._request.POST = request._request.POST.copy() response.set_cookie(key='access_token',
for key, value in request_data.items(): value=serializer.data.get('access_token'),
request._request.POST[key] = value secure=True)
response.set_cookie(key='refresh_token',
url, headers, body, oauth2_status = self.create_token_response(request._request) value=serializer.data.get('refresh_token'),
response = Response(data=json.loads(body), status=oauth2_status) secure=True)
for k, v in headers.items():
response[k] = v
return response return response
# Logout
# class LogoutView(generics.GenericAPIView):
# """Logout user"""
# permission_classes = (permissions.IsAuthenticated,)
#
# def post(self, request, *args, **kwargs):
# """POST method"""
# current_datetime = timezone.now()
# token = request.headers.get('Authorization').split(' ')[::-1][0]
# access_token = jwt_tokens.AccessToken(token)
# access_token.lifetime = timezone.timedelta(seconds=1)
# return Response(status=status.HTTP_200_OK)
# Utils
class TokenView(CsrfExemptMixin, OAuthLibMixin, GenericAPIView):
"""
Implements an endpoint to provide access tokens
The endpoint is used in the following flows:
* Authorization code
* Password
* Client credentials
"""
permission_classes = (permissions.AllowAny,)
def post(self, request, *args, **kwargs):
# 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, oauth2_status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=oauth2_status)
for k, v in headers.items():
response[k] = v
return response

View File

@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
import os import os
import sys import sys
from datetime import timedelta
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -67,7 +68,8 @@ EXTERNAL_APPS = [
'oauth2_provider', 'oauth2_provider',
'social_django', 'social_django',
'rest_framework_social_oauth2', 'rest_framework_social_oauth2',
'django_extensions' 'django_extensions',
'rest_framework_simplejwt.token_blacklist'
] ]
@ -197,10 +199,8 @@ REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
# OAuth # JWT
'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework_social_oauth2.authentication.SocialAuthentication',
), ),
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
'DEFAULT_VERSION': (AVAILABLE_VERSIONS['current'],), 'DEFAULT_VERSION': (AVAILABLE_VERSIONS['current'],),
@ -236,8 +236,8 @@ AUTHENTICATION_BACKENDS = (
# } # }
# Override default OAuth2 namespace # Override default OAuth2 namespace
DRFSO2_URL_NAMESPACE = 'oauth2' DRFSO2_URL_NAMESPACE = 'auth'
SOCIAL_AUTH_URL_NAMESPACE = 'oauth2' SOCIAL_AUTH_URL_NAMESPACE = 'auth'
OAUTH2_SOCIAL_AUTH_BACKEND_NAME = 'facebook' OAUTH2_SOCIAL_AUTH_BACKEND_NAME = 'facebook'
OAUTH2_SOCIAL_AUTH_GRANT_TYPE = 'convert_token' OAUTH2_SOCIAL_AUTH_GRANT_TYPE = 'convert_token'
OAUTH2_PROVIDER_APPLICATION_MODEL = 'authorization.Application' OAUTH2_PROVIDER_APPLICATION_MODEL = 'authorization.Application'
@ -328,3 +328,29 @@ RESETTING_TOKEN_EXPIRATION = 24 # hours
# CORS Config # CORS Config
CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = False CORS_ALLOW_CREDENTIALS = False
# JWT
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken', ),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

View File

@ -51,8 +51,8 @@ urlpatterns_doc = [
] ]
urlpatterns_social = [ urlpatterns_auth = [
path('api/oauth2/', include('authorization.urls.common')), path('api/auth/', include('authorization.urls.common')),
] ]
urlpatterns = [ urlpatterns = [
@ -61,7 +61,7 @@ urlpatterns = [
] ]
urlpatterns = urlpatterns + \ urlpatterns = urlpatterns + \
urlpatterns_social + \ urlpatterns_auth + \
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -24,4 +24,7 @@ django-rest-framework-social-oauth2==1.1.0
django-extensions==2.2.1 django-extensions==2.2.1
# CORS # CORS
django-cors-headers==3.0.2 django-cors-headers==3.0.2
# JWT
djangorestframework_simplejwt==4.3.0