version 0.0.5.12: refactored authorization app (added JWT support)
This commit is contained in:
parent
0a35f06ff5
commit
cc04a50709
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user