diff --git a/example_project/config/settings.py b/example_project/config/settings.py index 78f6a68..7fdb22d 100644 --- a/example_project/config/settings.py +++ b/example_project/config/settings.py @@ -124,6 +124,13 @@ # DRF settings +SIMPLE_JWT = { + 'AUTH_TOKEN_CLASSES': ( + 'rest_framework_simplejwt.tokens.AccessToken', + 'rest_framework_simplejwt.tokens.SlidingToken', + ), +} + # social auth settings # valid redirect domain for all apps: http://restsocialexample.com:8000/ SOCIAL_AUTH_FACEBOOK_KEY = '295137440610143' diff --git a/example_project/config/settings_test.py b/example_project/config/settings_test.py index 8753ae6..6738888 100644 --- a/example_project/config/settings_test.py +++ b/example_project/config/settings_test.py @@ -1,5 +1,7 @@ " Settings for tests. " -from .settings import * +INSTALLED_APPS = [] + +from .settings import * # NOQA: E402 # Databases DATABASES = { @@ -8,3 +10,6 @@ 'NAME': ':memory:', } } + +if 'knox' not in INSTALLED_APPS: + INSTALLED_APPS += ('knox', ) diff --git a/tests/base.py b/tests/base.py new file mode 100644 index 0000000..954893e --- /dev/null +++ b/tests/base.py @@ -0,0 +1,60 @@ +import json + +from httpretty import HTTPretty +from social_core.backends.utils import load_backends +from social_core.tests.backends.test_facebook import FacebookOAuth2Test +from social_core.tests.backends.test_twitter import TwitterOAuth1Test +from social_core.utils import module_member + +from rest_social_auth import views + + +# don't run third party tests +for attr in (attr for attr in dir(FacebookOAuth2Test) if attr.startswith('test_')): + delattr(FacebookOAuth2Test, attr) +for attr in (attr for attr in dir(TwitterOAuth1Test) if attr.startswith('test_')): + delattr(TwitterOAuth1Test, attr) + + +class RestSocialMixin(object): + def setUp(self): + HTTPretty.enable() + Backend = module_member(self.backend_path) + self.strategy = views.load_strategy() + self.backend = Backend(self.strategy, redirect_uri=self.complete_url) + self.name = self.backend.name.upper().replace('-', '_') + self.complete_url = self.strategy.build_absolute_uri( + self.raw_complete_url.format(self.backend.name) + ) + backends = (self.backend_path, ) + load_backends(backends, force_load=True) + + user_data_body = json.loads(self.user_data_body) + self.email = 'example@mail.com' + user_data_body['email'] = self.email + self.user_data_body = json.dumps(user_data_body) + + self.do_rest_login() + + def tearDown(self): + HTTPretty.disable() + HTTPretty.reset() + self.backend = None + self.strategy = None + self.name = None + self.complete_url = None + + +class BaseFacebookAPITestCase(RestSocialMixin, FacebookOAuth2Test): + + def do_rest_login(self): + start_url = self.backend.start().url + self.auth_handlers(start_url) + + +class BaseTwitterApiTestCase(RestSocialMixin, TwitterOAuth1Test): + + def do_rest_login(self): + self.request_token_handler() + start_url = self.backend.start().url + self.auth_handlers(start_url) diff --git a/tests/test_jwt.py b/tests/test_jwt.py new file mode 100644 index 0000000..bdfec07 --- /dev/null +++ b/tests/test_jwt.py @@ -0,0 +1,89 @@ +from django.test import modify_settings +from django.urls import reverse +from rest_framework.test import APITestCase +from rest_framework_jwt.settings import api_settings as jwt_api_settings +from social_core.utils import parse_qs + +from .base import BaseFacebookAPITestCase, BaseTwitterApiTestCase + + +jwt_modify_settings = dict( + INSTALLED_APPS={ + 'remove': [ + 'django.contrib.sessions', + 'rest_framework.authtoken', + 'knox', + ] + }, + MIDDLEWARE_CLASSES={ + 'remove': [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + ], + } +) + + +class TestSocialAuth1JWT(APITestCase, BaseTwitterApiTestCase): + + @modify_settings(INSTALLED_APPS={'remove': ['rest_framework.authtoken', ]}) + def test_login_social_oauth1_jwt(self): + """ + Currently oauth1 works only if session is enabled. + Probably it is possible to make it work without session, but + it will be needed to change the logic in python-social-auth. + """ + resp = self.client.post( + reverse('login_social_jwt_user'), data={'provider': 'twitter'}) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data, parse_qs(self.request_token_body)) + resp = self.client.post(reverse('login_social_jwt_user'), data={ + 'provider': 'twitter', + 'oauth_token': 'foobar', + 'oauth_verifier': 'overifier' + }) + self.assertEqual(resp.status_code, 200) + + +class TestSocialAuth2JWT(APITestCase, BaseFacebookAPITestCase): + + @modify_settings(**jwt_modify_settings) + def _check_login_social_jwt_only(self, url, data): + jwt_decode_handler = jwt_api_settings.JWT_DECODE_HANDLER + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + # check token valid + jwt_data = jwt_decode_handler(resp.data['token']) + self.assertEqual(jwt_data['email'], self.email) + + @modify_settings(**jwt_modify_settings) + def _check_login_social_jwt_user(self, url, data): + jwt_decode_handler = jwt_api_settings.JWT_DECODE_HANDLER + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['email'], self.email) + # check token valid + jwt_data = jwt_decode_handler(resp.data['token']) + self.assertEqual(jwt_data['email'], self.email) + + def test_login_social_jwt_only(self): + self._check_login_social_jwt_only( + reverse('login_social_jwt'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_jwt_only_provider_in_url(self): + self._check_login_social_jwt_only( + reverse('login_social_jwt', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_jwt_user(self): + self._check_login_social_jwt_user( + reverse('login_social_jwt_user'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_jwt_user_provider_in_url(self): + self._check_login_social_jwt_user( + reverse('login_social_jwt_user', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + ) diff --git a/tests/test_knox.py b/tests/test_knox.py new file mode 100644 index 0000000..d892955 --- /dev/null +++ b/tests/test_knox.py @@ -0,0 +1,66 @@ +from django.urls import reverse +from knox.auth import TokenAuthentication as KnoxTokenAuthentication +from rest_framework.test import APITestCase +from social_core.utils import parse_qs + +from .base import BaseFacebookAPITestCase, BaseTwitterApiTestCase + + +class TestSocialAuth1Knox(APITestCase, BaseTwitterApiTestCase): + + def test_login_social_oauth1_knox(self): + """ + Currently oauth1 works only if session is enabled. + Probably it is possible to make it work without session, but + it will be needed to change the logic in python-social-auth. + """ + resp = self.client.post( + reverse('login_social_knox_user'), data={'provider': 'twitter'}) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data, parse_qs(self.request_token_body)) + resp = self.client.post(reverse('login_social_knox_user'), data={ + 'provider': 'twitter', + 'oauth_token': 'foobar', + 'oauth_verifier': 'overifier' + }) + self.assertEqual(resp.status_code, 200) + + +class TestSocialAuth2Knox(APITestCase, BaseFacebookAPITestCase): + + def _check_login_social_knox_only(self, url, data): + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + # check token valid + knox_auth = KnoxTokenAuthentication() + user, auth_data = knox_auth.authenticate_credentials(resp.data['token'].encode('utf8')) + self.assertEqual(user.email, self.email) + + def _check_login_social_knox_user(self, url, data): + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['email'], self.email) + # check token valid + knox_auth = KnoxTokenAuthentication() + user, auth_data = knox_auth.authenticate_credentials(resp.data['token'].encode('utf8')) + self.assertEqual(user.email, self.email) + + def test_login_social_knox_only(self): + self._check_login_social_knox_only( + reverse('login_social_knox'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_knox_only_provider_in_url(self): + self._check_login_social_knox_only( + reverse('login_social_knox', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_knox_user(self): + self._check_login_social_knox_user( + reverse('login_social_knox_user'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_knox_user_provider_in_url(self): + self._check_login_social_knox_user( + reverse('login_social_knox_user', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}) diff --git a/tests/test_session.py b/tests/test_session.py new file mode 100644 index 0000000..114bcf0 --- /dev/null +++ b/tests/test_session.py @@ -0,0 +1,134 @@ +try: + from urlparse import parse_qsl, urlparse +except ImportError: + # python 3 + from urllib.parse import parse_qsl, urlparse + +from mock import patch +from django.urls import reverse +from django.contrib.auth import get_user_model +from django.test import modify_settings +from django.test.utils import override_settings +from rest_framework.test import APITestCase +from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly +from httpretty import HTTPretty +from social_core.utils import parse_qs + +from .base import BaseFacebookAPITestCase, BaseTwitterApiTestCase + + +session_modify_settings = dict( + INSTALLED_APPS={ + 'remove': [ + 'rest_framework.authtoken', + 'knox', + ] + }, +) + + +class TestSocialAuth1(APITestCase, BaseTwitterApiTestCase): + + @modify_settings(**session_modify_settings) + def test_login_social_oauth1_session(self): + resp = self.client.post( + reverse('login_social_session'), data={'provider': 'twitter'}) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data, parse_qs(self.request_token_body)) + resp = self.client.post(reverse('login_social_session'), data={ + 'provider': 'twitter', + 'oauth_token': 'foobar', + 'oauth_verifier': 'overifier' + }) + self.assertEqual(resp.status_code, 200) + + +class TestSocialAuth2(APITestCase, BaseFacebookAPITestCase): + + @modify_settings(**session_modify_settings) + def _check_login_social_session(self, url, data): + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['email'], self.email) + # check cookies are set + self.assertTrue('sessionid' in resp.cookies) + # check user is created + self.assertTrue( + get_user_model().objects.filter(email=self.email).exists()) + + def test_login_social_session(self): + self._check_login_social_session( + reverse('login_social_session'), + {'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_session_provider_in_url(self): + self._check_login_social_session( + reverse('login_social_session', kwargs={'provider': 'facebook'}), + {'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_no_provider_session(self): + resp = self.client.post( + reverse('login_social_session'), + {'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + self.assertEqual(resp.status_code, 400) + + def test_unknown_provider_session(self): + resp = self.client.post( + reverse('login_social_session', kwargs={'provider': 'unknown'}), + {'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + self.assertEqual(resp.status_code, 404) + + def test_login_social_http_origin(self): + resp = self.client.post( + reverse('login_social_session'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + HTTP_ORIGIN="http://frontend.com") + self.assertEqual(resp.status_code, 200) + url_params = dict(parse_qsl(urlparse(HTTPretty.latest_requests[0].path).query)) + self.assertEqual(url_params['redirect_uri'], "http://frontend.com/") + + @override_settings(REST_SOCIAL_OAUTH_ABSOLUTE_REDIRECT_URI='http://myproject.com/') + def test_login_absolute_redirect(self): + resp = self.client.post( + reverse('login_social_session'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + self.assertEqual(resp.status_code, 200) + url_params = dict(parse_qsl(urlparse(HTTPretty.latest_requests[0].path).query)) + self.assertEqual('http://myproject.com/', url_params['redirect_uri']) + + @override_settings(REST_SOCIAL_OAUTH_ABSOLUTE_REDIRECT_URI='http://myproject.com/') + def test_login_manual_redirect(self): + resp = self.client.post( + reverse('login_social_session'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw', + 'redirect_uri': 'http://manualdomain.com/'}) + self.assertEqual(resp.status_code, 200) + url_params = dict(parse_qsl(urlparse(HTTPretty.latest_requests[0].path).query)) + self.assertEqual('http://manualdomain.com/', url_params['redirect_uri']) + + @patch('rest_framework.views.APIView.permission_classes') + def test_login_social_session_model_permission(self, m_permission_classes): + setattr(m_permission_classes, '__get__', lambda *args, **kwargs: (DjangoModelPermissionsOrAnonReadOnly, )) + self._check_login_social_session( + reverse('login_social_session'), + {'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + +class TestSocialAuth2Error(APITestCase, BaseFacebookAPITestCase): + access_token_status = 400 + + def test_login_oauth_provider_error(self): + resp = self.client.post( + reverse('login_social_session'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + self.assertEqual(resp.status_code, 400) + + +class TestSocialAuth2HTTPError(APITestCase, BaseFacebookAPITestCase): + access_token_status = 401 + + def test_login_oauth_provider_http_error(self): + resp = self.client.post( + reverse('login_social_session'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + self.assertEqual(resp.status_code, 400) diff --git a/tests/test_simple_jwt.py b/tests/test_simple_jwt.py new file mode 100644 index 0000000..7e44c9c --- /dev/null +++ b/tests/test_simple_jwt.py @@ -0,0 +1,125 @@ +from django.test import modify_settings +from django.urls import reverse +from rest_framework.test import APITestCase +from rest_framework_simplejwt.authentication import JWTAuthentication +from social_core.utils import parse_qs + +from .base import BaseFacebookAPITestCase, BaseTwitterApiTestCase + + +jwt_simple_modify_settings = dict( + INSTALLED_APPS={ + 'remove': [ + 'django.contrib.sessions', + 'rest_framework.authtoken', + 'knox', + ] + }, + MIDDLEWARE_CLASSES={ + 'remove': [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + ], + } +) + + +class TestSocialAuth1SimpleJWT(APITestCase, BaseTwitterApiTestCase): + + @modify_settings(INSTALLED_APPS={'remove': ['rest_framework.authtoken', ]}) + def test_login_social_oauth1_jwt(self): + """ + Currently oauth1 works only if session is enabled. + Probably it is possible to make it work without session, but + it will be needed to change the logic in python-social-auth. + """ + resp = self.client.post( + reverse('login_social_jwt_user'), data={'provider': 'twitter'}) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data, parse_qs(self.request_token_body)) + resp = self.client.post(reverse('login_social_jwt_user'), data={ + 'provider': 'twitter', + 'oauth_token': 'foobar', + 'oauth_verifier': 'overifier' + }) + self.assertEqual(resp.status_code, 200) + + +class TestSocialAuth2SimpleJWT(APITestCase, BaseFacebookAPITestCase): + + @modify_settings(**jwt_simple_modify_settings) + def _check_login_social_simple_jwt_only(self, url, data, token_type): + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + # check token valid + jwt_auth = JWTAuthentication() + token_instance = jwt_auth.get_validated_token(resp.data['token']) + self.assertEqual(token_instance['token_type'], token_type) + + @modify_settings(**jwt_simple_modify_settings) + def _check_login_social_simple_jwt_user(self, url, data, token_type): + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['email'], self.email) + # check token valid + jwt_auth = JWTAuthentication() + token_instance = jwt_auth.get_validated_token(resp.data['token']) + self.assertEqual(token_instance['token_type'], token_type) + self.assertEqual(token_instance['email'], self.email) + + def test_login_social_simple_jwt_pair_only(self): + self._check_login_social_simple_jwt_only( + reverse('login_social_jwt_pair'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + token_type='access', + ) + + def test_login_social_simple_jwt_pair_only_provider_in_url(self): + self._check_login_social_simple_jwt_only( + reverse('login_social_jwt_pair', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + token_type='access', + ) + + def test_login_social_simple_jwt_pair_user(self): + self._check_login_social_simple_jwt_user( + reverse('login_social_jwt_pair_user'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + token_type='access', + ) + + def test_login_social_simple_jwt_pair_user_provider_in_url(self): + self._check_login_social_simple_jwt_user( + reverse('login_social_jwt_pair_user', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + token_type='access', + ) + + def test_login_social_simple_jwt_sliding_only(self): + self._check_login_social_simple_jwt_only( + reverse('login_social_jwt_sliding'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + token_type='sliding', + ) + + def test_login_social_simple_jwt_sliding_only_provider_in_url(self): + self._check_login_social_simple_jwt_only( + reverse('login_social_jwt_sliding', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + token_type='sliding', + ) + + def test_login_social_simple_jwt_sliding_user(self): + self._check_login_social_simple_jwt_user( + reverse('login_social_jwt_sliding_user'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + token_type='sliding', + ) + + def test_login_social_simple_jwt_sliding_user_provider_in_url(self): + self._check_login_social_simple_jwt_user( + reverse('login_social_jwt_sliding_user', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, + token_type='sliding', + ) diff --git a/tests/test_social.py b/tests/test_social.py deleted file mode 100644 index 357aa20..0000000 --- a/tests/test_social.py +++ /dev/null @@ -1,528 +0,0 @@ -import json -try: - from urlparse import parse_qsl, urlparse -except ImportError: - # python 3 - from urllib.parse import parse_qsl, urlparse - -from mock import patch -try: - from django.urls import reverse -except ImportError: - from django.core.urlresolvers import reverse # Django < 2.x -from django.contrib.auth import get_user_model -from django.test import modify_settings -from django.test.utils import override_settings -from rest_framework.test import APITestCase -from rest_framework.authtoken.models import Token -from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly -from httpretty import HTTPretty -from social_core.utils import module_member, parse_qs -from social_core.backends.utils import load_backends -from social_core.tests.backends.test_facebook import FacebookOAuth2Test -from social_core.tests.backends.test_twitter import TwitterOAuth1Test - -from rest_social_auth import views - - -# don't run third party tests -for attr in (attr for attr in dir(FacebookOAuth2Test) if attr.startswith('test_')): - delattr(FacebookOAuth2Test, attr) -for attr in (attr for attr in dir(TwitterOAuth1Test) if attr.startswith('test_')): - delattr(TwitterOAuth1Test, attr) - - -session_modify_settings = dict( - INSTALLED_APPS={ - 'remove': [ - 'rest_framework.authtoken', - ] - }, -) - - -token_modify_settings = dict( - INSTALLED_APPS={ - 'remove': [ - 'django.contrib.sessions' - ] - }, - MIDDLEWARE_CLASSES={ - 'remove': [ - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - ], - } -) - - -jwt_modify_settings = dict( - INSTALLED_APPS={ - 'remove': [ - 'django.contrib.sessions', - 'rest_framework.authtoken', - ] - }, - MIDDLEWARE_CLASSES={ - 'remove': [ - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - ], - } -) - - -class RestSocialMixin(object): - def setUp(self): - HTTPretty.enable() - Backend = module_member(self.backend_path) - self.strategy = views.load_strategy() - self.backend = Backend(self.strategy, redirect_uri=self.complete_url) - self.name = self.backend.name.upper().replace('-', '_') - self.complete_url = self.strategy.build_absolute_uri( - self.raw_complete_url.format(self.backend.name) - ) - backends = (self.backend_path, ) - load_backends(backends, force_load=True) - - user_data_body = json.loads(self.user_data_body) - self.email = 'example@mail.com' - user_data_body['email'] = self.email - self.user_data_body = json.dumps(user_data_body) - - self.do_rest_login() - - def tearDown(self): - HTTPretty.disable() - HTTPretty.reset() - self.backend = None - self.strategy = None - self.name = None - self.complete_url = None - - -class BaseFacebookAPITestCase(RestSocialMixin, FacebookOAuth2Test): - - def do_rest_login(self): - start_url = self.backend.start().url - self.auth_handlers(start_url) - - -class BaseTwitterApiTestCase(RestSocialMixin, TwitterOAuth1Test): - - def do_rest_login(self): - self.request_token_handler() - start_url = self.backend.start().url - self.auth_handlers(start_url) - - -class TestSocialAuth1(APITestCase, BaseTwitterApiTestCase): - - @modify_settings(**session_modify_settings) - def test_login_social_oauth1_session(self): - resp = self.client.post( - reverse('login_social_session'), data={'provider': 'twitter'}) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data, parse_qs(self.request_token_body)) - resp = self.client.post(reverse('login_social_session'), data={ - 'provider': 'twitter', - 'oauth_token': 'foobar', - 'oauth_verifier': 'overifier' - }) - self.assertEqual(resp.status_code, 200) - - def test_login_social_oauth1_token(self): - """ - Currently oauth1 works only if session is enabled. - Probably it is possible to make it work without session, but - it will be needed to change the logic in python-social-auth. - """ - resp = self.client.post( - reverse('login_social_token_user'), data={'provider': 'twitter'}) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data, parse_qs(self.request_token_body)) - resp = self.client.post(reverse('login_social_token_user'), data={ - 'provider': 'twitter', - 'oauth_token': 'foobar', - 'oauth_verifier': 'overifier' - }) - self.assertEqual(resp.status_code, 200) - - @modify_settings(INSTALLED_APPS={'remove': ['rest_framework.authtoken', ]}) - def test_login_social_oauth1_jwt(self): - """ - Currently oauth1 works only if session is enabled. - Probably it is possible to make it work without session, but - it will be needed to change the logic in python-social-auth. - """ - try: - import rest_framework_jwt - except ImportError: - return - assert rest_framework_jwt is not None - resp = self.client.post( - reverse('login_social_jwt_user'), data={'provider': 'twitter'}) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data, parse_qs(self.request_token_body)) - resp = self.client.post(reverse('login_social_token_user'), data={ - 'provider': 'twitter', - 'oauth_token': 'foobar', - 'oauth_verifier': 'overifier' - }) - self.assertEqual(resp.status_code, 200) - - def test_login_social_oauth1_knox(self): - """ - Currently oauth1 works only if session is enabled. - Probably it is possible to make it work without session, but - it will be needed to change the logic in python-social-auth. - """ - try: - import knox - except ImportError: - return - assert knox is not None - resp = self.client.post( - reverse('login_social_knox_user'), data={'provider': 'twitter'}) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data, parse_qs(self.request_token_body)) - resp = self.client.post(reverse('login_social_knox_user'), data={ - 'provider': 'twitter', - 'oauth_token': 'foobar', - 'oauth_verifier': 'overifier' - }) - self.assertEqual(resp.status_code, 200) - - -class TestSocialAuth2(APITestCase, BaseFacebookAPITestCase): - - @modify_settings(**session_modify_settings) - def _check_login_social_session(self, url, data): - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data['email'], self.email) - # check cookies are set - self.assertTrue('sessionid' in resp.cookies) - # check user is created - self.assertTrue( - get_user_model().objects.filter(email=self.email).exists()) - - @modify_settings(**token_modify_settings) - def _check_login_social_token_user(self, url, data): - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data['email'], self.email) - # check token exists - token = Token.objects.get(key=resp.data['token']) - # check user is created - self.assertEqual(token.user.email, self.email) - - @modify_settings(**token_modify_settings) - def _check_login_social_token_only(self, url, data): - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - # check token exists - token = Token.objects.get(key=resp.data['token']) - # check user is created - self.assertEqual(token.user.email, self.email) - - @modify_settings(**jwt_modify_settings) - @override_settings(SIMPLE_JWT={ - 'AUTH_TOKEN_CLASSES': ( - 'rest_framework_simplejwt.tokens.AccessToken', - 'rest_framework_simplejwt.tokens.SlidingToken', - ), - }) - def _check_login_social_jwt_only(self, url, data, token_type): - try: - from rest_framework_simplejwt.authentication import JWTAuthentication - except ImportError: - return - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - # check token valid - jwt_auth = JWTAuthentication() - token_instance = jwt_auth.get_validated_token(resp.data['token']) - self.assertEqual(token_instance['token_type'], token_type) - - @modify_settings(**jwt_modify_settings) - @override_settings(SIMPLE_JWT={ - 'AUTH_TOKEN_CLASSES': ( - 'rest_framework_simplejwt.tokens.AccessToken', - 'rest_framework_simplejwt.tokens.SlidingToken', - ), - }) - def _check_login_social_jwt_user(self, url, data, token_type): - try: - from rest_framework_simplejwt.authentication import JWTAuthentication - except ImportError: - return - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data['email'], self.email) - # check token valid - jwt_auth = JWTAuthentication() - token_instance = jwt_auth.get_validated_token(resp.data['token']) - self.assertEqual(token_instance['token_type'], token_type) - self.assertEqual(token_instance['email'], self.email) - - def _check_login_social_knox_only(self, url, data): - try: - from knox.auth import TokenAuthentication - except ImportError: - return - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - # check token valid - knox_auth = TokenAuthentication() - user, auth_data = knox_auth.authenticate_credentials(resp.data['token']) - self.assertEqual(user.email, self.email) - - def _check_login_social_knox_user(self, url, data): - try: - from knox.auth import TokenAuthentication - except ImportError: - return - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data['email'], self.email) - # check token valid - knox_auth = TokenAuthentication() - user, auth_data = knox_auth.authenticate_credentials(resp.data['token']) - self.assertEqual(user.email, self.email) - - def test_login_social_session(self): - self._check_login_social_session( - reverse('login_social_session'), - {'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_session_provider_in_url(self): - self._check_login_social_session( - reverse('login_social_session', kwargs={'provider': 'facebook'}), - {'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_token_user(self): - self._check_login_social_token_user( - reverse('login_social_token_user'), - {'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_token_user_provider_in_url(self): - self._check_login_social_token_user( - reverse('login_social_token_user', kwargs={'provider': 'facebook'}), - {'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_token_only(self): - self._check_login_social_token_only( - reverse('login_social_token'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_token_only_provider_in_url(self): - self._check_login_social_token_only( - reverse('login_social_token', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_jwt_pair_only(self): - self._check_login_social_jwt_only( - reverse('login_social_jwt_pair'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - token_type='access', - ) - - def test_login_social_jwt_pair_only_provider_in_url(self): - self._check_login_social_jwt_only( - reverse('login_social_jwt_pair', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - token_type='access', - ) - - def test_login_social_jwt_pair_user(self): - self._check_login_social_jwt_user( - reverse('login_social_jwt_pair_user'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - token_type='access', - ) - - def test_login_social_jwt_pair_user_provider_in_url(self): - self._check_login_social_jwt_user( - reverse('login_social_jwt_pair_user', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - token_type='access', - ) - - def test_login_social_jwt_sliding_only(self): - self._check_login_social_jwt_only( - reverse('login_social_jwt_sliding'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - token_type='sliding', - ) - - def test_login_social_jwt_sliding_only_provider_in_url(self): - self._check_login_social_jwt_only( - reverse('login_social_jwt_sliding', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - token_type='sliding', - ) - - def test_login_social_jwt_sliding_user(self): - self._check_login_social_jwt_user( - reverse('login_social_jwt_sliding_user'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - token_type='sliding', - ) - - def test_login_social_jwt_sliding_user_provider_in_url(self): - self._check_login_social_jwt_user( - reverse('login_social_jwt_sliding_user', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - token_type='sliding', - ) - - def test_login_social_knox_only(self): - self._check_login_social_knox_only( - reverse('login_social_knox'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_knox_only_provider_in_url(self): - self._check_login_social_knox_only( - reverse('login_social_knox', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_knox_user(self): - self._check_login_social_knox_user( - reverse('login_social_knox_user'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_login_social_knox_user_provider_in_url(self): - self._check_login_social_knox_user( - reverse('login_social_knox_user', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_no_provider_session(self): - resp = self.client.post( - reverse('login_social_session'), - {'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - self.assertEqual(resp.status_code, 400) - - def test_unknown_provider_session(self): - resp = self.client.post( - reverse('login_social_session', kwargs={'provider': 'unknown'}), - {'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - self.assertEqual(resp.status_code, 404) - - def test_login_social_http_origin(self): - resp = self.client.post( - reverse('login_social_session'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - HTTP_ORIGIN="http://frontend.com") - self.assertEqual(resp.status_code, 200) - url_params = dict(parse_qsl(urlparse(HTTPretty.latest_requests[0].path).query)) - self.assertEqual(url_params['redirect_uri'], "http://frontend.com/") - - @override_settings(REST_SOCIAL_OAUTH_ABSOLUTE_REDIRECT_URI='http://myproject.com/') - def test_login_absolute_redirect(self): - resp = self.client.post( - reverse('login_social_session'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - self.assertEqual(resp.status_code, 200) - url_params = dict(parse_qsl(urlparse(HTTPretty.latest_requests[0].path).query)) - self.assertEqual('http://myproject.com/', url_params['redirect_uri']) - - @override_settings(REST_SOCIAL_OAUTH_ABSOLUTE_REDIRECT_URI='http://myproject.com/') - def test_login_manual_redirect(self): - resp = self.client.post( - reverse('login_social_session'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw', - 'redirect_uri': 'http://manualdomain.com/'}) - self.assertEqual(resp.status_code, 200) - url_params = dict(parse_qsl(urlparse(HTTPretty.latest_requests[0].path).query)) - self.assertEqual('http://manualdomain.com/', url_params['redirect_uri']) - - @patch('rest_framework.views.APIView.permission_classes') - def test_login_social_session_model_permission(self, m_permission_classes): - setattr(m_permission_classes, '__get__', lambda *args, **kwargs: (DjangoModelPermissionsOrAnonReadOnly, )) - self._check_login_social_session( - reverse('login_social_session'), - {'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_user_login_with_no_email(self): - user_data_body = json.loads(self.user_data_body) - user_data_body['email'] = '' - self.user_data_body = json.dumps(user_data_body) - self.do_rest_login() - resp = self.client.post( - reverse('login_social_token'), data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'} - ) - self.assertEqual(resp.status_code, 400) - self.assertIn('error', resp.data) - - # Deprecated tests - - @modify_settings(**jwt_modify_settings) - def _deprecated_check_login_social_jwt_only(self, url, data): - try: - from rest_framework_jwt.settings import api_settings - except ImportError: - return - jwt_decode_handler = api_settings.JWT_DECODE_HANDLER - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - # check token valid - jwt_data = jwt_decode_handler(resp.data['token']) - self.assertEqual(jwt_data['email'], self.email) - - @modify_settings(**jwt_modify_settings) - def _deprecated_check_login_social_jwt_user(self, url, data): - try: - from rest_framework_jwt.settings import api_settings - except ImportError: - return - jwt_decode_handler = api_settings.JWT_DECODE_HANDLER - resp = self.client.post(url, data) - self.assertEqual(resp.status_code, 200) - self.assertEqual(resp.data['email'], self.email) - # check token valid - jwt_data = jwt_decode_handler(resp.data['token']) - self.assertEqual(jwt_data['email'], self.email) - - def test_deprecated_login_social_jwt_only(self): - self._deprecated_check_login_social_jwt_only( - reverse('login_social_jwt'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_deprecated_login_social_jwt_only_provider_in_url(self): - self._deprecated_check_login_social_jwt_only( - reverse('login_social_jwt', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_deprecated_login_social_jwt_user(self): - self._deprecated_check_login_social_jwt_user( - reverse('login_social_jwt_user'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - - def test_deprecated_login_social_jwt_user_provider_in_url(self): - self._deprecated_check_login_social_jwt_user( - reverse('login_social_jwt_user', kwargs={'provider': 'facebook'}), - data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}, - ) - - -class TestSocialAuth2Error(APITestCase, BaseFacebookAPITestCase): - access_token_status = 400 - - def test_login_oauth_provider_error(self): - resp = self.client.post( - reverse('login_social_session'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - self.assertEqual(resp.status_code, 400) - - -class TestSocialAuth2HTTPError(APITestCase, BaseFacebookAPITestCase): - access_token_status = 401 - - def test_login_oauth_provider_http_error(self): - resp = self.client.post( - reverse('login_social_session'), - data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) - self.assertEqual(resp.status_code, 400) diff --git a/tests/test_token.py b/tests/test_token.py new file mode 100644 index 0000000..fbf0c3f --- /dev/null +++ b/tests/test_token.py @@ -0,0 +1,99 @@ +import json + +from django.test import modify_settings +from django.urls import reverse +from rest_framework.authtoken.models import Token +from rest_framework.test import APITestCase +from social_core.utils import parse_qs + +from .base import BaseFacebookAPITestCase, BaseTwitterApiTestCase + + +token_modify_settings = dict( + INSTALLED_APPS={ + 'remove': [ + 'django.contrib.sessions', + 'knox', + ] + }, + MIDDLEWARE_CLASSES={ + 'remove': [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + ], + } +) + + +class TestSocialAuth1Token(APITestCase, BaseTwitterApiTestCase): + + def test_login_social_oauth1_token(self): + """ + Currently oauth1 works only if session is enabled. + Probably it is possible to make it work without session, but + it will be needed to change the logic in python-social-auth. + """ + resp = self.client.post( + reverse('login_social_token_user'), data={'provider': 'twitter'}) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data, parse_qs(self.request_token_body)) + resp = self.client.post(reverse('login_social_token_user'), data={ + 'provider': 'twitter', + 'oauth_token': 'foobar', + 'oauth_verifier': 'overifier' + }) + self.assertEqual(resp.status_code, 200) + + +class TestSocialAuth2Token(APITestCase, BaseFacebookAPITestCase): + + @modify_settings(**token_modify_settings) + def _check_login_social_token_user(self, url, data): + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['email'], self.email) + # check token exists + token = Token.objects.get(key=resp.data['token']) + # check user is created + self.assertEqual(token.user.email, self.email) + + @modify_settings(**token_modify_settings) + def _check_login_social_token_only(self, url, data): + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, 200) + # check token exists + token = Token.objects.get(key=resp.data['token']) + # check user is created + self.assertEqual(token.user.email, self.email) + + def test_login_social_token_user(self): + self._check_login_social_token_user( + reverse('login_social_token_user'), + {'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_token_user_provider_in_url(self): + self._check_login_social_token_user( + reverse('login_social_token_user', kwargs={'provider': 'facebook'}), + {'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_token_only(self): + self._check_login_social_token_only( + reverse('login_social_token'), + data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_login_social_token_only_provider_in_url(self): + self._check_login_social_token_only( + reverse('login_social_token', kwargs={'provider': 'facebook'}), + data={'code': '3D52VoM1uiw94a1ETnGvYlCw'}) + + def test_user_login_with_no_email(self): + user_data_body = json.loads(self.user_data_body) + user_data_body['email'] = '' + self.user_data_body = json.dumps(user_data_body) + self.do_rest_login() + resp = self.client.post( + reverse('login_social_token'), data={'provider': 'facebook', 'code': '3D52VoM1uiw94a1ETnGvYlCw'} + ) + self.assertEqual(resp.status_code, 400) + self.assertIn('error', resp.data) diff --git a/tox.ini b/tox.ini index f4f8d7b..7bd5be0 100644 --- a/tox.ini +++ b/tox.ini @@ -18,16 +18,21 @@ deps = djangorestframework<4.0 social-auth-core==3.0.0 social-auth-app-django==3.1.0 + djangorestframework-jwt + djangorestframework_simplejwt + django-rest-knox django111: Django>=1.11,<1.12 django20: Django>=2.0,<2.1 django21: Django>=2.1,<2.2 - py37-django111: djangorestframework-jwt - py37-django111: djangorestframework_simplejwt py37-django111: coverage -rrequirements_test.txt commands = py.test {posargs} +[testenv:py27-django111] +commands = + py.test --ignore=tests/test_simple_jwt.py {posargs} + [testenv:py37-django111] commands = coverage run --source=rest_social_auth -m py.test {posargs}