diff --git a/docs/installation.rst b/docs/installation.rst index b85cc17..8a2d3b6 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -111,15 +111,11 @@ Facebook .. code-block:: python from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter - from rest_auth.registration.views import SocialLoginView, SocialConnectView + from rest_auth.registration.views import SocialLoginView class FacebookLogin(SocialLoginView): adapter_class = FacebookOAuth2Adapter - # Add a connect view if you want to allow connecting existing accounts - class FacebookConnect(SocialConnectView): - adapter_class = FacebookOAuth2Adapter - 4. Create url for FacebookLogin view: .. code-block:: python @@ -127,7 +123,6 @@ Facebook urlpatterns += [ ..., url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login') - url(r'^rest-auth/facebook/connect/$', FacebookConnect.as_view(), name='fb_connect') ] @@ -142,18 +137,12 @@ If you are using Twitter for your social authentication, it is a bit different s from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter from rest_auth.registration.views import SocialLoginView - from rest_auth.social_serializers import TwitterLoginSerializer, TwitterConnectSerializer + from rest_auth.social_serializers import TwitterLoginSerializer class TwitterLogin(SocialLoginView): serializer_class = TwitterLoginSerializer adapter_class = TwitterOAuthAdapter - # Add a connect view if you want to allow connecting existing accounts - class TwitterConnect(SocialConnectView): - serializer_class = TwitterConnectSerializer - adapter_class = TwitterOAuthAdapter - - 4. Create url for TwitterLogin view: .. code-block:: python @@ -168,7 +157,32 @@ If you are using Twitter for your social authentication, it is a bit different s Additional Social Connect Views ############################### -If you are using social connect views, you can also use additional views to check all social accounts attached to the current authenticated user and disconnect selected social accounts. +If you want to allow connecting existing accounts in addition to just login, you can use connect views: + +.. code-block:: python + + from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter + from rest_auth.registration.views import SocialConnectView + from rest_auth.social_serializers import TwitterConnectSerializer + + class FacebookConnect(SocialConnectView): + adapter_class = FacebookOAuth2Adapter + + class TwitterConnect(SocialConnectView): + serializer_class = TwitterConnectSerializer + adapter_class = TwitterOAuthAdapter + +In urls.py: + +.. code-block:: python + + urlpatterns += [ + ..., + url(r'^rest-auth/facebook/connect/$', FacebookConnect.as_view(), name='fb_connect') + url(r'^rest-auth/twitter/connect/$', TwitterConnect.as_view(), name='twitter_connect') + ] + +You can also use additional views to check all social accounts attached to the current authenticated user and disconnect selected social accounts. .. code-block:: python diff --git a/rest_auth/tests/mixins.py b/rest_auth/tests/mixins.py index 220a28d..30b3d58 100644 --- a/rest_auth/tests/mixins.py +++ b/rest_auth/tests/mixins.py @@ -90,6 +90,9 @@ class TestsMixin(object): self.tw_login_url = reverse('tw_login') self.tw_login_no_view_url = reverse('tw_login_no_view') self.tw_login_no_adapter_url = reverse('tw_login_no_adapter') + self.fb_connect_url = reverse('fb_connect') + self.tw_connect_url = reverse('tw_connect') + self.social_account_list_url = reverse('social_account_list') def _login(self): payload = { diff --git a/rest_auth/tests/test_social.py b/rest_auth/tests/test_social.py index e6eca03..6e7a495 100644 --- a/rest_auth/tests/test_social.py +++ b/rest_auth/tests/test_social.py @@ -5,6 +5,11 @@ from django.contrib.auth import get_user_model from django.test.utils import override_settings from django.contrib.sites.models import Site +try: + from django.urls import reverse +except ImportError: + from django.core.urlresolvers import reverse + from allauth.socialaccount.models import SocialApp from allauth.socialaccount.providers.facebook.provider import GRAPH_API_URL import responses @@ -303,3 +308,147 @@ class TestSocialAuth(TestsMixin, TestCase): self.assertIn('user', self.response.json.keys()) self.assertEqual(get_user_model().objects.all().count(), users_count + 1) + + +@override_settings(ROOT_URLCONF="tests.urls") +class TestSocialConnectAuth(TestsMixin, TestCase): + + USERNAME = 'person' + PASS = 'person' + EMAIL = "person1@world.com" + REGISTRATION_DATA = { + "username": USERNAME, + "password1": PASS, + "password2": PASS, + "email": EMAIL + } + LOGIN_DATA = { + "username": USERNAME, + "password": PASS, + } + + def setUp(self): + self.init() + + facebook_social_app = SocialApp.objects.create( + provider='facebook', + name='Facebook', + client_id='123123123', + secret='321321321', + ) + + twitter_social_app = SocialApp.objects.create( + provider='twitter', + name='Twitter', + client_id='11223344', + secret='55667788', + ) + + site = Site.objects.get_current() + facebook_social_app.sites.add(site) + twitter_social_app.sites.add(site) + self.graph_api_url = GRAPH_API_URL + '/me' + self.twitter_url = 'https://api.twitter.com/1.1/account/verify_credentials.json' + + @responses.activate + def test_social_connect_no_auth(self): + responses.add( + responses.GET, + self.graph_api_url, + body='', + status=200, + content_type='application/json' + ) + + payload = { + 'access_token': 'abc123' + } + self.post(self.fb_connect_url, data=payload, status_code=403) + self.post(self.tw_connect_url, data=payload, status_code=403) + + @responses.activate + def test_social_connect(self): + # register user + self.post( + self.register_url, + data=self.REGISTRATION_DATA, + status_code=201 + ) + + # Test Facebook + resp_body = { + "id": "123123123123", + "first_name": "John", + "gender": "male", + "last_name": "Smith", + "link": "https://www.facebook.com/john.smith", + "locale": "en_US", + "name": "John Smith", + "timezone": 2, + "updated_time": "2014-08-13T10:14:38+0000", + "username": "john.smith", + "verified": True + } + + responses.add( + responses.GET, + self.graph_api_url, + body=json.dumps(resp_body), + status=200, + content_type='application/json' + ) + + payload = { + 'access_token': 'abc123' + } + self.post(self.fb_connect_url, data=payload, status_code=200) + self.assertIn('key', self.response.json.keys()) + + # Test Twitter + self.post(self.logout_url) + self.post(self.login_url, data=self.LOGIN_DATA) + + resp_body = { + "id": "123123123123", + } + + responses.add( + responses.GET, + self.twitter_url, + body=json.dumps(resp_body), + status=200, + content_type='application/json' + ) + + payload = { + 'access_token': 'abc123', + 'token_secret': '1111222233334444' + } + + self.post(self.tw_connect_url, data=payload) + + self.assertIn('key', self.response.json.keys()) + + # Check current social accounts + self.get(self.social_account_list_url) + self.assertEqual(len(self.response.json), 2) + self.assertEqual(self.response.json[0]['provider'], 'facebook') + self.assertEqual(self.response.json[1]['provider'], 'twitter') + + facebook_social_account_id = self.response.json[0]['id'] + + # Try disconnecting accounts + self.incorrect_disconnect_url = reverse( + 'social_account_disconnect', args=[999] + ) + self.post(self.incorrect_disconnect_url, status_code=404) + + self.disconnect_url = reverse( + 'social_account_disconnect', args=[facebook_social_account_id] + ) + self.post(self.disconnect_url, status_code=200) + + # Check social accounts after disconnecting + self.get(self.social_account_list_url) + self.assertEqual(len(self.response.json), 1) + self.assertEqual(self.response.json[0]['provider'], 'twitter') diff --git a/rest_auth/tests/urls.py b/rest_auth/tests/urls.py index 6371218..401f23a 100644 --- a/rest_auth/tests/urls.py +++ b/rest_auth/tests/urls.py @@ -8,8 +8,13 @@ from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter from rest_framework.decorators import api_view from rest_auth.urls import urlpatterns -from rest_auth.registration.views import SocialLoginView -from rest_auth.social_serializers import TwitterLoginSerializer +from rest_auth.registration.views import ( + SocialLoginView, SocialConnectView, SocialAccountListView, + SocialAccountDisconnectView +) +from rest_auth.social_serializers import ( + TwitterLoginSerializer, TwitterConnectSerializer +) class FacebookLogin(SocialLoginView): @@ -21,6 +26,15 @@ class TwitterLogin(SocialLoginView): serializer_class = TwitterLoginSerializer +class FacebookConnect(SocialConnectView): + adapter_class = FacebookOAuth2Adapter + + +class TwitterConnect(SocialConnectView): + adapter_class = TwitterOAuthAdapter + serializer_class = TwitterConnectSerializer + + class TwitterLoginSerializerFoo(TwitterLoginSerializer): pass @@ -49,5 +63,10 @@ urlpatterns += [ url(r'^social-login/twitter/$', TwitterLogin.as_view(), name='tw_login'), url(r'^social-login/twitter-no-view/$', twitter_login_view, name='tw_login_no_view'), url(r'^social-login/twitter-no-adapter/$', TwitterLoginNoAdapter.as_view(), name='tw_login_no_adapter'), + url(r'^social-login/facebook/connect/$', FacebookConnect.as_view(), name='fb_connect'), + url(r'^social-login/twitter/connect/$', TwitterConnect.as_view(), name='tw_connect'), + url(r'^socialaccounts/$', SocialAccountListView.as_view(), name='social_account_list'), + url(r'^socialaccounts/(?P\d+)/disconnect/$', SocialAccountDisconnectView.as_view(), + name='social_account_disconnect'), url(r'^accounts/', include('allauth.socialaccount.urls')) ]