From 87e542250c78cadf282ef43f336d33e850d39616 Mon Sep 17 00:00:00 2001 From: Alexey Evseev Date: Mon, 10 Feb 2020 00:27:45 +0700 Subject: [PATCH] Finalize session simulation for OAuth1 (#118) * Finalize session simulation for OAuth1 --- Makefile | 4 +++- README.md | 4 ---- RELEASE_NOTES.md | 1 + rest_social_auth/views.py | 33 ++++++++++++++++++++++----------- tests/test_jwt.py | 36 ++++++++++++------------------------ tests/test_knox.py | 22 +++++++++++++++++----- tests/test_simple_jwt.py | 36 ++++++++++++------------------------ tests/test_token.py | 39 +++++++++++++++------------------------ 8 files changed, 82 insertions(+), 93 deletions(-) diff --git a/Makefile b/Makefile index c4935bd..07c14b5 100644 --- a/Makefile +++ b/Makefile @@ -66,9 +66,11 @@ native-run-example: native-migrate native-clean: find . -path ./venv -prune | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf -native-test: native-install-all native-clean native-lint +native-test-only: native-install-all native-clean PYTHONPATH='example_project/' python -m pytest -Wignore $(TEST_ARGS) +native-test: native-lint native-test-only + native-lint: native-install-all flake8 . diff --git a/README.md b/README.md index f30757a..4e357e4 100644 --- a/README.md +++ b/README.md @@ -330,10 +330,6 @@ OAuth 1.0a workflow with rest-social-auth This flow is the same as described in [satellizer](https://github.com/sahat/satellizer#-login-with-oauth-10). This angularjs module is used in example project. -#### Note -If you use token (or jwt) authentication and OAuth 1.0, then you still need 'django.contrib.sessions' app (it is not required for OAuth 2.0 and token authentication). -This is because python-social-auth will store some data in session between requests to OAuth 1.0 provider. - rest-social-auth purpose ------------------------ diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 73a0237..5f5424a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,7 @@ rest_social_auth release notes master ------ - Update supported version of django-rest-knox (>=4.0.0, <5.0.0). v4 has breaking changes. +- Allow to use token auth with OAuth1 without django session enabled Issues: #110 diff --git a/rest_social_auth/views.py b/rest_social_auth/views.py index 471e5bf..6288474 100644 --- a/rest_social_auth/views.py +++ b/rest_social_auth/views.py @@ -166,21 +166,32 @@ def get_object(self): self.request.backend.STATE_PARAMETER = False if self.oauth_v1(): - # Oauth1 uses sessions, in case of token authentication session store will be empty - backend = self.request.backend - session_token_name = backend.name + backend.UNATHORIZED_TOKEN_SUFIX - if not self.request.session.exists(session_token_name): - oauth1_tokrn_param = backend.data.get(backend.OAUTH_TOKEN_PARAMETER_NAME) - self.request.session[session_token_name] = [ - urlencode({ - backend.OAUTH_TOKEN_PARAMETER_NAME: oauth1_tokrn_param, - 'oauth_token_secret': backend.data.get('oauth_token_secret') - }) - ] + self.save_token_param_in_session() user = self.request.backend.complete(user=user) return user + def save_token_param_in_session(self): + """ + Save token param in strategy's session. + This method will allow to use token auth with OAuth1 even if session is not enabled in + django settings (social_core expects that session is enabled). + """ + backend = self.request.backend + session_token_name = backend.name + backend.UNATHORIZED_TOKEN_SUFIX + session = self.request.strategy.session + if ( + (isinstance(session, dict) and session_token_name not in session) or + not session.exists(session_token_name) + ): + oauth1_token_param = backend.data.get(backend.OAUTH_TOKEN_PARAMETER_NAME) + session[session_token_name] = [ + urlencode({ + backend.OAUTH_TOKEN_PARAMETER_NAME: oauth1_token_param, + 'oauth_token_secret': backend.data.get('oauth_token_secret') + }) + ] + def do_login(self, backend, user): """ Do login action here. diff --git a/tests/test_jwt.py b/tests/test_jwt.py index bdfec07..4b7a3a8 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -1,4 +1,4 @@ -from django.test import modify_settings +from django.test import override_settings from django.urls import reverse from rest_framework.test import APITestCase from rest_framework_jwt.settings import api_settings as jwt_api_settings @@ -7,33 +7,22 @@ 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', - ], - } +jwt_override_settings = dict( + INSTALLED_APPS=[ + 'django.contrib.contenttypes', + 'rest_framework', + 'social_django', + 'rest_social_auth', + 'users', + ], + MIDDLEWARE=[], ) +@override_settings(**jwt_override_settings) 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) @@ -46,9 +35,9 @@ def test_login_social_oauth1_jwt(self): self.assertEqual(resp.status_code, 200) +@override_settings(**jwt_override_settings) 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) @@ -57,7 +46,6 @@ def _check_login_social_jwt_only(self, url, data): 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) diff --git a/tests/test_knox.py b/tests/test_knox.py index d892955..5d33909 100644 --- a/tests/test_knox.py +++ b/tests/test_knox.py @@ -1,3 +1,4 @@ +from django.test import override_settings from django.urls import reverse from knox.auth import TokenAuthentication as KnoxTokenAuthentication from rest_framework.test import APITestCase @@ -6,14 +7,24 @@ from .base import BaseFacebookAPITestCase, BaseTwitterApiTestCase +knox_override_settings = dict( + INSTALLED_APPS=[ + 'django.contrib.contenttypes', + 'rest_framework', + 'social_django', + 'rest_social_auth', + 'knox', # For django-rest-knox + 'users', + ], + MIDDLEWARE=[ + ], +) + + +@override_settings(**knox_override_settings) 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) @@ -26,6 +37,7 @@ def test_login_social_oauth1_knox(self): self.assertEqual(resp.status_code, 200) +@override_settings(**knox_override_settings) class TestSocialAuth2Knox(APITestCase, BaseFacebookAPITestCase): def _check_login_social_knox_only(self, url, data): diff --git a/tests/test_simple_jwt.py b/tests/test_simple_jwt.py index 7e44c9c..78e2095 100644 --- a/tests/test_simple_jwt.py +++ b/tests/test_simple_jwt.py @@ -1,4 +1,4 @@ -from django.test import modify_settings +from django.test import override_settings from django.urls import reverse from rest_framework.test import APITestCase from rest_framework_simplejwt.authentication import JWTAuthentication @@ -7,33 +7,22 @@ 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', - ], - } +jwt_simple_override_settings = dict( + INSTALLED_APPS=[ + 'django.contrib.contenttypes', + 'rest_framework', + 'social_django', + 'rest_social_auth', + 'users', + ], + MIDDLEWARE=[], ) +@override_settings(**jwt_simple_override_settings) 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) @@ -46,9 +35,9 @@ def test_login_social_oauth1_jwt(self): self.assertEqual(resp.status_code, 200) +@override_settings(**jwt_simple_override_settings) 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) @@ -57,7 +46,6 @@ def _check_login_social_simple_jwt_only(self, url, data, token_type): 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) diff --git a/tests/test_token.py b/tests/test_token.py index 2caeb8a..12b4116 100644 --- a/tests/test_token.py +++ b/tests/test_token.py @@ -1,6 +1,6 @@ import json -from django.test import modify_settings +from django.test import override_settings from django.urls import reverse from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase @@ -9,33 +9,25 @@ 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', - ], - } +token_override_settings = dict( + INSTALLED_APPS=[ + 'django.contrib.contenttypes', + 'rest_framework', + 'rest_framework.authtoken', + 'social_django', + 'rest_social_auth', + 'users', + ], + MIDDLEWARE=[], ) +@override_settings(**token_override_settings) 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'}) + url = reverse('login_social_token_user') + resp = self.client.post(url, 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={ @@ -46,9 +38,9 @@ def test_login_social_oauth1_token(self): self.assertEqual(resp.status_code, 200) +@override_settings(**token_override_settings) 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) @@ -58,7 +50,6 @@ def _check_login_social_token_user(self, url, data): # 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)