Refactor social connect views and serializers

# 347
This commit is contained in:
Maxim Kukhtenkov 2018-01-18 21:08:41 -05:00
parent 41ae498be0
commit fed6b9840c
3 changed files with 169 additions and 183 deletions

View File

@ -1,5 +1,4 @@
from django.http import HttpRequest
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model
@ -9,6 +8,9 @@ try:
get_username_max_length)
from allauth.account.adapter import get_adapter
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:
raise ImportError("allauth needs to be added to INSTALLED_APPS.")
@ -16,6 +18,21 @@ from rest_framework import serializers
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):
access_token = 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)
complete_social_login(request, login)
except HTTPError:
raise serializers.ValidationError(_('Incorrect value'))
raise serializers.ValidationError(_("Incorrect value"))
if not login.is_existing:
# We have an account already signed up in a different flow
@ -130,6 +147,22 @@ class SocialLoginSerializer(serializers.Serializer):
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):
username = serializers.CharField(
max_length=get_username_max_length(),
@ -182,44 +215,3 @@ class RegisterSerializer(serializers.Serializer):
class VerifyEmailSerializer(serializers.Serializer):
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

View File

@ -7,21 +7,25 @@ from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import (AllowAny,
IsAuthenticated)
from rest_framework.decorators import detail_route
from rest_framework.viewsets import GenericViewSet
from rest_framework.generics import CreateAPIView
from rest_framework.generics import CreateAPIView, ListAPIView, GenericAPIView
from rest_framework.exceptions import NotFound
from rest_framework import status
from allauth.account.adapter import get_adapter
from allauth.account.views import ConfirmEmailView
from allauth.account.utils import complete_signup
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,
JWTSerializer,
create_token)
from rest_auth.models import TokenModel
from rest_auth.registration.serializers import (SocialLoginSerializer,
VerifyEmailSerializer,
from rest_auth.registration.serializers import (VerifyEmailSerializer,
SocialLoginSerializer,
SocialAccountSerializer,
SocialConnectSerializer)
from rest_auth.utils import jwt_encode
from rest_auth.views import LoginView
@ -94,98 +98,89 @@ class VerifyEmailView(APIView, ConfirmEmailView):
return Response({'detail': _('ok')}, status=status.HTTP_200_OK)
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
from allauth.socialaccount import signals
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.adapter import get_adapter
class SocialLoginView(LoginView):
"""
class used for social authentications
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 used for social authentications
example usage for facebook with access_token
-------------
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
class SocialConnectView(LoginView):
"""
class used for social account linking
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
-------------
example usage for facebook with access_token
-------------
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,)
-------------
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)
def process_login(self):
get_adapter(self.request).login(self.request, self.user)
class SocialConnectView(SocialLoginView):
"""
class used for social account linking
class SocialAccountListView(ListAPIView):
"""
List SocialAccounts for the currently logged in user
"""
serializer_class = SocialAccountSerializer
permission_classes = (IsAuthenticated,)
example usage for facebook with access_token
-------------
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
class FacebookConnect(SocialConnectView):
adapter_class = FacebookOAuth2Adapter
-------------
"""
serializer_class = SocialConnectSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return SocialAccount.objects.filter(user=self.request.user)
class SocialAccountViewSet(GenericViewSet):
"""
allauth SocialAccount REST API read and disconnect views for logged in users
class SocialAccountDisconnectView(GenericAPIView):
"""
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
account handling logic for more details, this viewset emulates the allauth web UI.
"""
def get_queryset(self):
return SocialAccount.objects.filter(user=self.request.user)
serializer_class = SocialAccountSerializer
permission_classes = (IsAuthenticated,)
queryset = SocialAccount.objects.none()
def post(self, request, *args, **kwargs):
accounts = self.get_queryset()
account = accounts.filter(pk=kwargs['pk']).first()
if not account:
raise NotFound
def get_queryset(self):
return SocialAccount.objects.filter(user=self.request.user)
get_social_adapter(self.request).validate_disconnect(account, accounts)
def list(self, request):
"""
list SocialAccounts for the currently logged in user
"""
account.delete()
signals.social_account_removed.send(
sender=SocialAccount,
request=self.request,
socialaccount=account
)
return Response(self.get_serializer(self.get_queryset(), many=True).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)
return Response(self.get_serializer(account).data)

View File

@ -1,7 +1,6 @@
from django.conf import settings
from django.http import HttpRequest
from rest_framework import serializers
# 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:
@ -12,71 +11,71 @@ if 'allauth.socialaccount' in settings.INSTALLED_APPS:
from rest_auth.registration.serializers import SocialConnectMixin
class TwitterLoginSerializer(serializers.Serializer):
access_token = serializers.CharField()
token_secret = serializers.CharField()
class TwitterLoginSerializer(serializers.Serializer):
access_token = serializers.CharField()
token_secret = serializers.CharField()
def _get_request(self):
request = self.context.get('request')
if not isinstance(request, HttpRequest):
request = request._request
return request
def _get_request(self):
request = self.context.get('request')
if not isinstance(request, HttpRequest):
request = request._request
return request
def get_social_login(self, adapter, app, token, response):
"""
:param adapter: allauth.socialaccount Adapter subclass.
Usually OAuthAdapter or Auth2Adapter
:param app: `allauth.socialaccount.SocialApp` instance
:param token: `allauth.socialaccount.SocialToken` instance
:param response: Provider's response for OAuth1. Not used in the
:returns: A populated instance of the
`allauth.socialaccount.SocialLoginView` instance
"""
request = self._get_request()
social_login = adapter.complete_login(request, app, token,
response=response)
social_login.token = token
return social_login
def get_social_login(self, adapter, app, token, response):
"""
:param adapter: allauth.socialaccount Adapter subclass.
Usually OAuthAdapter or Auth2Adapter
:param app: `allauth.socialaccount.SocialApp` instance
:param token: `allauth.socialaccount.SocialToken` instance
:param response: Provider's response for OAuth1. Not used in the
:returns: A populated instance of the
`allauth.socialaccount.SocialLoginView` instance
"""
request = self._get_request()
social_login = adapter.complete_login(request, app, token,
response=response)
social_login.token = token
return social_login
def validate(self, attrs):
view = self.context.get('view')
request = self._get_request()
def validate(self, attrs):
view = self.context.get('view')
request = self._get_request()
if not view:
raise serializers.ValidationError(
"View is not defined, pass it as a context variable"
)
if not view:
raise serializers.ValidationError(
"View is not defined, pass it as a context variable"
)
adapter_class = getattr(view, 'adapter_class', None)
if not adapter_class:
raise serializers.ValidationError("Define adapter_class in view")
adapter_class = getattr(view, 'adapter_class', None)
if not adapter_class:
raise serializers.ValidationError("Define adapter_class in view")
adapter = adapter_class(request)
app = adapter.get_provider().get_app(request)
adapter = adapter_class(request)
app = adapter.get_provider().get_app(request)
access_token = attrs.get('access_token')
token_secret = attrs.get('token_secret')
access_token = attrs.get('access_token')
token_secret = attrs.get('token_secret')
request.session['oauth_api.twitter.com_access_token'] = {
'oauth_token': access_token,
'oauth_token_secret': token_secret,
}
token = SocialToken(token=access_token, token_secret=token_secret)
token.app = app
request.session['oauth_api.twitter.com_access_token'] = {
'oauth_token': access_token,
'oauth_token_secret': token_secret,
}
token = SocialToken(token=access_token, token_secret=token_secret)
token.app = app
try:
login = self.get_social_login(adapter, app, token, access_token)
complete_social_login(request, login)
except OAuthError as e:
raise serializers.ValidationError(str(e))
try:
login = self.get_social_login(adapter, app, token, access_token)
complete_social_login(request, login)
except OAuthError as e:
raise serializers.ValidationError(str(e))
if not login.is_existing:
login.lookup()
login.save(request, connect=True)
attrs['user'] = login.account.user
if not login.is_existing:
login.lookup()
login.save(request, connect=True)
attrs['user'] = login.account.user
return attrs
return attrs
class TwitterConnectSerializer(SocialConnectMixin, TwitterLoginSerializer):
pass
class TwitterConnectSerializer(SocialConnectMixin, TwitterLoginSerializer):
pass