Tivix / django-rest-auth

This app makes it extremely easy to build Django powered SPA's (Single Page App) or Mobile apps exposing all registration and authentication related functionality as CBV's (Class Base View) and REST (JSON)
www.tivix.com
MIT License
2.41k stars 660 forks source link

Support providers connect for Vkontakte and Google #428

Open taime opened 6 years ago

taime commented 6 years ago

Hello! Please help me make social connect for provider Vkontakte. I've already have VKOAuth2Serializer that works fine for login and register. But connect doesnt' work. Here is urls:

url(r'^rest-auth/vk/', views.VkLogin.as_view()),
url(r'^rest-auth/vk/connect/$', views.VkConnect.as_view(), name='vk_connect'),

Views:

    from allauth.socialaccount.providers.oauth2.client import OAuth2Client
    from allauth.socialaccount.providers.vk.views import VKOAuth2Adapter

    class VkLogin(CustomSocialLoginView):
        adapter_class = VKOAuth2Adapter
        serializer_class = VKOAuth2Serializer
        client_class = OAuth2Client
        callback_url = 'http://localhost:3000'

    class VkConnect(SocialConnectView): 
        adapter_class = VKOAuth2Adapter
        #May be here should be something else?

Serializers

from allauth.socialaccount.helpers import complete_social_login
from rest_auth.registration.serializers import SocialLoginSerializer
from django.utils.translation import ugettext_lazy as _
from requests.exceptions import HTTPError
from rest_framework import serializers

class VKOAuth2Serializer(SocialLoginSerializer):
    email = serializers.CharField(required=False, allow_blank=True)
    user_id = serializers.CharField(required=False, allow_blank=True)

    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"))

        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)

        # Case 1: We received the access_token
        if attrs.get('access_token'):
            if not attrs.get('user_id') or not attrs.get('email'):
                raise serializers.ValidationError(_("Incorrect input. email and user_id is required with access_token."))

            access_data = {
                'access_token': attrs.get('access_token'),
                'user_id': attrs.get('user_id'),
                'email': attrs.get('email'),
            }

        # Case 2: We received the authorization code
        elif attrs.get('code'):
            self.callback_url = getattr(view, 'callback_url', None)
            self.client_class = getattr(view, 'client_class', None)

            if not self.callback_url:
                raise serializers.ValidationError(_("Define callback_url in view"))
            if not self.client_class:
                raise serializers.ValidationError(_("Define client_class in view"))

            code = attrs.get('code')

            provider = adapter.get_provider()
            scope = provider.get_scope(request)
            client = self.client_class(
                request,
                app.client_id,
                app.secret,
                adapter.access_token_method,
                adapter.access_token_url,
                self.callback_url,
                scope
            )
            access_data = client.get_access_token(code)
            if attrs.get('email'):
                access_data['email'] = attrs.get('email')
            if not access_data.get('email'):
                raise serializers.ValidationError(_("Incorrect input. Social account must have email, otherwise send it in email field."))
        else:
            raise serializers.ValidationError(_("Incorrect input. access_token or code is required."))

        social_token = adapter.parse_token({'access_token': access_data['access_token']})
        social_token.app = app

        try:
            login = self.get_social_login(adapter, app, social_token, access_data)
            complete_social_login(request, login)
        except HTTPError:
            raise serializers.ValidationError(_('Incorrect value'))

        if not login.is_existing:
            login.lookup()
            login.save(request, connect=True)
        attrs['user'] = login.account.user

        return attrs

Please tell me how to do connect. And also I want to ask, why nobody implement serializers for 3d party providers to library? Why only twitter and facebook? Would you add Vkontakte if I make pull request?

Thank you.

project is here: https://github.com/taime/imetrics

maxim-kht commented 6 years ago

Hi @taime What error does it display on connect?

taime commented 6 years ago
Environment:

Request Method: POST
Request URL: http://instametrics.gyds.ru/rest-auth/vk/connect/

Django Version: 2.0.3
Python Version: 3.5.5
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django_filters',
 'rest_framework',
 'rest_framework.authtoken',
 'rest_auth',
 'django.contrib.sites',
 'allauth',
 'allauth.account',
 'rest_auth.registration',
 'allauth.socialaccount',
 'allauth.socialaccount.providers.facebook',
 'allauth.socialaccount.providers.vk',
 'allauth.socialaccount.providers.twitter',
 'storages',
 'core',
 'api']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']

Traceback:

File "/usr/local/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
  35.             response = get_response(request)

File "/usr/local/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  128.                 response = self.process_exception_by_middleware(e, request)

File "/usr/local/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  126.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/usr/local/lib/python3.5/site-packages/django/views/decorators/csrf.py" in wrapped_view
  54.         return view_func(*args, **kwargs)

File "/usr/local/lib/python3.5/site-packages/django/views/generic/base.py" in view
  69.             return self.dispatch(request, *args, **kwargs)

File "/usr/local/lib/python3.5/site-packages/django/utils/decorators.py" in _wrapper
  62.             return bound_func(*args, **kwargs)

File "/usr/local/lib/python3.5/site-packages/django/views/decorators/debug.py" in sensitive_post_parameters_wrapper
  76.             return view(request, *args, **kwargs)

File "/usr/local/lib/python3.5/site-packages/django/utils/decorators.py" in bound_func
  58.                 return func.__get__(self, type(self))(*args2, **kwargs2)

File "/usr/local/lib/python3.5/site-packages/rest_auth/views.py" in dispatch
  49.         return super(LoginView, self).dispatch(*args, **kwargs)

File "/usr/local/lib/python3.5/site-packages/rest_framework/views.py" in dispatch
  494.             response = self.handle_exception(exc)

File "/usr/local/lib/python3.5/site-packages/rest_framework/views.py" in handle_exception
  454.             self.raise_uncaught_exception(exc)

File "/usr/local/lib/python3.5/site-packages/rest_framework/views.py" in dispatch
  491.             response = handler(request, *args, **kwargs)

File "/usr/local/lib/python3.5/site-packages/rest_auth/views.py" in post
  93.         self.serializer.is_valid(raise_exception=True)

File "/usr/local/lib/python3.5/site-packages/rest_framework/serializers.py" in is_valid
  236.                 self._validated_data = self.run_validation(self.initial_data)

File "/usr/local/lib/python3.5/site-packages/rest_framework/serializers.py" in run_validation
  438.             value = self.validate(value)

File "/code/core/serializers.py" in validate
  72.             login = self.get_social_login(adapter, app, social_token, access_data)

File "/usr/local/lib/python3.5/site-packages/rest_auth/registration/serializers.py" in get_social_login
  58.         social_login = adapter.complete_login(request, app, token, response=response)

File "/code/allauth/socialaccount/providers/vk/views.py" in complete_login
  54.         extra_data = resp.json()['response'][0]

Exception Type: KeyError at /rest-auth/vk/connect/
Exception Value: 'response'

And here is the project: https://github.com/taime/imetrics

maxim-kht commented 6 years ago

Added answer on Stackoverflow

Since VK requires custom provider logic, you need to use serializer with logic defined in VKOAuth2Serializer but with social login state process being set to connect. You can achieve it by creating a subclass of VKOAuth2Serializer with SocialConnectMixin.

So your new serializer and connect view would look the following way:

Serializer:

from rest_auth.registration.serializers import SocialConnectMixin

class VKOAuth2ConnectSerializer(SocialConnectMixin, VKOAuth2Serializer):
    pass

View:

class VkConnect(SocialConnectView): 
    adapter_class = VKOAuth2Adapter
    serializer_class = VKOAuth2ConnectSerializer
taime commented 6 years ago

As I noticed on Stackoverflow: this doesn't work as connection. This works like registration/login.