mirror of
https://github.com/Tivix/django-rest-auth.git
synced 2024-11-22 00:56:34 +03:00
Refactor social connect views and serializers
# 347
This commit is contained in:
parent
41ae498be0
commit
fed6b9840c
|
@ -1,5 +1,4 @@
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
@ -9,6 +8,9 @@ try:
|
||||||
get_username_max_length)
|
get_username_max_length)
|
||||||
from allauth.account.adapter import get_adapter
|
from allauth.account.adapter import get_adapter
|
||||||
from allauth.account.utils import setup_user_email
|
from allauth.account.utils import setup_user_email
|
||||||
|
from allauth.socialaccount.helpers import complete_social_login
|
||||||
|
from allauth.socialaccount.models import SocialAccount
|
||||||
|
from allauth.socialaccount.providers.base import AuthProcess
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError("allauth needs to be added to INSTALLED_APPS.")
|
raise ImportError("allauth needs to be added to INSTALLED_APPS.")
|
||||||
|
|
||||||
|
@ -16,6 +18,21 @@ from rest_framework import serializers
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
|
||||||
|
|
||||||
|
class SocialAccountSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
serialize allauth SocialAccounts for use with a REST API
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = SocialAccount
|
||||||
|
fields = (
|
||||||
|
'id',
|
||||||
|
'provider',
|
||||||
|
'uid',
|
||||||
|
'last_login',
|
||||||
|
'date_joined',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SocialLoginSerializer(serializers.Serializer):
|
class SocialLoginSerializer(serializers.Serializer):
|
||||||
access_token = serializers.CharField(required=False, allow_blank=True)
|
access_token = serializers.CharField(required=False, allow_blank=True)
|
||||||
code = serializers.CharField(required=False, allow_blank=True)
|
code = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
@ -105,7 +122,7 @@ class SocialLoginSerializer(serializers.Serializer):
|
||||||
login = self.get_social_login(adapter, app, social_token, access_token)
|
login = self.get_social_login(adapter, app, social_token, access_token)
|
||||||
complete_social_login(request, login)
|
complete_social_login(request, login)
|
||||||
except HTTPError:
|
except HTTPError:
|
||||||
raise serializers.ValidationError(_('Incorrect value'))
|
raise serializers.ValidationError(_("Incorrect value"))
|
||||||
|
|
||||||
if not login.is_existing:
|
if not login.is_existing:
|
||||||
# We have an account already signed up in a different flow
|
# We have an account already signed up in a different flow
|
||||||
|
@ -130,6 +147,22 @@ class SocialLoginSerializer(serializers.Serializer):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class SocialConnectMixin(object):
|
||||||
|
def get_social_login(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Set the social login process state to connect rather than login
|
||||||
|
Refer to the implementation of get_social_login in base class and to the
|
||||||
|
allauth.socialaccount.helpers module complete_social_login function.
|
||||||
|
"""
|
||||||
|
social_login = super(SocialConnectMixin, self).get_social_login(*args, **kwargs)
|
||||||
|
social_login.state['process'] = AuthProcess.CONNECT
|
||||||
|
return social_login
|
||||||
|
|
||||||
|
|
||||||
|
class SocialConnectSerializer(SocialConnectMixin, SocialLoginSerializer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RegisterSerializer(serializers.Serializer):
|
class RegisterSerializer(serializers.Serializer):
|
||||||
username = serializers.CharField(
|
username = serializers.CharField(
|
||||||
max_length=get_username_max_length(),
|
max_length=get_username_max_length(),
|
||||||
|
@ -182,44 +215,3 @@ class RegisterSerializer(serializers.Serializer):
|
||||||
|
|
||||||
class VerifyEmailSerializer(serializers.Serializer):
|
class VerifyEmailSerializer(serializers.Serializer):
|
||||||
key = serializers.CharField()
|
key = serializers.CharField()
|
||||||
|
|
||||||
|
|
||||||
# Import is needed only if we are using social login, in which
|
|
||||||
# case the allauth.socialaccount will be declared
|
|
||||||
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
|
||||||
from allauth.socialaccount.helpers import complete_social_login
|
|
||||||
from allauth.socialaccount.models import SocialAccount
|
|
||||||
from allauth.socialaccount.providers.base import AuthProcess
|
|
||||||
|
|
||||||
class SocialAccountSerializer(serializers.ModelSerializer):
|
|
||||||
"""
|
|
||||||
serialize allauth SocialAccounts for use with a REST API
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
model = SocialAccount
|
|
||||||
fields = (
|
|
||||||
'id',
|
|
||||||
'provider',
|
|
||||||
'uid',
|
|
||||||
'last_login',
|
|
||||||
'date_joined',
|
|
||||||
'extra_data',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SocialConnectMixin(object):
|
|
||||||
def get_social_login(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
set the social login process state to connect rather than login
|
|
||||||
|
|
||||||
Refer to the implementation of get_social_login in base class and to the
|
|
||||||
allauth.socialaccount.helpers module complete_social_login function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
social_login = super(SocialConnectMixin, self).get_social_login(*args, **kwargs)
|
|
||||||
social_login.state['process'] = AuthProcess.CONNECT
|
|
||||||
return social_login
|
|
||||||
|
|
||||||
|
|
||||||
class SocialConnectSerializer(SocialConnectMixin, SocialLoginSerializer):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -7,21 +7,25 @@ from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import (AllowAny,
|
from rest_framework.permissions import (AllowAny,
|
||||||
IsAuthenticated)
|
IsAuthenticated)
|
||||||
from rest_framework.decorators import detail_route
|
from rest_framework.generics import CreateAPIView, ListAPIView, GenericAPIView
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.exceptions import NotFound
|
||||||
from rest_framework.generics import CreateAPIView
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
from allauth.account.adapter import get_adapter
|
||||||
from allauth.account.views import ConfirmEmailView
|
from allauth.account.views import ConfirmEmailView
|
||||||
from allauth.account.utils import complete_signup
|
from allauth.account.utils import complete_signup
|
||||||
from allauth.account import app_settings as allauth_settings
|
from allauth.account import app_settings as allauth_settings
|
||||||
|
from allauth.socialaccount import signals
|
||||||
|
from allauth.socialaccount.adapter import get_adapter as get_social_adapter
|
||||||
|
from allauth.socialaccount.models import SocialAccount
|
||||||
|
|
||||||
from rest_auth.app_settings import (TokenSerializer,
|
from rest_auth.app_settings import (TokenSerializer,
|
||||||
JWTSerializer,
|
JWTSerializer,
|
||||||
create_token)
|
create_token)
|
||||||
from rest_auth.models import TokenModel
|
from rest_auth.models import TokenModel
|
||||||
from rest_auth.registration.serializers import (SocialLoginSerializer,
|
from rest_auth.registration.serializers import (VerifyEmailSerializer,
|
||||||
VerifyEmailSerializer,
|
SocialLoginSerializer,
|
||||||
|
SocialAccountSerializer,
|
||||||
SocialConnectSerializer)
|
SocialConnectSerializer)
|
||||||
from rest_auth.utils import jwt_encode
|
from rest_auth.utils import jwt_encode
|
||||||
from rest_auth.views import LoginView
|
from rest_auth.views import LoginView
|
||||||
|
@ -94,98 +98,89 @@ class VerifyEmailView(APIView, ConfirmEmailView):
|
||||||
return Response({'detail': _('ok')}, status=status.HTTP_200_OK)
|
return Response({'detail': _('ok')}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
class SocialLoginView(LoginView):
|
||||||
from allauth.socialaccount import signals
|
"""
|
||||||
from allauth.socialaccount.models import SocialAccount
|
class used for social authentications
|
||||||
from allauth.socialaccount.adapter import get_adapter
|
example usage for facebook with access_token
|
||||||
|
-------------
|
||||||
|
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||||
|
|
||||||
from rest_auth.registration.serializers import SocialAccountSerializer
|
class FacebookLogin(SocialLoginView):
|
||||||
|
adapter_class = FacebookOAuth2Adapter
|
||||||
|
-------------
|
||||||
|
|
||||||
|
example usage for facebook with code
|
||||||
|
|
||||||
|
-------------
|
||||||
|
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||||
|
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
|
||||||
|
|
||||||
|
class FacebookLogin(SocialLoginView):
|
||||||
|
adapter_class = FacebookOAuth2Adapter
|
||||||
|
client_class = OAuth2Client
|
||||||
|
callback_url = 'localhost:8000'
|
||||||
|
-------------
|
||||||
|
"""
|
||||||
|
serializer_class = SocialLoginSerializer
|
||||||
|
|
||||||
|
def process_login(self):
|
||||||
|
get_adapter(self.request).login(self.request, self.user)
|
||||||
|
|
||||||
|
|
||||||
class SocialLoginView(LoginView):
|
class SocialConnectView(LoginView):
|
||||||
"""
|
"""
|
||||||
class used for social authentications
|
class used for social account linking
|
||||||
example usage for facebook with access_token
|
|
||||||
-------------
|
|
||||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
|
||||||
|
|
||||||
class FacebookLogin(SocialLoginView):
|
example usage for facebook with access_token
|
||||||
adapter_class = FacebookOAuth2Adapter
|
-------------
|
||||||
-------------
|
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||||
|
|
||||||
example usage for facebook with code
|
class FacebookConnect(SocialConnectView):
|
||||||
|
adapter_class = FacebookOAuth2Adapter
|
||||||
|
-------------
|
||||||
|
"""
|
||||||
|
serializer_class = SocialConnectSerializer
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
-------------
|
def process_login(self):
|
||||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
get_adapter(self.request).login(self.request, self.user)
|
||||||
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
|
|
||||||
|
|
||||||
class FacebookLogin(SocialLoginView):
|
|
||||||
adapter_class = FacebookOAuth2Adapter
|
|
||||||
client_class = OAuth2Client
|
|
||||||
callback_url = 'localhost:8000'
|
|
||||||
-------------
|
|
||||||
"""
|
|
||||||
|
|
||||||
serializer_class = SocialLoginSerializer
|
|
||||||
|
|
||||||
def process_login(self):
|
|
||||||
get_adapter(self.request).login(self.request, self.user)
|
|
||||||
|
|
||||||
|
|
||||||
class SocialConnectView(SocialLoginView):
|
class SocialAccountListView(ListAPIView):
|
||||||
"""
|
"""
|
||||||
class used for social account linking
|
List SocialAccounts for the currently logged in user
|
||||||
|
"""
|
||||||
|
serializer_class = SocialAccountSerializer
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
example usage for facebook with access_token
|
def get_queryset(self):
|
||||||
-------------
|
return SocialAccount.objects.filter(user=self.request.user)
|
||||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
|
||||||
|
|
||||||
class FacebookConnect(SocialConnectView):
|
|
||||||
adapter_class = FacebookOAuth2Adapter
|
|
||||||
-------------
|
|
||||||
"""
|
|
||||||
|
|
||||||
serializer_class = SocialConnectSerializer
|
|
||||||
permission_classes = (IsAuthenticated,)
|
|
||||||
|
|
||||||
|
|
||||||
class SocialAccountViewSet(GenericViewSet):
|
class SocialAccountDisconnectView(GenericAPIView):
|
||||||
"""
|
"""
|
||||||
allauth SocialAccount REST API read and disconnect views for logged in users
|
Disconnect SocialAccount from remote service for
|
||||||
|
the currently logged in user
|
||||||
|
"""
|
||||||
|
serializer_class = SocialConnectSerializer
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
Refer to the django-allauth package implementation of the models and
|
def get_queryset(self):
|
||||||
account handling logic for more details, this viewset emulates the allauth web UI.
|
return SocialAccount.objects.filter(user=self.request.user)
|
||||||
"""
|
|
||||||
|
|
||||||
serializer_class = SocialAccountSerializer
|
def post(self, request, *args, **kwargs):
|
||||||
permission_classes = (IsAuthenticated,)
|
accounts = self.get_queryset()
|
||||||
queryset = SocialAccount.objects.none()
|
account = accounts.filter(pk=kwargs['pk']).first()
|
||||||
|
if not account:
|
||||||
|
raise NotFound
|
||||||
|
|
||||||
def get_queryset(self):
|
get_social_adapter(self.request).validate_disconnect(account, accounts)
|
||||||
return SocialAccount.objects.filter(user=self.request.user)
|
|
||||||
|
|
||||||
def list(self, request):
|
account.delete()
|
||||||
"""
|
signals.social_account_removed.send(
|
||||||
list SocialAccounts for the currently logged in user
|
sender=SocialAccount,
|
||||||
"""
|
request=self.request,
|
||||||
|
socialaccount=account
|
||||||
|
)
|
||||||
|
|
||||||
return Response(self.get_serializer(self.get_queryset(), many=True).data)
|
return Response(self.get_serializer(account).data)
|
||||||
|
|
||||||
@detail_route(methods=['POST'])
|
|
||||||
def disconnect(self, request, pk):
|
|
||||||
"""
|
|
||||||
disconnect SocialAccount from remote service for the currently logged in user
|
|
||||||
"""
|
|
||||||
|
|
||||||
accounts = self.get_queryset()
|
|
||||||
account = accounts.get(pk=pk)
|
|
||||||
get_adapter(self.request).validate_disconnect(account, accounts)
|
|
||||||
|
|
||||||
account.delete()
|
|
||||||
signals.social_account_removed.send(
|
|
||||||
sender=SocialAccount,
|
|
||||||
request=self.request,
|
|
||||||
socialaccount=account
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response(self.get_serializer(account).data)
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
# Import is needed only if we are using social login, in which
|
# Import is needed only if we are using social login, in which
|
||||||
# case the allauth.socialaccount will be declared
|
# case the allauth.socialaccount will be declared
|
||||||
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
||||||
|
@ -12,71 +11,71 @@ if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
||||||
from rest_auth.registration.serializers import SocialConnectMixin
|
from rest_auth.registration.serializers import SocialConnectMixin
|
||||||
|
|
||||||
|
|
||||||
class TwitterLoginSerializer(serializers.Serializer):
|
class TwitterLoginSerializer(serializers.Serializer):
|
||||||
access_token = serializers.CharField()
|
access_token = serializers.CharField()
|
||||||
token_secret = serializers.CharField()
|
token_secret = serializers.CharField()
|
||||||
|
|
||||||
def _get_request(self):
|
def _get_request(self):
|
||||||
request = self.context.get('request')
|
request = self.context.get('request')
|
||||||
if not isinstance(request, HttpRequest):
|
if not isinstance(request, HttpRequest):
|
||||||
request = request._request
|
request = request._request
|
||||||
return request
|
return request
|
||||||
|
|
||||||
def get_social_login(self, adapter, app, token, response):
|
def get_social_login(self, adapter, app, token, response):
|
||||||
"""
|
"""
|
||||||
:param adapter: allauth.socialaccount Adapter subclass.
|
:param adapter: allauth.socialaccount Adapter subclass.
|
||||||
Usually OAuthAdapter or Auth2Adapter
|
Usually OAuthAdapter or Auth2Adapter
|
||||||
:param app: `allauth.socialaccount.SocialApp` instance
|
:param app: `allauth.socialaccount.SocialApp` instance
|
||||||
:param token: `allauth.socialaccount.SocialToken` instance
|
:param token: `allauth.socialaccount.SocialToken` instance
|
||||||
:param response: Provider's response for OAuth1. Not used in the
|
:param response: Provider's response for OAuth1. Not used in the
|
||||||
:returns: A populated instance of the
|
:returns: A populated instance of the
|
||||||
`allauth.socialaccount.SocialLoginView` instance
|
`allauth.socialaccount.SocialLoginView` instance
|
||||||
"""
|
"""
|
||||||
request = self._get_request()
|
request = self._get_request()
|
||||||
social_login = adapter.complete_login(request, app, token,
|
social_login = adapter.complete_login(request, app, token,
|
||||||
response=response)
|
response=response)
|
||||||
social_login.token = token
|
social_login.token = token
|
||||||
return social_login
|
return social_login
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
view = self.context.get('view')
|
view = self.context.get('view')
|
||||||
request = self._get_request()
|
request = self._get_request()
|
||||||
|
|
||||||
if not view:
|
if not view:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"View is not defined, pass it as a context variable"
|
"View is not defined, pass it as a context variable"
|
||||||
)
|
)
|
||||||
|
|
||||||
adapter_class = getattr(view, 'adapter_class', None)
|
adapter_class = getattr(view, 'adapter_class', None)
|
||||||
if not adapter_class:
|
if not adapter_class:
|
||||||
raise serializers.ValidationError("Define adapter_class in view")
|
raise serializers.ValidationError("Define adapter_class in view")
|
||||||
|
|
||||||
adapter = adapter_class(request)
|
adapter = adapter_class(request)
|
||||||
app = adapter.get_provider().get_app(request)
|
app = adapter.get_provider().get_app(request)
|
||||||
|
|
||||||
access_token = attrs.get('access_token')
|
access_token = attrs.get('access_token')
|
||||||
token_secret = attrs.get('token_secret')
|
token_secret = attrs.get('token_secret')
|
||||||
|
|
||||||
request.session['oauth_api.twitter.com_access_token'] = {
|
request.session['oauth_api.twitter.com_access_token'] = {
|
||||||
'oauth_token': access_token,
|
'oauth_token': access_token,
|
||||||
'oauth_token_secret': token_secret,
|
'oauth_token_secret': token_secret,
|
||||||
}
|
}
|
||||||
token = SocialToken(token=access_token, token_secret=token_secret)
|
token = SocialToken(token=access_token, token_secret=token_secret)
|
||||||
token.app = app
|
token.app = app
|
||||||
|
|
||||||
try:
|
try:
|
||||||
login = self.get_social_login(adapter, app, token, access_token)
|
login = self.get_social_login(adapter, app, token, access_token)
|
||||||
complete_social_login(request, login)
|
complete_social_login(request, login)
|
||||||
except OAuthError as e:
|
except OAuthError as e:
|
||||||
raise serializers.ValidationError(str(e))
|
raise serializers.ValidationError(str(e))
|
||||||
|
|
||||||
if not login.is_existing:
|
if not login.is_existing:
|
||||||
login.lookup()
|
login.lookup()
|
||||||
login.save(request, connect=True)
|
login.save(request, connect=True)
|
||||||
attrs['user'] = login.account.user
|
attrs['user'] = login.account.user
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class TwitterConnectSerializer(SocialConnectMixin, TwitterLoginSerializer):
|
class TwitterConnectSerializer(SocialConnectMixin, TwitterLoginSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in New Issue
Block a user