From 45a5abc6bbb2c890d85c811b8d7a82e6fb8f1aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 5 Oct 2021 11:43:28 +0200 Subject: [PATCH 01/21] OpenID Connect backend for Twitch authentication (#607) (#608) * open id backend for Twitch authentication * add entry to CHANGELOG.md * fixed code style issues Fixes #607 Co-authored-by: Jesse Gerard Brands --- CHANGELOG.md | 1 + social_core/backends/twitch.py | 26 ++++++++++++ social_core/tests/backends/test_twitch.py | 48 +++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dd4f866..bdc64446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Add fields that populate on create but not update `SOCIAL_AUTH_IMMUTABLE_USER_FIELDS` - Add Gitea oauth2 backend +- Add Twitch OpenId backend ### Changed - Fixed Slack user identity API call with Bearer headers diff --git a/social_core/backends/twitch.py b/social_core/backends/twitch.py index aa555aae..857d24e3 100644 --- a/social_core/backends/twitch.py +++ b/social_core/backends/twitch.py @@ -3,6 +3,32 @@ https://python-social-auth.readthedocs.io/en/latest/backends/twitch.html """ from .oauth import BaseOAuth2 +from .open_id_connect import OpenIdConnectAuth + + +class TwitchOpenIdConnect(OpenIdConnectAuth): + """Twitch OpenID Connect authentication backend""" + name = 'twitch' + USERNAME_KEY = 'preferred_username' + OIDC_ENDPOINT = 'https://id.twitch.tv/oauth2' + DEFAULT_SCOPE = ['openid', 'user:read:email'] + TWITCH_CLAIMS = '{"id_token":{"email": null,"email_verified":null,"preferred_username":null}}' + + def auth_params(self, state=None): + params = super().auth_params(state) + # Twitch uses a non-compliant OpenID implementation where the claims must be passed as a param + params['claims'] = self.TWITCH_CLAIMS + return params + + def get_user_details(self, response): + return { + 'username': self.id_token['preferred_username'], + 'email': self.id_token['email'], + # Twitch does not provide this information + 'fullname': '', + 'first_name': '', + 'last_name': '', + } class TwitchOAuth2(BaseOAuth2): diff --git a/social_core/tests/backends/test_twitch.py b/social_core/tests/backends/test_twitch.py index 67b1e375..b1011cc4 100644 --- a/social_core/tests/backends/test_twitch.py +++ b/social_core/tests/backends/test_twitch.py @@ -1,5 +1,53 @@ import json from .oauth import OAuth2Test +from .open_id_connect import OpenIdConnectTestMixin + + +class TwitchOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): + backend_path = 'social_core.backends.twitch.TwitchOpenIdConnect' + user_data_url = 'https://id.twitch.tv/oauth2/userinfo' + issuer = 'https://id.twitch.tv/oauth2' + expected_username = 'test_user1' + openid_config_body = json.dumps({ + 'authorization_endpoint': 'https://id.twitch.tv/oauth2/authorize', + 'claims_parameter_supported': True, + 'claims_supported': [ + 'iss', + 'azp', + 'preferred_username', + 'updated_at', + 'aud', + 'exp', + 'iat', + 'picture', + 'sub', + 'email', + 'email_verified', + ], + 'id_token_signing_alg_values_supported': [ + 'RS256', + ], + 'issuer': 'https://id.twitch.tv/oauth2', + 'jwks_uri': 'https://id.twitch.tv/oauth2/keys', + 'response_types_supported': [ + 'id_token', + 'code', + 'token', + 'code id_token', + 'token id_token', + ], + 'scopes_supported': [ + 'openid', + ], + 'subject_types_supported': [ + 'public', + ], + 'token_endpoint': 'https://id.twitch.tv/oauth2/token', + 'token_endpoint_auth_methods_supported': [ + 'client_secret_post', + ], + 'userinfo_endpoint': 'https://id.twitch.tv/oauth2/userinfo', + }) class TwitchOAuth2Test(OAuth2Test): From 2e3ccb47912d8755405759e933ca08a74dbbb173 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 11 Oct 2021 16:57:02 +0300 Subject: [PATCH 02/21] Bug/twitch oauth2 deprecated (#619) * twitch oauth2 switched to new api * CHANGELOG.md updated Fixes #607 Co-authored-by: nikitatonkoshkur --- CHANGELOG.md | 1 + social_core/backends/twitch.py | 24 +++++++++++---- social_core/tests/backends/test_twitch.py | 36 +++++++++++------------ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdc64446..e2182cc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Fixed Slack user identity API call with Bearer headers - Fixed microsoft-graph login error +- Fixed Twitch OAuth2 backend ## [4.1.0](https://github.com/python-social-auth/social-core/releases/tag/4.1.0) - 2021-03-01 diff --git a/social_core/backends/twitch.py b/social_core/backends/twitch.py index 857d24e3..a7222db2 100644 --- a/social_core/backends/twitch.py +++ b/social_core/backends/twitch.py @@ -38,19 +38,31 @@ class TwitchOAuth2(BaseOAuth2): AUTHORIZATION_URL = 'https://id.twitch.tv/oauth2/authorize' ACCESS_TOKEN_URL = 'https://id.twitch.tv/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' - DEFAULT_SCOPE = ['user_read'] + DEFAULT_SCOPE = ['user:read:email'] REDIRECT_STATE = False + def get_user_id(self, details, response): + """ + Use twitch user id as unique id + """ + return response.get('id') + def get_user_details(self, response): return { - 'username': response.get('name'), + 'username': response.get('login'), 'email': response.get('email'), 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): - return self.get_json( - 'https://api.twitch.tv/kraken/user/', - params={'oauth_token': access_token, 'api_version': 5}, - ) + client_id, _ = self.get_key_and_secret() + auth_headers = { + 'Authorization': 'Bearer %s' % access_token, + 'Client-Id': client_id + } + url = 'https://api.twitch.tv/helix/users' + + data = self.get_json(url, headers=auth_headers) + + return data['data'][0] if data.get('data') else {} diff --git a/social_core/tests/backends/test_twitch.py b/social_core/tests/backends/test_twitch.py index b1011cc4..fdf948c1 100644 --- a/social_core/tests/backends/test_twitch.py +++ b/social_core/tests/backends/test_twitch.py @@ -52,29 +52,29 @@ class TwitchOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): class TwitchOAuth2Test(OAuth2Test): backend_path = 'social_core.backends.twitch.TwitchOAuth2' - user_data_url = 'https://api.twitch.tv/kraken/user/' + user_data_url = 'https://api.twitch.tv/helix/users' expected_username = 'test_user1' access_token_body = json.dumps({ 'access_token': 'foobar', + 'token_type': 'bearer', }) user_data_body = json.dumps({ - 'type': 'user', - 'name': 'test_user1', - 'created_at': '2011-06-03T17:49:19Z', - 'updated_at': '2012-06-18T17:19:57Z', - 'logo': 'http://static-cdn.jtvnw.net/jtv_user_pictures/' - 'test_user1-profile_image-62e8318af864d6d7-300x300.jpeg', - '_id': 22761313, - 'display_name': 'test_user1', - 'bio': 'test bio woo I\'m a test user', - 'email': 'asdf@asdf.com', - 'email_verified': True, - 'partnered': True, - 'twitter_connected': False, - 'notifications': { - 'push': True, - 'email': True - } + 'data': [ + { + 'id': '689563726', + 'login': 'test_user1', + 'display_name': 'test_user1', + 'type': '', + 'broadcaster_type': '', + 'description': '', + 'profile_image_url': 'https://static-cdn.jtvnw.net/jtv_user_pictures/foo.png', + 'offline_image_url': '', + 'view_count': 0, + 'email': 'example@reply.com', + 'created_at': '2021-05-21T18:59:25Z', + 'access_token': 'hmkgz15x7j54jm63rpwfwhcnue6t4fxwv' + } + ] }) def test_login(self): From 3e8027dad53a73b309cc9e578bc304f3a08d83d6 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Mon, 11 Oct 2021 22:26:16 -0700 Subject: [PATCH 03/21] Disable stale bot Signed-off-by: Anders Kaseorg --- .github/stale.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 9eb6c2c8..00000000 --- a/.github/stale.yml +++ /dev/null @@ -1,12 +0,0 @@ -daysUntilStale: 60 -daysUntilClose: 7 -exemptLabels: - - pinned - - security -staleLabel: stale -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -closeComment: false -only: issues From 6decc57efea8e705856a65bae8f829149712a202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Wed, 13 Oct 2021 10:26:06 +0200 Subject: [PATCH 04/21] facebook: avoid changing settings dict Fixes #586 --- social_core/backends/facebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/facebook.py b/social_core/backends/facebook.py index 11f70dd1..9417a2a4 100644 --- a/social_core/backends/facebook.py +++ b/social_core/backends/facebook.py @@ -65,7 +65,7 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" - params = self.setting('PROFILE_EXTRA_PARAMS', {}) + params = self.setting('PROFILE_EXTRA_PARAMS', {}).copy() params['access_token'] = access_token if self.setting('APPSECRET_PROOF', True): From 17476e32ec16d3d0a4c261947a1dd814f3432bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Wed, 13 Oct 2021 10:27:16 +0200 Subject: [PATCH 05/21] facebook: Update API version to current one --- social_core/backends/facebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/facebook.py b/social_core/backends/facebook.py index 9417a2a4..b94ef5bd 100644 --- a/social_core/backends/facebook.py +++ b/social_core/backends/facebook.py @@ -14,7 +14,7 @@ AuthMissingParameter -API_VERSION = 8.0 +API_VERSION = 14.0 class FacebookOAuth2(BaseOAuth2): From c991c0c2080ee59113e4d1a14367a5ce5b335483 Mon Sep 17 00:00:00 2001 From: Day Barr Date: Mon, 8 Nov 2021 23:22:32 +0000 Subject: [PATCH 06/21] Fix typo in PartialDecoratorTestCase mock name mock_sesstion_set => mock_session_set --- social_core/tests/test_partial.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/social_core/tests/test_partial.py b/social_core/tests/test_partial.py index b5ae3c49..67439c73 100644 --- a/social_core/tests/test_partial.py +++ b/social_core/tests/test_partial.py @@ -17,8 +17,8 @@ def setUp(self): self.mock_partial_store = Mock() self.mock_strategy.storage.partial.store = self.mock_partial_store - self.mock_sesstion_set = Mock() - self.mock_strategy.session_set = self.mock_sesstion_set + self.mock_session_set = Mock() + self.mock_strategy.session_set = self.mock_session_set def test_save_to_session(self): # GIVEN @@ -42,10 +42,10 @@ def decorated_func(*args, **kwargs): self.assertEqual((self.mock_current_partial,), self.mock_partial_store.call_args[0]) - self.assertEqual(1, self.mock_sesstion_set.call_count) + self.assertEqual(1, self.mock_session_set.call_count) self.assertEqual((PARTIAL_TOKEN_SESSION_NAME, self.mock_current_partial_token), - self.mock_sesstion_set.call_args[0]) + self.mock_session_set.call_args[0]) def test_not_to_save_to_session(self): # GIVEN @@ -69,7 +69,7 @@ def decorated_func(*args, **kwargs): self.assertEqual((self.mock_current_partial,), self.mock_partial_store.call_args[0]) - self.assertEqual(0, self.mock_sesstion_set.call_count) + self.assertEqual(0, self.mock_session_set.call_count) def test_save_to_session_by_backward_compatible_decorator(self): # GIVEN @@ -93,10 +93,10 @@ def decorated_func(*args, **kwargs): self.assertEqual((self.mock_current_partial,), self.mock_partial_store.call_args[0]) - self.assertEqual(1, self.mock_sesstion_set.call_count) + self.assertEqual(1, self.mock_session_set.call_count) self.assertEqual((PARTIAL_TOKEN_SESSION_NAME, self.mock_current_partial_token), - self.mock_sesstion_set.call_args[0]) + self.mock_session_set.call_args[0]) def test_not_to_save_to_session_when_the_response_is_a_dict(self): # GIVEN @@ -114,4 +114,4 @@ def decorated_func(*args, **kwargs): # THEN self.assertEqual(expected_response, response) self.assertEqual(0, self.mock_partial_store.call_count) - self.assertEqual(0, self.mock_sesstion_set.call_count) + self.assertEqual(0, self.mock_session_set.call_count) From 271b26eb69f76842f869218d5f90e3111951f8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 9 Nov 2021 11:47:11 +0100 Subject: [PATCH 07/21] backeds: Fix getting e-mail from Azure Using upn is wrong as it contains tags for external users. Fixes #627 --- social_core/backends/azuread.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/social_core/backends/azuread.py b/social_core/backends/azuread.py index 3ca9908e..085ed7bc 100644 --- a/social_core/backends/azuread.py +++ b/social_core/backends/azuread.py @@ -45,7 +45,7 @@ class AzureADOAuth2(BaseOAuth2): ACCESS_TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False - DEFAULT_SCOPE = ['openid', 'profile', 'user_impersonation'] + DEFAULT_SCOPE = ['openid', 'profile', 'user_impersonation', 'email'] EXTRA_DATA = [ ('access_token', 'access_token'), ('id_token', 'id_token'), @@ -70,7 +70,7 @@ def get_user_details(self, response): response.get('family_name', '') ) return {'username': fullname, - 'email': response.get('upn'), + 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} From d6d9df982b412fbd06184e680768b1d3649f1f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Wed, 10 Nov 2021 13:11:36 +0100 Subject: [PATCH 08/21] backends: Fallback to UPN for email in Azure The e-mail is not provided for managed users, but it usually matches UPN. This was also the previous behavior prior #629. --- social_core/backends/azuread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/azuread.py b/social_core/backends/azuread.py index 085ed7bc..2e37af51 100644 --- a/social_core/backends/azuread.py +++ b/social_core/backends/azuread.py @@ -70,7 +70,7 @@ def get_user_details(self, response): response.get('family_name', '') ) return {'username': fullname, - 'email': response.get('email'), + 'email': response.get('email', response.get('upn')), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} From eb2010b542e7a5963caca291cce8a4caee23a09b Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 15 Nov 2021 20:11:16 +0100 Subject: [PATCH 09/21] Fix kid key rotation for OpenID Connect (#631) * Fix kid lookups for key rotation (OpenID Connect) According to the OpenID Connect Documentation https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys keys should be re-fetched if a kid in an ID Token is not part of the cached keys. The only thing that needs to be done is to check if one of the keys matches the kid and if not, we should refetch the keys. Fixes #591 --- social_core/backends/open_id_connect.py | 28 ++++++-- social_core/tests/backends/oauth.py | 13 ++-- social_core/tests/backends/test_elixir.py | 2 +- social_core/tests/backends/test_fence.py | 2 +- social_core/tests/backends/test_globus.py | 2 +- social_core/tests/backends/test_google.py | 2 +- social_core/tests/backends/test_okta.py | 2 +- ..._id_connect.py => test_open_id_connect.py} | 66 +++++++++++++++++-- social_core/tests/backends/test_twitch.py | 2 +- social_core/utils.py | 5 ++ 10 files changed, 100 insertions(+), 24 deletions(-) rename social_core/tests/backends/{open_id_connect.py => test_open_id_connect.py} (75%) diff --git a/social_core/backends/open_id_connect.py b/social_core/backends/open_id_connect.py index c770b934..eb734f87 100644 --- a/social_core/backends/open_id_connect.py +++ b/social_core/backends/open_id_connect.py @@ -143,12 +143,28 @@ def validate_claims(self, id_token): raise AuthTokenError(self, 'Incorrect id_token: nonce') def find_valid_key(self, id_token): - for key in self.get_jwks_keys(): - rsakey = jwk.construct(key) - message, encoded_sig = id_token.rsplit('.', 1) - decoded_sig = base64url_decode(encoded_sig.encode('utf-8')) - if rsakey.verify(message.encode('utf-8'), decoded_sig): - return key + kid = jwt.get_unverified_header(id_token).get('kid') + + keys = self.get_jwks_keys() + if kid is not None: + for key in keys: + if kid == key.get('kid'): + break + else: + # In case the key id is not found in the cached keys, just + # reload the JWKS keys. Ideally this should be done by + # invalidating the cache. + self.get_jwks_keys.invalidate() + keys = self.get_jwks_keys() + + for key in keys: + if kid is None or kid == key.get('kid'): + rsakey = jwk.construct(key) + message, encoded_sig = id_token.rsplit('.', 1) + decoded_sig = base64url_decode(encoded_sig.encode('utf-8')) + if rsakey.verify(message.encode('utf-8'), decoded_sig): + return key + return None def validate_and_return_id_token(self, id_token, access_token): """ diff --git a/social_core/tests/backends/oauth.py b/social_core/tests/backends/oauth.py index 9058a8ed..0f04b2bd 100644 --- a/social_core/tests/backends/oauth.py +++ b/social_core/tests/backends/oauth.py @@ -58,11 +58,6 @@ def auth_handlers(self, start_url): target_url, status=200, body='foobar') - HTTPretty.register_uri(self._method(self.backend.ACCESS_TOKEN_METHOD), - uri=self.backend.access_token_url(), - status=self.access_token_status, - body=self.access_token_body or '', - content_type='text/json') if self.user_data_url: HTTPretty.register_uri(HTTPretty.POST if self.user_data_url_post else HTTPretty.GET, self.user_data_url, @@ -70,6 +65,13 @@ def auth_handlers(self, start_url): content_type=self.user_data_content_type) return target_url + def pre_complete_callback(self, start_url): + HTTPretty.register_uri(self._method(self.backend.ACCESS_TOKEN_METHOD), + uri=self.backend.access_token_url(), + status=self.access_token_status, + body=self.access_token_body or '', + content_type='text/json') + def do_start(self): start_url = self.backend.start().url target_url = self.auth_handlers(start_url) @@ -80,6 +82,7 @@ def do_start(self): self.backend) self.strategy.set_request_data(parse_qs(urlparse(target_url).query), self.backend) + self.pre_complete_callback(start_url) return self.backend.complete() diff --git a/social_core/tests/backends/test_elixir.py b/social_core/tests/backends/test_elixir.py index 766c76be..28b726d8 100644 --- a/social_core/tests/backends/test_elixir.py +++ b/social_core/tests/backends/test_elixir.py @@ -1,5 +1,5 @@ from .oauth import OAuth2Test -from .open_id_connect import OpenIdConnectTestMixin +from .test_open_id_connect import OpenIdConnectTestMixin class ElixirOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): diff --git a/social_core/tests/backends/test_fence.py b/social_core/tests/backends/test_fence.py index 24a8362e..ed8b0aaf 100644 --- a/social_core/tests/backends/test_fence.py +++ b/social_core/tests/backends/test_fence.py @@ -1,7 +1,7 @@ import json from .oauth import OAuth2Test -from .open_id_connect import OpenIdConnectTestMixin +from .test_open_id_connect import OpenIdConnectTestMixin class FenceOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): diff --git a/social_core/tests/backends/test_globus.py b/social_core/tests/backends/test_globus.py index d9381bcc..443607e4 100644 --- a/social_core/tests/backends/test_globus.py +++ b/social_core/tests/backends/test_globus.py @@ -1,7 +1,7 @@ import json from .oauth import OAuth2Test -from .open_id_connect import OpenIdConnectTestMixin +from .test_open_id_connect import OpenIdConnectTestMixin class GlobusOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): diff --git a/social_core/tests/backends/test_google.py b/social_core/tests/backends/test_google.py index e3c7b58a..701af100 100644 --- a/social_core/tests/backends/test_google.py +++ b/social_core/tests/backends/test_google.py @@ -7,7 +7,7 @@ from ..models import User from .oauth import OAuth1Test, OAuth2Test -from .open_id_connect import OpenIdConnectTestMixin +from .test_open_id_connect import OpenIdConnectTestMixin class GoogleOAuth2Test(OAuth2Test): diff --git a/social_core/tests/backends/test_okta.py b/social_core/tests/backends/test_okta.py index 4762851c..16526cbd 100644 --- a/social_core/tests/backends/test_okta.py +++ b/social_core/tests/backends/test_okta.py @@ -2,7 +2,7 @@ from httpretty import HTTPretty from social_core.tests.backends.oauth import OAuth2Test -from social_core.tests.backends.open_id_connect import OpenIdConnectTestMixin +from social_core.tests.backends.test_open_id_connect import OpenIdConnectTestMixin JWK_KEY = { 'kty': 'RSA', diff --git a/social_core/tests/backends/open_id_connect.py b/social_core/tests/backends/test_open_id_connect.py similarity index 75% rename from social_core/tests/backends/open_id_connect.py rename to social_core/tests/backends/test_open_id_connect.py index 6731039e..22ce53ab 100644 --- a/social_core/tests/backends/open_id_connect.py +++ b/social_core/tests/backends/test_open_id_connect.py @@ -6,10 +6,16 @@ import datetime import base64 from calendar import timegm +from unittest import mock +from urllib.parse import urlparse from jose import jwt from httpretty import HTTPretty +from social_core.backends.open_id_connect import OpenIdConnectAuth +from ...utils import parse_qs +from .oauth import OAuth2Test + sys.path.insert(0, '..') @@ -49,6 +55,7 @@ class OpenIdConnectTestMixin: issuer = None # id_token issuer openid_config_body = None key = None + access_token_kwargs = {} def setUp(self): super().setUp() @@ -96,7 +103,7 @@ def get_id_token(self, client_key=None, expiration_datetime=None, } def prepare_access_token_body(self, client_key=None, tamper_message=False, - expiration_datetime=None, + expiration_datetime=None, kid=None, issue_datetime=None, nonce=None, issuer=None): """ @@ -125,12 +132,13 @@ def prepare_access_token_body(self, client_key=None, tamper_message=False, ) body['id_token'] = jwt.encode( - id_token, + claims=id_token, key=dict(self.key, iat=timegm(issue_datetime.utctimetuple()), nonce=nonce), algorithm='RS256', - access_token='foobar' + access_token='foobar', + headers=dict(kid=kid), ) if tamper_message: @@ -142,12 +150,20 @@ def prepare_access_token_body(self, client_key=None, tamper_message=False, return json.dumps(body) def authtoken_raised(self, expected_message, **access_token_kwargs): - self.access_token_body = self.prepare_access_token_body( - **access_token_kwargs - ) + self.access_token_kwargs = access_token_kwargs with self.assertRaisesRegex(AuthTokenError, expected_message): self.do_login() + def pre_complete_callback(self, start_url): + nonce = parse_qs(urlparse(start_url).query)['nonce'] + + self.access_token_kwargs.setdefault('nonce', nonce) + self.access_token_body = self.prepare_access_token_body( + **self.access_token_kwargs + ) + super().pre_complete_callback(start_url) + + def test_invalid_signature(self): self.authtoken_raised( 'Token error: Signature verification failed', @@ -177,5 +193,41 @@ def test_invalid_issue_time(self): def test_invalid_nonce(self): self.authtoken_raised( 'Token error: Incorrect id_token: nonce', - nonce='something-wrong' + nonce='something-wrong', + kid='testkey', ) + + def test_invalid_kid(self): + self.authtoken_raised('Token error: Signature verification failed', kid='doesnotexist') + + +class ExampleOpenIdConnectAuth(OpenIdConnectAuth): + name = 'example123' + OIDC_ENDPOINT = 'https://example.com/oidc' + + +class OpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): + backend_path = \ + 'social_core.tests.backends.test_open_id_connect.ExampleOpenIdConnectAuth' + issuer = 'https://example.com' + openid_config_body = json.dumps({ + 'issuer': 'https://example.com', + 'authorization_endpoint': 'https://example.com/oidc/auth', + 'token_endpoint': 'https://example.com/oidc/token', + 'userinfo_endpoint': 'https://example.com/oidc/userinfo', + 'revocation_endpoint': 'https://example.com/oidc/revoke', + 'jwks_uri': 'https://example.com/oidc/certs', + }) + + expected_username = 'cartman' + + def pre_complete_callback(self, start_url): + super().pre_complete_callback(start_url) + HTTPretty.register_uri('GET', + uri=self.backend.userinfo_url(), + status=200, + body=json.dumps({'preferred_username': self.expected_username}), + content_type='text/json') + + def test_everything_works(self): + self.do_login() diff --git a/social_core/tests/backends/test_twitch.py b/social_core/tests/backends/test_twitch.py index fdf948c1..2907f809 100644 --- a/social_core/tests/backends/test_twitch.py +++ b/social_core/tests/backends/test_twitch.py @@ -1,6 +1,6 @@ import json from .oauth import OAuth2Test -from .open_id_connect import OpenIdConnectTestMixin +from .test_open_id_connect import OpenIdConnectTestMixin class TwitchOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): diff --git a/social_core/utils.py b/social_core/utils.py index 2e2523c3..46b14b39 100644 --- a/social_core/utils.py +++ b/social_core/utils.py @@ -305,4 +305,9 @@ def wrapped(this): if not cached_value: raise return cached_value + + wrapped.invalidate = self._invalidate return wrapped + + def _invalidate(self): + self.cache.clear() From 8d7c351f5e39e5ea6ccdc7e13eb6b94f6ed1aeab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 6 Dec 2021 11:29:54 +0100 Subject: [PATCH 10/21] pyupgrade: Remove not needed future imports - remove no longer needed future imports - remove no longer necessary coding header --- social_core/backends/gae.py | 1 - social_core/backends/pinterest.py | 2 -- social_core/backends/professionali.py | 1 - social_core/backends/slack.py | 1 - social_core/backends/vk.py | 1 - social_core/backends/weibo.py | 1 - social_core/backends/weixin.py | 1 - social_core/tests/backends/open_id.py | 1 - social_core/tests/backends/test_auth0.py | 1 - social_core/tests/backends/test_discourse.py | 1 - social_core/tests/backends/test_open_id_connect.py | 1 - social_core/tests/backends/test_saml.py | 1 - social_core/tests/backends/test_vk.py | 3 --- 13 files changed, 16 deletions(-) diff --git a/social_core/backends/gae.py b/social_core/backends/gae.py index a6aa8e3e..1919db01 100644 --- a/social_core/backends/gae.py +++ b/social_core/backends/gae.py @@ -1,7 +1,6 @@ """ Google App Engine support using User API """ -from __future__ import absolute_import from google.appengine.api import users diff --git a/social_core/backends/pinterest.py b/social_core/backends/pinterest.py index eb5d2ce3..c28417d0 100644 --- a/social_core/backends/pinterest.py +++ b/social_core/backends/pinterest.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- """ Pinterest OAuth2 backend, docs at: https://developers.pinterest.com/docs/api/authentication/ """ -from __future__ import unicode_literals import ssl diff --git a/social_core/backends/professionali.py b/social_core/backends/professionali.py index 15f39776..2c02d76d 100644 --- a/social_core/backends/professionali.py +++ b/social_core/backends/professionali.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Professionaly OAuth 2.0 support. diff --git a/social_core/backends/slack.py b/social_core/backends/slack.py index def02fbb..7819285c 100644 --- a/social_core/backends/slack.py +++ b/social_core/backends/slack.py @@ -3,7 +3,6 @@ https://python-social-auth.readthedocs.io/en/latest/backends/slack.html https://api.slack.com/docs/oauth """ -from __future__ import unicode_literals from .oauth import BaseOAuth2 diff --git a/social_core/backends/vk.py b/social_core/backends/vk.py index 41a92d29..a32f04f7 100644 --- a/social_core/backends/vk.py +++ b/social_core/backends/vk.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ VK.com OpenAPI, OAuth2 and Iframe application OAuth2 backends, docs at: https://python-social-auth.readthedocs.io/en/latest/backends/vk.html diff --git a/social_core/backends/weibo.py b/social_core/backends/weibo.py index cb5dc908..da62dcd5 100644 --- a/social_core/backends/weibo.py +++ b/social_core/backends/weibo.py @@ -1,4 +1,3 @@ -# coding:utf-8 # author:hepochen@gmail.com https://github.com/hepochen """ Weibo OAuth2 backend, docs at: diff --git a/social_core/backends/weixin.py b/social_core/backends/weixin.py index f68beb56..381de304 100644 --- a/social_core/backends/weixin.py +++ b/social_core/backends/weixin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # author:duoduo3369@gmail.com https://github.com/duoduo369 """ Weixin OAuth2 backend diff --git a/social_core/tests/backends/open_id.py b/social_core/tests/backends/open_id.py index 8a4efdf3..75b75bc8 100644 --- a/social_core/tests/backends/open_id.py +++ b/social_core/tests/backends/open_id.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import sys from html.parser import HTMLParser diff --git a/social_core/tests/backends/test_auth0.py b/social_core/tests/backends/test_auth0.py index 3f40a18e..be8406eb 100644 --- a/social_core/tests/backends/test_auth0.py +++ b/social_core/tests/backends/test_auth0.py @@ -1,4 +1,3 @@ - import json from jose import jwt diff --git a/social_core/tests/backends/test_discourse.py b/social_core/tests/backends/test_discourse.py index 458f8b9f..738cd4a7 100644 --- a/social_core/tests/backends/test_discourse.py +++ b/social_core/tests/backends/test_discourse.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from urllib.parse import urlparse, parse_qs import requests diff --git a/social_core/tests/backends/test_open_id_connect.py b/social_core/tests/backends/test_open_id_connect.py index 22ce53ab..4511ae4f 100644 --- a/social_core/tests/backends/test_open_id_connect.py +++ b/social_core/tests/backends/test_open_id_connect.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ...exceptions import AuthTokenError import os import sys diff --git a/social_core/tests/backends/test_saml.py b/social_core/tests/backends/test_saml.py index 85e57949..fdb2d970 100644 --- a/social_core/tests/backends/test_saml.py +++ b/social_core/tests/backends/test_saml.py @@ -1,4 +1,3 @@ - import json import os import re diff --git a/social_core/tests/backends/test_vk.py b/social_core/tests/backends/test_vk.py index 606b899d..6d8c2279 100644 --- a/social_core/tests/backends/test_vk.py +++ b/social_core/tests/backends/test_vk.py @@ -1,6 +1,3 @@ -# coding: utf-8 -from __future__ import unicode_literals - import json from .oauth import OAuth2Test From df88f81e541f24bef4d620efc34effd6c102a2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 6 Dec 2021 11:31:57 +0100 Subject: [PATCH 11/21] pyupgrade: Use modern comprehensions - use dict comprehensions - define sets directly - use generators when applicable instead of comprehesions --- social_core/backends/oauth.py | 2 +- social_core/backends/odnoklassniki.py | 8 ++++---- social_core/backends/utils.py | 2 +- social_core/pipeline/user.py | 4 ++-- social_core/pipeline/utils.py | 4 ++-- social_core/storage.py | 4 ++-- social_core/tests/backends/test_discourse.py | 6 +++--- social_core/tests/backends/test_saml.py | 8 ++++---- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/social_core/backends/oauth.py b/social_core/backends/oauth.py index a2228244..1d21fe1d 100644 --- a/social_core/backends/oauth.py +++ b/social_core/backends/oauth.py @@ -209,7 +209,7 @@ def get_unauthorized_token(self): utoken = parse_qs(utoken) if utoken.get(self.OAUTH_TOKEN_PARAMETER_NAME) == data_token: self.strategy.session_set(name, list(set(unauthed_tokens) - - set([orig_utoken]))) + {orig_utoken})) token = utoken break else: diff --git a/social_core/backends/odnoklassniki.py b/social_core/backends/odnoklassniki.py index 03979c91..27b66769 100644 --- a/social_core/backends/odnoklassniki.py +++ b/social_core/backends/odnoklassniki.py @@ -51,8 +51,8 @@ class OdnoklassnikiApp(BaseAuth): ID_KEY = 'uid' def extra_data(self, user, uid, response, details=None, *args, **kwargs): - return dict([(key, value) for key, value in response.items() - if key in response['extra_data_list']]) + return {key: value for key, value in response.items() + if key in response['extra_data_list']} def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( @@ -109,8 +109,8 @@ def get_response(self): fields = ('logged_user_id', 'api_server', 'application_key', 'session_key', 'session_secret_key', 'authorized', 'apiconnection') - return dict((name, self.data[name]) for name in fields - if name in self.data) + return {name: self.data[name] for name in fields + if name in self.data} def verify_auth_sig(self): correct_key = self.get_auth_sig() diff --git a/social_core/backends/utils.py b/social_core/backends/utils.py index 9fbe51e0..88f143d0 100644 --- a/social_core/backends/utils.py +++ b/social_core/backends/utils.py @@ -76,7 +76,7 @@ def user_backends_data(user, backends, storage): if user_is_authenticated(user): associated = storage.user.get_social_auth_for_user(user) not_associated = list(set(available) - - set(assoc.provider for assoc in associated)) + {assoc.provider for assoc in associated}) values['associated'] = associated values['not_associated'] = not_associated return values diff --git a/social_core/pipeline/user.py b/social_core/pipeline/user.py index ef45ec08..f6cf14df 100644 --- a/social_core/pipeline/user.py +++ b/social_core/pipeline/user.py @@ -69,8 +69,8 @@ def create_user(strategy, details, backend, user=None, *args, **kwargs): if user: return {'is_new': False} - fields = dict((name, kwargs.get(name, details.get(name))) - for name in backend.setting('USER_FIELDS', USER_FIELDS)) + fields = {name: kwargs.get(name, details.get(name)) + for name in backend.setting('USER_FIELDS', USER_FIELDS)} if not fields: return diff --git a/social_core/pipeline/utils.py b/social_core/pipeline/utils.py index 112aed45..767e242f 100644 --- a/social_core/pipeline/utils.py +++ b/social_core/pipeline/utils.py @@ -63,6 +63,6 @@ def partial_load(strategy, token): kwargs['user'] = strategy.storage.user.get_user(user) partial.args = [strategy.from_session_value(val) for val in args] - partial.kwargs = dict((key, strategy.from_session_value(val)) - for key, val in kwargs.items()) + partial.kwargs = {key: strategy.from_session_value(val) + for key, val in kwargs.items()} return partial diff --git a/social_core/storage.py b/social_core/storage.py index f48a7cd9..2a2a5cbd 100644 --- a/social_core/storage.py +++ b/social_core/storage.py @@ -224,10 +224,10 @@ def oids(cls, server_url, handle=None): kwargs = {'server_url': server_url} if handle is not None: kwargs['handle'] = handle - return sorted([ + return sorted(( (assoc.id, cls.openid_association(assoc)) for assoc in cls.get(**kwargs) - ], key=lambda x: x[1].issued, reverse=True) + ), key=lambda x: x[1].issued, reverse=True) @classmethod def openid_association(cls, assoc): diff --git a/social_core/tests/backends/test_discourse.py b/social_core/tests/backends/test_discourse.py index 738cd4a7..a6c25ff5 100644 --- a/social_core/tests/backends/test_discourse.py +++ b/social_core/tests/backends/test_discourse.py @@ -49,9 +49,9 @@ def do_start(self): ) response = requests.get(start_url) - query_values = dict( - (k, v[0]) for k, v in parse_qs(urlparse(response.url).query).items() - ) + query_values = { + k: v[0] for k, v in parse_qs(urlparse(response.url).query).items() + } self.strategy.set_request_data(query_values, self.backend) return self.backend.complete() diff --git a/social_core/tests/backends/test_saml.py b/social_core/tests/backends/test_saml.py index fdb2d970..985acf87 100644 --- a/social_core/tests/backends/test_saml.py +++ b/social_core/tests/backends/test_saml.py @@ -79,8 +79,8 @@ def do_start(self): response = requests.get(start_url) self.assertTrue(response.url.startswith(return_url)) self.assertEqual(response.text, 'foobar') - query_values = dict((k, v[0]) for k, v in - parse_qs(urlparse(response.url).query).items()) + query_values = {k: v[0] for k, v in + parse_qs(urlparse(response.url).query).items()} self.assertNotIn(' ', query_values['SAMLResponse']) self.strategy.set_request_data(query_values, self.backend) return self.backend.complete() @@ -109,8 +109,8 @@ def modify_start_url(self, start_url): """ # Parse the SAML Request URL to get the XML being sent to TestShib url_parts = urlparse(start_url) - query = dict((k, v[0]) for (k, v) in - parse_qs(url_parts.query).items()) + query = {k: v[0] for (k, v) in + parse_qs(url_parts.query).items()} xml = OneLogin_Saml2_Utils.decode_base64_and_inflate( query['SAMLRequest'] ) From 80a4e2b6ce19b5b24f6d4ffce7e53e2e4d26d406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 6 Dec 2021 11:33:41 +0100 Subject: [PATCH 12/21] pyupgrade: Remove u prefix from unicode literals These are no longer necessary in Python 3. --- social_core/backends/facebook.py | 2 +- social_core/tests/backends/test_bitbucket.py | 94 ++++++++++---------- social_core/tests/backends/test_ngpvan.py | 14 +-- social_core/tests/backends/test_slack.py | 22 ++--- 4 files changed, 66 insertions(+), 66 deletions(-) diff --git a/social_core/backends/facebook.py b/social_core/backends/facebook.py index b94ef5bd..c8ef724f 100644 --- a/social_core/backends/facebook.py +++ b/social_core/backends/facebook.py @@ -213,7 +213,7 @@ def auth_html(self): def load_signed_request(self, signed_request): def base64_url_decode(data): data = data.encode('ascii') - data += '='.encode('ascii') * (4 - (len(data) % 4)) + data += b'=' * (4 - (len(data) % 4)) return base64.urlsafe_b64decode(data) key, secret = self.get_key_and_secret() diff --git a/social_core/tests/backends/test_bitbucket.py b/social_core/tests/backends/test_bitbucket.py index 263f6419..14818f93 100644 --- a/social_core/tests/backends/test_bitbucket.py +++ b/social_core/tests/backends/test_bitbucket.py @@ -13,41 +13,41 @@ class BitbucketOAuthMixin: bb_api_user_emails = 'https://api.bitbucket.org/2.0/user/emails' user_data_body = json.dumps({ - u'created_on': u'2012-03-29T18:07:38+00:00', - u'display_name': u'Foo Bar', - u'links': { - u'avatar': {u'href': u'https://bitbucket.org/account/foobar/avatar/32/'}, - u'followers': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/followers'}, - u'following': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/following'}, - u'hooks': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/hooks'}, - u'html': {u'href': u'https://bitbucket.org/foobar'}, - u'repositories': {u'href': u'https://api.bitbucket.org/2.0/repositories/foobar'}, - u'self': {u'href': u'https://api.bitbucket.org/2.0/users/foobar'}}, - u'location': u'Fooville, Bar', - u'type': u'user', - u'username': u'foobar', - u'uuid': u'{397621dc-0f78-329f-8d6d-727396248e3f}', - u'website': u'http://foobar.com' + 'created_on': '2012-03-29T18:07:38+00:00', + 'display_name': 'Foo Bar', + 'links': { + 'avatar': {'href': 'https://bitbucket.org/account/foobar/avatar/32/'}, + 'followers': {'href': 'https://api.bitbucket.org/2.0/users/foobar/followers'}, + 'following': {'href': 'https://api.bitbucket.org/2.0/users/foobar/following'}, + 'hooks': {'href': 'https://api.bitbucket.org/2.0/users/foobar/hooks'}, + 'html': {'href': 'https://bitbucket.org/foobar'}, + 'repositories': {'href': 'https://api.bitbucket.org/2.0/repositories/foobar'}, + 'self': {'href': 'https://api.bitbucket.org/2.0/users/foobar'}}, + 'location': 'Fooville, Bar', + 'type': 'user', + 'username': 'foobar', + 'uuid': '{397621dc-0f78-329f-8d6d-727396248e3f}', + 'website': 'http://foobar.com' }) emails_body = json.dumps({ - u'page': 1, - u'pagelen': 10, - u'size': 2, - u'values': [ + 'page': 1, + 'pagelen': 10, + 'size': 2, + 'values': [ { - u'email': u'foo@bar.com', - u'is_confirmed': True, - u'is_primary': True, - u'links': {u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, - u'type': u'email' + 'email': 'foo@bar.com', + 'is_confirmed': True, + 'is_primary': True, + 'links': {'self': {'href': 'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, + 'type': 'email' }, { - u'email': u'not@confirme.com', - u'is_confirmed': False, - u'is_primary': False, - u'links': {u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/not@confirmed.com'}}, - u'type': u'email' + 'email': 'not@confirme.com', + 'is_confirmed': False, + 'is_primary': False, + 'links': {'self': {'href': 'https://api.bitbucket.org/2.0/user/emails/not@confirmed.com'}}, + 'type': 'email' } ] }) @@ -82,16 +82,16 @@ def test_partial_pipeline(self): class BitbucketOAuth1FailTest(BitbucketOAuth1Test): emails_body = json.dumps({ - u'page': 1, - u'pagelen': 10, - u'size': 1, - u'values': [ + 'page': 1, + 'pagelen': 10, + 'size': 1, + 'values': [ { - u'email': u'foo@bar.com', - u'is_confirmed': False, - u'is_primary': True, - u'links': {u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, - u'type': u'email' + 'email': 'foo@bar.com', + 'is_confirmed': False, + 'is_primary': True, + 'links': {'self': {'href': 'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, + 'type': 'email' } ] }) @@ -137,16 +137,16 @@ def test_partial_pipeline(self): class BitbucketOAuth2FailTest(BitbucketOAuth2Test): emails_body = json.dumps({ - u'page': 1, - u'pagelen': 10, - u'size': 1, - u'values': [ + 'page': 1, + 'pagelen': 10, + 'size': 1, + 'values': [ { - u'email': u'foo@bar.com', - u'is_confirmed': False, - u'is_primary': True, - u'links': {u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, - u'type': u'email' + 'email': 'foo@bar.com', + 'is_confirmed': False, + 'is_primary': True, + 'links': {'self': {'href': 'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, + 'type': 'email' } ] }) diff --git a/social_core/tests/backends/test_ngpvan.py b/social_core/tests/backends/test_ngpvan.py index 50e940e9..d0b13e46 100644 --- a/social_core/tests/backends/test_ngpvan.py +++ b/social_core/tests/backends/test_ngpvan.py @@ -170,12 +170,12 @@ def test_user_data(self): ] }) user = self.do_start() - self.assertEqual(user.username, u'testuser@user.local') - self.assertEqual(user.email, u'testuser@user.local') - self.assertEqual(user.extra_user_fields['phone'], u'+12015555555') - self.assertEqual(user.extra_user_fields['first_name'], u'John') - self.assertEqual(user.extra_user_fields['last_name'], u'Smith') - self.assertEqual(user.extra_user_fields['fullname'], u'John Smith') + self.assertEqual(user.username, 'testuser@user.local') + self.assertEqual(user.email, 'testuser@user.local') + self.assertEqual(user.extra_user_fields['phone'], '+12015555555') + self.assertEqual(user.extra_user_fields['first_name'], 'John') + self.assertEqual(user.extra_user_fields['last_name'], 'Smith') + self.assertEqual(user.extra_user_fields['fullname'], 'John Smith') def test_extra_data_phone(self): """Confirm that you can get a phone number via the relevant setting""" @@ -185,7 +185,7 @@ def test_extra_data_phone(self): ] }) user = self.do_start() - self.assertEqual(user.social_user.extra_data['phone'], u'+12015555555') + self.assertEqual(user.social_user.extra_data['phone'], '+12015555555') def test_association_uid(self): """Test that the correct association uid is stored in the database""" diff --git a/social_core/tests/backends/test_slack.py b/social_core/tests/backends/test_slack.py index 7055f574..f73f8e5c 100644 --- a/social_core/tests/backends/test_slack.py +++ b/social_core/tests/backends/test_slack.py @@ -15,12 +15,12 @@ class SlackOAuth2Test(OAuth2Test): 'user': { 'email': 'foobar@example.com', 'name': 'Foo Bar', - 'id': u'123456' + 'id': '123456' }, 'team': { - 'id': u'456789' + 'id': '456789' }, - 'scope': u'identity.basic,identity.email' + 'scope': 'identity.basic,identity.email' }) expected_username = 'foobar' @@ -38,13 +38,13 @@ class SlackOAuth2TestTeamName(SlackOAuth2Test): 'user': { 'email': 'foobar@example.com', 'name': 'Foo Bar', - 'id': u'123456' + 'id': '123456' }, 'team': { - 'id': u'456789', - 'name': u'Square', + 'id': '456789', + 'name': 'Square', }, - 'scope': u'identity.basic,identity.email,identity.team' + 'scope': 'identity.basic,identity.email,identity.team' }) @@ -54,13 +54,13 @@ class SlackOAuth2TestUnicodeTeamName(SlackOAuth2Test): 'user': { 'email': 'foobar@example.com', 'name': 'Foo Bar', - 'id': u'123456' + 'id': '123456' }, 'team': { - 'id': u'456789', - 'name': u'Square \u221a team', + 'id': '456789', + 'name': 'Square \u221a team', }, - 'scope': u'identity.basic,identity.email,identity.team' + 'scope': 'identity.basic,identity.email,identity.team' }) def test_login(self): From b37a500cdc271e1c4ddbafbc93410dafd0422eae Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 6 Dec 2021 07:38:45 -0300 Subject: [PATCH 13/21] backends: include error in exceptions from the apple backend (#634) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * print error in apple backend * use f-string readable code Co-authored-by: Michal Čihař --- social_core/backends/apple.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/social_core/backends/apple.py b/social_core/backends/apple.py index edfab315..42d707ec 100644 --- a/social_core/backends/apple.py +++ b/social_core/backends/apple.py @@ -128,8 +128,8 @@ def decode_id_token(self, id_token): audience=self.get_audience(), algorithms=['RS256'], ) - except PyJWTError: - raise AuthFailed(self, 'Token validation failed') + except PyJWTError as error: + raise AuthFailed(self, f'Token validation failed by {error}') return decoded From 5d2c69a463e8658c8967a926ce61a4c257d79b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 6 Dec 2021 11:27:48 +0100 Subject: [PATCH 14/21] pyupgrade: Improve format strings - utilize f-strings when possible - simplify format string to omit position --- social_core/actions.py | 2 +- social_core/backends/asana.py | 2 +- social_core/backends/atlassian.py | 4 ++-- social_core/backends/azuread_b2c.py | 2 +- social_core/backends/azuread_tenant.py | 2 +- social_core/backends/beats.py | 6 +++--- social_core/backends/chatwork.py | 4 ++-- social_core/backends/coding.py | 2 +- social_core/backends/cognito.py | 8 ++++---- social_core/backends/coursera.py | 2 +- social_core/backends/discourse.py | 2 +- social_core/backends/douban.py | 2 +- social_core/backends/dribbble.py | 2 +- social_core/backends/dropbox.py | 2 +- social_core/backends/eveonline.py | 2 +- social_core/backends/exacttarget.py | 2 +- social_core/backends/fitbit.py | 4 ++-- social_core/backends/gitea.py | 2 +- social_core/backends/github.py | 6 +++--- social_core/backends/gitlab.py | 2 +- social_core/backends/goclio.py | 2 +- social_core/backends/itembase.py | 4 ++-- social_core/backends/jawbone.py | 2 +- social_core/backends/justgiving.py | 2 +- social_core/backends/kakao.py | 2 +- social_core/backends/line.py | 2 +- social_core/backends/livejournal.py | 2 +- social_core/backends/loginradius.py | 2 +- social_core/backends/lyft.py | 2 +- social_core/backends/mapmyfitness.py | 2 +- social_core/backends/mediawiki.py | 10 +++++----- social_core/backends/mendeley.py | 2 +- social_core/backends/monzo.py | 2 +- social_core/backends/naver.py | 2 +- social_core/backends/oauth.py | 4 ++-- social_core/backends/odnoklassniki.py | 14 +++++++------- social_core/backends/okta.py | 2 +- social_core/backends/open_id.py | 2 +- social_core/backends/open_id_connect.py | 2 +- social_core/backends/orcid.py | 2 +- social_core/backends/osso.py | 2 +- social_core/backends/patreon.py | 4 ++-- social_core/backends/phabricator.py | 2 +- social_core/backends/pixelpin.py | 2 +- social_core/backends/professionali.py | 4 ++-- social_core/backends/pushbullet.py | 2 +- social_core/backends/qiita.py | 2 +- social_core/backends/readability.py | 6 +++--- social_core/backends/reddit.py | 2 +- social_core/backends/runkeeper.py | 2 +- social_core/backends/saml.py | 4 ++-- social_core/backends/shimmering.py | 2 +- social_core/backends/shopify.py | 2 +- social_core/backends/sketchfab.py | 2 +- social_core/backends/slack.py | 2 +- social_core/backends/spotify.py | 6 +++--- social_core/backends/stripe.py | 2 +- social_core/backends/telegram.py | 4 ++-- social_core/backends/twilio.py | 2 +- social_core/backends/uber.py | 2 +- social_core/backends/universe.py | 2 +- social_core/backends/upwork.py | 2 +- social_core/backends/vend.py | 4 ++-- social_core/backends/yahoo.py | 2 +- social_core/backends/zoom.py | 2 +- social_core/exceptions.py | 14 +++++++------- social_core/strategy.py | 2 +- social_core/tests/backends/legacy.py | 8 ++++---- social_core/tests/backends/test_auth0.py | 2 +- social_core/tests/backends/test_discourse.py | 4 ++-- social_core/tests/backends/test_open_id_connect.py | 6 +++--- social_core/tests/test_utils.py | 2 +- social_core/utils.py | 2 +- 73 files changed, 116 insertions(+), 116 deletions(-) diff --git a/social_core/actions.py b/social_core/actions.py index eee563d8..81427156 100644 --- a/social_core/actions.py +++ b/social_core/actions.py @@ -92,7 +92,7 @@ def do_complete(backend, login, user=None, redirect_name='next', if redirect_value and redirect_value != url: redirect_value = quote(redirect_value) url += ('&' if '?' in url else '?') + \ - '{0}={1}'.format(redirect_name, redirect_value) + f'{redirect_name}={redirect_value}' if backend.setting('SANITIZE_REDIRECTS', True): allowed_hosts = backend.setting('ALLOWED_REDIRECT_HOSTS', []) + \ diff --git a/social_core/backends/asana.py b/social_core/backends/asana.py index d19fd140..7e050378 100644 --- a/social_core/backends/asana.py +++ b/social_core/backends/asana.py @@ -28,7 +28,7 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): return self.get_json(self.USER_DATA_URL, headers={ - 'Authorization': 'Bearer {}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }) def extra_data(self, user, uid, response, details=None, *args, **kwargs): diff --git a/social_core/backends/atlassian.py b/social_core/backends/atlassian.py index f76969f9..cb8e09a7 100644 --- a/social_core/backends/atlassian.py +++ b/social_core/backends/atlassian.py @@ -30,8 +30,8 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): resources = self.get_json('https://api.atlassian.com/oauth/token/accessible-resources', - headers={'Authorization': 'Bearer {}'.format(access_token)}) + headers={'Authorization': f'Bearer {access_token}'}) user_info = self.get_json('https://api.atlassian.com/ex/jira/{}/rest/api/2/myself'.format(resources[0]['id']), - headers={'Authorization': 'Bearer {}'.format(access_token)}) + headers={'Authorization': f'Bearer {access_token}'}) user_info['resources'] = resources return user_info diff --git a/social_core/backends/azuread_b2c.py b/social_core/backends/azuread_b2c.py index 7662f41e..a9f3842c 100644 --- a/social_core/backends/azuread_b2c.py +++ b/social_core/backends/azuread_b2c.py @@ -165,7 +165,7 @@ def get_public_key(self, kid): for key in resp.json()['keys']: if key['kid'] == kid: return self.jwt_key_to_pem(key) - raise DecodeError('Cannot find kid={}'.format(kid)) + raise DecodeError(f'Cannot find kid={kid}') def user_data(self, access_token, *args, **kwargs): response = kwargs.get('response') diff --git a/social_core/backends/azuread_tenant.py b/social_core/backends/azuread_tenant.py index 53b6501c..c17f8bc1 100644 --- a/social_core/backends/azuread_tenant.py +++ b/social_core/backends/azuread_tenant.py @@ -79,7 +79,7 @@ def get_certificate(self, kid): x5c = key['x5c'][0] break else: - raise DecodeError('Cannot find kid={}'.format(kid)) + raise DecodeError(f'Cannot find kid={kid}') return load_der_x509_certificate(base64.b64decode(x5c), default_backend()) diff --git a/social_core/backends/beats.py b/social_core/backends/beats.py index 4dda471a..61de4038 100644 --- a/social_core/backends/beats.py +++ b/social_core/backends/beats.py @@ -23,8 +23,8 @@ def get_user_id(self, details, response): def auth_headers(self): return { - 'Authorization': 'Basic {0}'.format(base64.urlsafe_b64encode( - ('{0}:{1}'.format(*self.get_key_and_secret()).encode()) + 'Authorization': 'Basic {}'.format(base64.urlsafe_b64encode( + '{}:{}'.format(*self.get_key_and_secret()).encode() )) } @@ -61,5 +61,5 @@ def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://partner.api.beatsmusic.com/v1/api/me', - headers={'Authorization': 'Bearer {0}'.format(access_token)} + headers={'Authorization': f'Bearer {access_token}'} ) diff --git a/social_core/backends/chatwork.py b/social_core/backends/chatwork.py index b69a627e..82d71f05 100644 --- a/social_core/backends/chatwork.py +++ b/social_core/backends/chatwork.py @@ -23,12 +23,12 @@ class ChatworkOAuth2(BaseOAuth2): def api_url(self, path): api_url = self.setting('API_URL') or self.API_URL - return '{0}{1}'.format(api_url.rstrip('/'), path) + return '{}{}'.format(api_url.rstrip('/'), path) def auth_headers(self): return { 'Authorization': b'Basic ' + base64.b64encode( - '{0}:{1}'.format(*self.get_key_and_secret()).encode() + '{}:{}'.format(*self.get_key_and_secret()).encode() ) } diff --git a/social_core/backends/coding.py b/social_core/backends/coding.py index 81f998b1..5bbf138c 100644 --- a/social_core/backends/coding.py +++ b/social_core/backends/coding.py @@ -43,6 +43,6 @@ def user_data(self, access_token, *args, **kwargs): def _user_data(self, access_token, path=None): url = urljoin( self.api_url(), - 'account/current_user{0}'.format(path or '') + 'account/current_user{}'.format(path or '') ) return self.get_json(url, params={'access_token': access_token}) diff --git a/social_core/backends/cognito.py b/social_core/backends/cognito.py index dabe718b..df6f33af 100644 --- a/social_core/backends/cognito.py +++ b/social_core/backends/cognito.py @@ -12,13 +12,13 @@ def user_pool_domain(self): return self.setting('POOL_DOMAIN') def authorization_url(self): - return '{}/login'.format(self.user_pool_domain()) + return f'{self.user_pool_domain()}/login' def access_token_url(self): - return '{}/oauth2/token'.format(self.user_pool_domain()) + return f'{self.user_pool_domain()}/oauth2/token' def user_data_url(self): - return '{}/oauth2/userInfo'.format(self.user_pool_domain()) + return f'{self.user_pool_domain()}/oauth2/userInfo' def get_user_details(self, response): """Return user details from their cognito pool account""" @@ -38,7 +38,7 @@ def user_data(self, access_token, *args, **kwargs): """Grab user profile information from cognito.""" response = self.get_json( url=self.user_data_url(), - headers={'Authorization': 'Bearer {}'.format(access_token)}, + headers={'Authorization': f'Bearer {access_token}'}, ) user_data = { diff --git a/social_core/backends/coursera.py b/social_core/backends/coursera.py index ff5f287a..56162bf6 100644 --- a/social_core/backends/coursera.py +++ b/social_core/backends/coursera.py @@ -40,4 +40,4 @@ def user_data(self, access_token, *args, **kwargs): ) def get_auth_header(self, access_token): - return {'Authorization': 'Bearer {0}'.format(access_token)} + return {'Authorization': f'Bearer {access_token}'} diff --git a/social_core/backends/discourse.py b/social_core/backends/discourse.py index 4c8bdc4f..905392b1 100644 --- a/social_core/backends/discourse.py +++ b/social_core/backends/discourse.py @@ -39,7 +39,7 @@ def auth_url(self): 'sso': base_64_payload, 'sig': payload_signature }) - return '{0}?{1}'.format(self.get_idp_url(), encoded_params) + return f'{self.get_idp_url()}?{encoded_params}' def get_idp_url(self): return self.setting('SERVER_URL') + '/session/sso_provider' diff --git a/social_core/backends/douban.py b/social_core/backends/douban.py index ff931c0a..96a07102 100644 --- a/social_core/backends/douban.py +++ b/social_core/backends/douban.py @@ -55,5 +55,5 @@ def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://api.douban.com/v2/user/~me', - headers={'Authorization': 'Bearer {0}'.format(access_token)} + headers={'Authorization': f'Bearer {access_token}'} ) diff --git a/social_core/backends/dribbble.py b/social_core/backends/dribbble.py index 7381e6d4..d8835376 100644 --- a/social_core/backends/dribbble.py +++ b/social_core/backends/dribbble.py @@ -58,5 +58,5 @@ def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://api.dribbble.com/v1/user', headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }) diff --git a/social_core/backends/dropbox.py b/social_core/backends/dropbox.py index a22b79b1..7a3addb2 100644 --- a/social_core/backends/dropbox.py +++ b/social_core/backends/dropbox.py @@ -26,6 +26,6 @@ def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.dropboxapi.com/2/users/get_current_account', - headers={'Authorization': 'Bearer {0}'.format(access_token)}, + headers={'Authorization': f'Bearer {access_token}'}, method='POST' ) diff --git a/social_core/backends/eveonline.py b/social_core/backends/eveonline.py index 1b7e5dc1..33b15be3 100644 --- a/social_core/backends/eveonline.py +++ b/social_core/backends/eveonline.py @@ -38,5 +38,5 @@ def user_data(self, access_token, *args, **kwargs): """Get Character data from EVE server""" return self.get_json( 'https://login.eveonline.com/oauth/verify', - headers={'Authorization': 'Bearer {0}'.format(access_token)} + headers={'Authorization': f'Bearer {access_token}'} ) diff --git a/social_core/backends/exacttarget.py b/social_core/backends/exacttarget.py index a066cdde..5beeacc7 100644 --- a/social_core/backends/exacttarget.py +++ b/social_core/backends/exacttarget.py @@ -45,7 +45,7 @@ def get_user_id(self, details, response): 'email': 'example@example.com' } """ - return '{0}'.format(details.get('id')) + return '{}'.format(details.get('id')) def uses_redirect(self): return False diff --git a/social_core/backends/fitbit.py b/social_core/backends/fitbit.py index a2f4cde3..768cc8f1 100644 --- a/social_core/backends/fitbit.py +++ b/social_core/backends/fitbit.py @@ -59,9 +59,9 @@ def user_data(self, access_token, *args, **kwargs): )['user'] def auth_headers(self): - tokens = '{0}:{1}'.format(*self.get_key_and_secret()) + tokens = '{}:{}'.format(*self.get_key_and_secret()) tokens = base64.urlsafe_b64encode(tokens.encode()) tokens = tokens.decode() return { - 'Authorization': 'Basic {0}'.format(tokens) + 'Authorization': f'Basic {tokens}' } diff --git a/social_core/backends/gitea.py b/social_core/backends/gitea.py index 1d19a492..5899f42d 100644 --- a/social_core/backends/gitea.py +++ b/social_core/backends/gitea.py @@ -24,7 +24,7 @@ class GiteaOAuth2 (BaseOAuth2): def api_url(self, path): api_url = self.setting('API_URL') or self.API_URL - return '{0}{1}'.format(api_url.rstrip('/'), path) + return '{}{}'.format(api_url.rstrip('/'), path) def authorization_url(self): return self.api_url('/login/oauth/authorize') diff --git a/social_core/backends/github.py b/social_core/backends/github.py index 42a8dac6..e8bd2d9e 100644 --- a/social_core/backends/github.py +++ b/social_core/backends/github.py @@ -64,8 +64,8 @@ def user_data(self, access_token, *args, **kwargs): return data def _user_data(self, access_token, path=None): - url = urljoin(self.api_url(), 'user{0}'.format(path or '')) - return self.get_json(url, headers={'Authorization': 'token {0}'.format(access_token)}) + url = urljoin(self.api_url(), 'user{}'.format(path or '')) + return self.get_json(url, headers={'Authorization': f'token {access_token}'}) class GithubMemberOAuth2(GithubOAuth2): @@ -74,7 +74,7 @@ class GithubMemberOAuth2(GithubOAuth2): def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" user_data = super().user_data(access_token, *args, **kwargs) - headers = {'Authorization': 'token {0}'.format(access_token)} + headers = {'Authorization': f'token {access_token}'} try: self.request(self.member_url(user_data), headers=headers) except HTTPError as err: diff --git a/social_core/backends/gitlab.py b/social_core/backends/gitlab.py index 13f51500..3fc7f07b 100644 --- a/social_core/backends/gitlab.py +++ b/social_core/backends/gitlab.py @@ -27,7 +27,7 @@ class GitLabOAuth2(BaseOAuth2): def api_url(self, path): api_url = self.setting('API_URL') or self.API_URL - return '{0}{1}'.format(api_url.rstrip('/'), path) + return '{}{}'.format(api_url.rstrip('/'), path) def authorization_url(self): return self.api_url('/oauth/authorize') diff --git a/social_core/backends/goclio.py b/social_core/backends/goclio.py index 4d75c1ca..e682e9da 100644 --- a/social_core/backends/goclio.py +++ b/social_core/backends/goclio.py @@ -16,7 +16,7 @@ def get_user_details(self, response): email = user.get('email', None) first_name, last_name = (user.get('first_name', None), user.get('last_name', None)) - fullname = '%s %s' % (first_name, last_name) + fullname = f'{first_name} {last_name}' return {'username': username, 'fullname': fullname, diff --git a/social_core/backends/itembase.py b/social_core/backends/itembase.py index 7938e932..d8330b40 100644 --- a/social_core/backends/itembase.py +++ b/social_core/backends/itembase.py @@ -50,14 +50,14 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): return self.get_json(self.USER_DETAILS_URL, headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }) def activation_data(self, response): # returns activation_data dict with activation_url inside # see http://developers.itembase.com/authentication/activation return self.get_json(self.ACTIVATION_ENDPOINT, headers={ - 'Authorization': 'Bearer {0}'.format(response['access_token']) + 'Authorization': 'Bearer {}'.format(response['access_token']) }) @handle_http_errors diff --git a/social_core/backends/jawbone.py b/social_core/backends/jawbone.py index 9805b5f0..c98d71ef 100644 --- a/social_core/backends/jawbone.py +++ b/social_core/backends/jawbone.py @@ -48,7 +48,7 @@ def process_error(self, data): if error == 'access_denied': raise AuthCanceled(self) else: - raise AuthUnknownError(self, 'Jawbone error was {0}'.format( + raise AuthUnknownError(self, 'Jawbone error was {}'.format( error )) return super().process_error(data) diff --git a/social_core/backends/justgiving.py b/social_core/backends/justgiving.py index 57c89a52..66de2b36 100644 --- a/social_core/backends/justgiving.py +++ b/social_core/backends/justgiving.py @@ -31,7 +31,7 @@ def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" key, secret = self.get_key_and_secret() return self.get_json(self.USER_DATA_URL, headers={ - 'Authorization': 'Bearer {0}'.format(access_token), + 'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json', 'x-application-key': secret, 'x-api-key': key diff --git a/social_core/backends/kakao.py b/social_core/backends/kakao.py index ceea26fc..9e015eac 100644 --- a/social_core/backends/kakao.py +++ b/social_core/backends/kakao.py @@ -39,7 +39,7 @@ def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://kapi.kakao.com/v2/user/me', headers={ - 'Authorization': 'Bearer {0}'.format(access_token), + 'Authorization': f'Bearer {access_token}', 'Content_Type': 'application/x-www-form-urlencoded;charset=utf-8', }, params={'access_token': access_token} diff --git a/social_core/backends/line.py b/social_core/backends/line.py index b1cfcf4b..9cb0a527 100644 --- a/social_core/backends/line.py +++ b/social_core/backends/line.py @@ -96,7 +96,7 @@ def user_data(self, access_token, *args, **kwargs): response = self.get_json( self.USER_INFO_URL, headers={ - 'Authorization': 'Bearer {}'.format(access_token) + 'Authorization': f'Bearer {access_token}' } ) self.process_error(response) diff --git a/social_core/backends/livejournal.py b/social_core/backends/livejournal.py index 3e1df99d..795f8f97 100644 --- a/social_core/backends/livejournal.py +++ b/social_core/backends/livejournal.py @@ -23,4 +23,4 @@ def openid_url(self): """Returns LiveJournal authentication URL""" if not self.data.get('openid_lj_user'): raise AuthMissingParameter(self, 'openid_lj_user') - return 'http://{0}.livejournal.com'.format(self.data['openid_lj_user']) + return 'http://{}.livejournal.com'.format(self.data['openid_lj_user']) diff --git a/social_core/backends/loginradius.py b/social_core/backends/loginradius.py index 49bfa55d..bdea3a6e 100644 --- a/social_core/backends/loginradius.py +++ b/social_core/backends/loginradius.py @@ -65,5 +65,5 @@ def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response. Since LoginRadius handles multiple providers, we need to distinguish them to prevent conflicts.""" - return '{0}-{1}'.format(response.get('Provider'), + return '{}-{}'.format(response.get('Provider'), response.get(self.ID_KEY)) diff --git a/social_core/backends/lyft.py b/social_core/backends/lyft.py index 8c016b45..9e259b26 100644 --- a/social_core/backends/lyft.py +++ b/social_core/backends/lyft.py @@ -37,7 +37,7 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json(self.USER_DATA_URL, headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }) def auth_complete_params(self, state=None): diff --git a/social_core/backends/mapmyfitness.py b/social_core/backends/mapmyfitness.py index 7c202b9b..7311bdd2 100644 --- a/social_core/backends/mapmyfitness.py +++ b/social_core/backends/mapmyfitness.py @@ -43,7 +43,7 @@ def user_data(self, access_token, *args, **kwargs): key = self.get_key_and_secret()[0] url = 'https://oauth2-api.mapmyapi.com/v7.0/user/self/' headers = { - 'Authorization': 'Bearer {0}'.format(access_token), + 'Authorization': f'Bearer {access_token}', 'Api-Key': key } return self.get_json(url, headers=headers) diff --git a/social_core/backends/mediawiki.py b/social_core/backends/mediawiki.py index 16565658..f8a5b1ce 100644 --- a/social_core/backends/mediawiki.py +++ b/social_core/backends/mediawiki.py @@ -72,7 +72,7 @@ def oauth_authorization_request(self, token): state = self.get_or_create_state() base_url = self.setting('MEDIAWIKI_URL') - return '{0}?{1}'.format(base_url, urlencode({ + return '{}?{}'.format(base_url, urlencode({ 'title': 'Special:Oauth/authenticate', self.OAUTH_TOKEN_PARAMETER_NAME: oauth_token, self.REDIRECT_URI_PARAMETER_NAME: self.get_redirect_uri(state) @@ -123,7 +123,7 @@ def get_user_details(self, response): raise AuthException( self, 'An error occurred while trying to read json ' + - 'content: {0}'.format(exception) + f'content: {exception}' ) issuer = urlparse(identity['iss']).netloc @@ -132,7 +132,7 @@ def get_user_details(self, response): if not issuer == expected_domain: raise AuthException( self, - 'Unexpected issuer {0}, expected {1}'.format( + 'Unexpected issuer {}, expected {}'.format( issuer, expected_domain ) @@ -143,7 +143,7 @@ def get_user_details(self, response): if not now >= (issued_at - self.LEEWAY): raise AuthException( self, - 'Identity issued {0} seconds in the future'.format( + 'Identity issued {} seconds in the future'.format( issued_at - now ) ) @@ -157,7 +157,7 @@ def get_user_details(self, response): if identity['nonce'] != request_nonce: raise AuthException( self, - 'Replay attack detected: {0} != {1}'.format( + 'Replay attack detected: {} != {}'.format( identity['nonce'], request_nonce ) diff --git a/social_core/backends/mendeley.py b/social_core/backends/mendeley.py index ebb73e66..e1619109 100644 --- a/social_core/backends/mendeley.py +++ b/social_core/backends/mendeley.py @@ -63,5 +63,5 @@ def get_user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.mendeley.com/profiles/me/', - headers={'Authorization': 'Bearer {0}'.format(access_token)} + headers={'Authorization': f'Bearer {access_token}'} ) diff --git a/social_core/backends/monzo.py b/social_core/backends/monzo.py index 46b5ac63..c9b42706 100644 --- a/social_core/backends/monzo.py +++ b/social_core/backends/monzo.py @@ -28,5 +28,5 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://api.monzo.com/accounts', - headers={'Authorization': 'Bearer {0}'.format(access_token)}, + headers={'Authorization': f'Bearer {access_token}'}, ) diff --git a/social_core/backends/naver.py b/social_core/backends/naver.py index 332f8132..df0ad295 100644 --- a/social_core/backends/naver.py +++ b/social_core/backends/naver.py @@ -27,7 +27,7 @@ def user_data(self, access_token, *args, **kwargs): response = self.request( 'https://openapi.naver.com/v1/nid/me', headers={ - 'Authorization': 'Bearer {0}'.format(access_token), + 'Authorization': f'Bearer {access_token}', 'Content_Type': 'text/json' } ) diff --git a/social_core/backends/oauth.py b/social_core/backends/oauth.py index 1d21fe1d..c1b03b79 100644 --- a/social_core/backends/oauth.py +++ b/social_core/backends/oauth.py @@ -258,7 +258,7 @@ def oauth_authorization_request(self, token): ) state = self.get_or_create_state() params[self.REDIRECT_URI_PARAMETER_NAME] = self.get_redirect_uri(state) - return '{0}?{1}'.format(self.authorization_url(), urlencode(params)) + return f'{self.authorization_url()}?{urlencode(params)}' def oauth_auth(self, token=None, oauth_verifier=None, signature_type=SIGNATURE_TYPE_AUTH_HEADER): @@ -329,7 +329,7 @@ def auth_url(self): # redirect_uri matching is strictly enforced, so match the # providers value exactly. params = unquote(params) - return '{0}?{1}'.format(self.authorization_url(), params) + return f'{self.authorization_url()}?{params}' def auth_complete_params(self, state=None): client_id, client_secret = self.get_key_and_secret() diff --git a/social_core/backends/odnoklassniki.py b/social_core/backends/odnoklassniki.py index 27b66769..2b8c3848 100644 --- a/social_core/backends/odnoklassniki.py +++ b/social_core/backends/odnoklassniki.py @@ -75,7 +75,7 @@ def auth_complete(self, *args, **kwargs): self.setting('EXTRA_USER_DATA_LIST', ()) data = { 'method': 'users.getInfo', - 'uids': '{0}'.format(response['logged_user_id']), + 'uids': '{}'.format(response['logged_user_id']), 'fields': ','.join(fields), } client_key, client_secret = self.get_key_and_secret() @@ -100,7 +100,7 @@ def auth_complete(self, *args, **kwargs): def get_auth_sig(self): secret_key = self.setting('SECRET') - hash_source = '{0:s}{1:s}{2:s}'.format(self.data['logged_user_id'], + hash_source = '{:s}{:s}{:s}'.format(self.data['logged_user_id'], self.data['session_key'], secret_key) return md5(hash_source.encode('utf-8')).hexdigest() @@ -127,12 +127,12 @@ def odnoklassniki_oauth_sig(data, client_secret): search for "little bit different way" """ suffix = md5( - '{0:s}{1:s}'.format(data['access_token'], + '{:s}{:s}'.format(data['access_token'], client_secret).encode('utf-8') ).hexdigest() - check_list = sorted(['{0:s}={1:s}'.format(key, value) + check_list = sorted(f'{key:s}={value:s}' for key, value in data.items() - if key != 'access_token']) + if key != 'access_token') return md5((''.join(check_list) + suffix).encode('utf-8')).hexdigest() @@ -143,8 +143,8 @@ def odnoklassniki_iframe_sig(data, client_secret_or_session_secret): If API method requires session context, request is signed with session secret key. Otherwise it is signed with application secret key """ - param_list = sorted(['{0:s}={1:s}'.format(key, value) - for key, value in data.items()]) + param_list = sorted(f'{key:s}={value:s}' + for key, value in data.items()) return md5( (''.join(param_list) + client_secret_or_session_secret).encode('utf-8') ).hexdigest() diff --git a/social_core/backends/okta.py b/social_core/backends/okta.py index b228e21f..3e0e2cbc 100644 --- a/social_core/backends/okta.py +++ b/social_core/backends/okta.py @@ -60,6 +60,6 @@ def user_data(self, access_token, *args, **kwargs): return self.get_json( self._url('v1/userinfo'), headers={ - 'Authorization': 'Bearer {}'.format(access_token), + 'Authorization': f'Bearer {access_token}', } ) diff --git a/social_core/backends/open_id.py b/social_core/backends/open_id.py index 38b217c6..063db2c6 100644 --- a/social_core/backends/open_id.py +++ b/social_core/backends/open_id.py @@ -239,7 +239,7 @@ def openid_request(self, params=None): return self.consumer().begin(url_add_parameters(self.openid_url(), params)) except DiscoveryFailure as err: - raise AuthException(self, 'OpenID discovery error: {0}'.format( + raise AuthException(self, 'OpenID discovery error: {}'.format( err )) diff --git a/social_core/backends/open_id_connect.py b/social_core/backends/open_id_connect.py index eb734f87..3e678870 100644 --- a/social_core/backends/open_id_connect.py +++ b/social_core/backends/open_id_connect.py @@ -215,7 +215,7 @@ def request_access_token(self, *args, **kwargs): def user_data(self, access_token, *args, **kwargs): return self.get_json(self.userinfo_url(), headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }) def get_user_details(self, response): diff --git a/social_core/backends/orcid.py b/social_core/backends/orcid.py index fa68fd38..14ef280b 100644 --- a/social_core/backends/orcid.py +++ b/social_core/backends/orcid.py @@ -116,7 +116,7 @@ def user_data(self, access_token, *args, **kwargs): self.USER_ID_URL, headers={ 'Content-Type': 'application/json', - 'Authorization': 'Bearer {}'.format(str(access_token)) + 'Authorization': f'Bearer {str(access_token)}' }, ) diff --git a/social_core/backends/osso.py b/social_core/backends/osso.py index 0fcbe8d7..a7c8a5b3 100644 --- a/social_core/backends/osso.py +++ b/social_core/backends/osso.py @@ -44,7 +44,7 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): """Loads normalized user profile from Osso""" - url = '{osso_base_url}/oauth/me?'.format(osso_base_url=self.osso_base_url) + urlencode({ + url = f'{self.osso_base_url}/oauth/me?' + urlencode({ 'access_token': access_token }) return self.get_json(url) \ No newline at end of file diff --git a/social_core/backends/patreon.py b/social_core/backends/patreon.py index d0e0dc8d..ad5d8009 100644 --- a/social_core/backends/patreon.py +++ b/social_core/backends/patreon.py @@ -33,9 +33,9 @@ def user_data(self, access_token, *args, **kwargs): def get_api(self, access_token, suffix): return self.get_json( - 'https://www.patreon.com/api/oauth2/v2/{}'.format(suffix), + f'https://www.patreon.com/api/oauth2/v2/{suffix}', headers=self.get_auth_header(access_token) ) def get_auth_header(self, access_token): - return {'Authorization': 'Bearer {0}'.format(access_token)} + return {'Authorization': f'Bearer {access_token}'} diff --git a/social_core/backends/phabricator.py b/social_core/backends/phabricator.py index 1bd4ddc3..0a2eb9fe 100644 --- a/social_core/backends/phabricator.py +++ b/social_core/backends/phabricator.py @@ -16,7 +16,7 @@ class PhabricatorOAuth2(BaseOAuth2): def api_url(self, path): api_url = self.setting('API_URL') or self.API_URL - return '{0}{1}'.format(api_url.rstrip('/'), path) + return '{}{}'.format(api_url.rstrip('/'), path) def authorization_url(self): return self.api_url('/oauthserver/auth/') diff --git a/social_core/backends/pixelpin.py b/social_core/backends/pixelpin.py index 3048134b..31b9c41f 100644 --- a/social_core/backends/pixelpin.py +++ b/social_core/backends/pixelpin.py @@ -30,6 +30,6 @@ def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://login.pixelpin.io/connect/userinfo', headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' } ) diff --git a/social_core/backends/professionali.py b/social_core/backends/professionali.py index 2c02d76d..6cb00094 100644 --- a/social_core/backends/professionali.py +++ b/social_core/backends/professionali.py @@ -25,9 +25,9 @@ def get_user_details(self, response): first_name, last_name = map(response.get, ('firstname', 'lastname')) email = '' if self.setting('FAKE_EMAIL'): - email = '{0}@professionali.ru'.format(time()) + email = f'{time()}@professionali.ru' return { - 'username': '{0}_{1}'.format(last_name, first_name), + 'username': f'{last_name}_{first_name}', 'first_name': first_name, 'last_name': last_name, 'email': email diff --git a/social_core/backends/pushbullet.py b/social_core/backends/pushbullet.py index d821223f..d799fd1c 100644 --- a/social_core/backends/pushbullet.py +++ b/social_core/backends/pushbullet.py @@ -18,6 +18,6 @@ def get_user_details(self, response): return {'username': response.get('access_token')} def get_user_id(self, details, response): - auth = 'Basic {0}'.format(base64.b64encode(details['username'])) + auth = 'Basic {}'.format(base64.b64encode(details['username'])) return self.get_json('https://api.pushbullet.com/v2/users/me', headers={'Authorization': auth})['iden'] diff --git a/social_core/backends/qiita.py b/social_core/backends/qiita.py index 87fb6a40..e484448f 100644 --- a/social_core/backends/qiita.py +++ b/social_core/backends/qiita.py @@ -62,6 +62,6 @@ def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://qiita.com/api/v2/authenticated_user', headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' } ) diff --git a/social_core/backends/readability.py b/social_core/backends/readability.py index 593117fc..b5adf79b 100644 --- a/social_core/backends/readability.py +++ b/social_core/backends/readability.py @@ -12,9 +12,9 @@ class ReadabilityOAuth(BaseOAuth1): """Readability OAuth authentication backend""" name = 'readability' ID_KEY = 'username' - AUTHORIZATION_URL = '{0}/oauth/authorize/'.format(READABILITY_API) - REQUEST_TOKEN_URL = '{0}/oauth/request_token/'.format(READABILITY_API) - ACCESS_TOKEN_URL = '{0}/oauth/access_token/'.format(READABILITY_API) + AUTHORIZATION_URL = f'{READABILITY_API}/oauth/authorize/' + REQUEST_TOKEN_URL = f'{READABILITY_API}/oauth/request_token/' + ACCESS_TOKEN_URL = f'{READABILITY_API}/oauth/access_token/' EXTRA_DATA = [('date_joined', 'date_joined'), ('kindle_email_address', 'kindle_email_address'), ('avatar_url', 'avatar_url'), diff --git a/social_core/backends/reddit.py b/social_core/backends/reddit.py index 97fa76b6..acd8102b 100644 --- a/social_core/backends/reddit.py +++ b/social_core/backends/reddit.py @@ -43,7 +43,7 @@ def user_data(self, access_token, *args, **kwargs): def auth_headers(self): return { 'Authorization': b'Basic ' + base64.urlsafe_b64encode( - '{0}:{1}'.format(*self.get_key_and_secret()).encode() + '{}:{}'.format(*self.get_key_and_secret()).encode() ) } diff --git a/social_core/backends/runkeeper.py b/social_core/backends/runkeeper.py index 18c2eec8..2bd3776b 100644 --- a/social_core/backends/runkeeper.py +++ b/social_core/backends/runkeeper.py @@ -43,5 +43,5 @@ def user_data(self, access_token, *args, **kwargs): return dict(user_data, **profile_data) def _user_data(self, access_token, path): - url = 'https://api.runkeeper.com{0}'.format(path) + url = f'https://api.runkeeper.com{path}' return self.get_json(url, params={'access_token': access_token}) diff --git a/social_core/backends/saml.py b/social_core/backends/saml.py index c2902475..eb72e649 100644 --- a/social_core/backends/saml.py +++ b/social_core/backends/saml.py @@ -303,7 +303,7 @@ def get_user_id(self, details, response): """ idp = self.get_idp(response['idp_name']) uid = idp.get_user_permanent_id(response['attributes']) - return '{0}:{1}'.format(idp.name, uid) + return f'{idp.name}:{uid}' def auth_complete(self, *args, **kwargs): """ @@ -318,7 +318,7 @@ def auth_complete(self, *args, **kwargs): if errors or not auth.is_authenticated(): reason = auth.get_last_error_reason() raise AuthFailed( - self, 'SAML login failed: {0} ({1})'.format(errors, reason) + self, f'SAML login failed: {errors} ({reason})' ) attributes = auth.get_attributes() diff --git a/social_core/backends/shimmering.py b/social_core/backends/shimmering.py index bc9a18c0..17650cf7 100644 --- a/social_core/backends/shimmering.py +++ b/social_core/backends/shimmering.py @@ -18,7 +18,7 @@ def get_user_details(self, response): last_name = response.get('last_name') email = response.get('email') username = response.get('username') - fullname = '{} {}'.format(first_name, last_name) + fullname = f'{first_name} {last_name}' return { 'username': username, 'fullname': fullname, diff --git a/social_core/backends/shopify.py b/social_core/backends/shopify.py index 9c8967d1..9f64c36b 100644 --- a/social_core/backends/shopify.py +++ b/social_core/backends/shopify.py @@ -91,7 +91,7 @@ def do_auth(self, access_token, shop_url, website, *args, **kwargs): 'backend': self, 'response': { 'shop': shop_url, - 'website': 'http://{0}'.format(website), + 'website': f'http://{website}', 'access_token': access_token } }) diff --git a/social_core/backends/sketchfab.py b/social_core/backends/sketchfab.py index 5a8d0c6b..8b747a2a 100644 --- a/social_core/backends/sketchfab.py +++ b/social_core/backends/sketchfab.py @@ -35,5 +35,5 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://sketchfab.com/v2/users/me', headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }) diff --git a/social_core/backends/slack.py b/social_core/backends/slack.py index 7819285c..341654b2 100644 --- a/social_core/backends/slack.py +++ b/social_core/backends/slack.py @@ -41,7 +41,7 @@ def get_user_details(self, response): if self.setting('USERNAME_WITH_TEAM', True) and team and \ 'name' in team: - username = '{0}@{1}'.format(username, response['team']['name']) + username = '{}@{}'.format(username, response['team']['name']) return { 'username': username, diff --git a/social_core/backends/spotify.py b/social_core/backends/spotify.py index cc0e67e0..55c95a1e 100644 --- a/social_core/backends/spotify.py +++ b/social_core/backends/spotify.py @@ -22,10 +22,10 @@ class SpotifyOAuth2(BaseOAuth2): ] def auth_headers(self): - auth_str = '{0}:{1}'.format(*self.get_key_and_secret()) + auth_str = '{}:{}'.format(*self.get_key_and_secret()) b64_auth_str = base64.urlsafe_b64encode(auth_str.encode()).decode() return { - 'Authorization': 'Basic {0}'.format(b64_auth_str) + 'Authorization': f'Basic {b64_auth_str}' } def get_user_details(self, response): @@ -43,5 +43,5 @@ def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.spotify.com/v1/me', - headers={'Authorization': 'Bearer {0}'.format(access_token)} + headers={'Authorization': f'Bearer {access_token}'} ) diff --git a/social_core/backends/stripe.py b/social_core/backends/stripe.py index 54d3cbd1..fe2e0a2c 100644 --- a/social_core/backends/stripe.py +++ b/social_core/backends/stripe.py @@ -39,7 +39,7 @@ def auth_complete_params(self, state=None): def auth_headers(self): client_id, client_secret = self.get_key_and_secret() return {'Accept': 'application/json', - 'Authorization': 'Bearer {0}'.format(client_secret)} + 'Authorization': f'Bearer {client_secret}'} def refresh_token_params(self, refresh_token, *args, **kwargs): return {'refresh_token': refresh_token, diff --git a/social_core/backends/telegram.py b/social_core/backends/telegram.py index e7f5d599..2f5077c8 100644 --- a/social_core/backends/telegram.py +++ b/social_core/backends/telegram.py @@ -23,7 +23,7 @@ def verify_data(self, response): if received_hash_string is None or auth_date is None: raise AuthMissingParameter('telegram', 'hash or auth_date') - data_check_string = ['{}={}'.format(k, v) + data_check_string = [f'{k}={v}' for k, v in response.items() if k != 'hash'] data_check_string = '\n'.join(sorted(data_check_string)) secret_key = hashlib.sha256(bot_token.encode()).digest() @@ -43,7 +43,7 @@ def extra_data(self, user, uid, response, details=None, *args, **kwargs): def get_user_details(self, response): first_name = response.get('first_name', '') last_name = response.get('last_name', '') - fullname = '{} {}'.format(first_name, last_name).strip() + fullname = f'{first_name} {last_name}'.strip() return { 'username': response.get('username') or response[self.ID_KEY], 'first_name': first_name, diff --git a/social_core/backends/twilio.py b/social_core/backends/twilio.py index 449c7726..756ad523 100644 --- a/social_core/backends/twilio.py +++ b/social_core/backends/twilio.py @@ -28,7 +28,7 @@ def auth_url(self): callback = self.strategy.absolute_uri(self.redirect_uri) callback = sub(r'^https', 'http', callback) query = urlencode({'cb': callback}) - return 'https://www.twilio.com/authorize/{0}?{1}'.format(key, query) + return f'https://www.twilio.com/authorize/{key}?{query}' def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" diff --git a/social_core/backends/uber.py b/social_core/backends/uber.py index 21bd9c51..b2aab2b3 100644 --- a/social_core/backends/uber.py +++ b/social_core/backends/uber.py @@ -34,6 +34,6 @@ def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" response = kwargs.pop('response') return self.get_json('https://api.uber.com/v1/me', headers={ - 'Authorization': '{0} {1}'.format(response.get('token_type'), + 'Authorization': '{} {}'.format(response.get('token_type'), access_token) }) diff --git a/social_core/backends/universe.py b/social_core/backends/universe.py index c67e1c96..7869a801 100644 --- a/social_core/backends/universe.py +++ b/social_core/backends/universe.py @@ -31,5 +31,5 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json(self.USER_INFO_URL, headers={ - 'Authorization': 'Bearer {}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }) diff --git a/social_core/backends/upwork.py b/social_core/backends/upwork.py index be68d0de..37f3e68a 100644 --- a/social_core/backends/upwork.py +++ b/social_core/backends/upwork.py @@ -22,7 +22,7 @@ def get_user_details(self, response): auth_user = response.get('auth_user', {}) first_name = auth_user.get('first_name') last_name = auth_user.get('last_name') - fullname = '{} {}'.format(first_name, last_name) + fullname = f'{first_name} {last_name}' profile_url = info.get('profile_url', '') username = profile_url.rsplit('/')[-1].replace('~', '') return { diff --git a/social_core/backends/vend.py b/social_core/backends/vend.py index 99e45633..1ba046cc 100644 --- a/social_core/backends/vend.py +++ b/social_core/backends/vend.py @@ -32,8 +32,8 @@ def get_user_details(self, response): def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" prefix = kwargs['response']['domain_prefix'] - url = 'https://{0}.vendhq.com/api/users'.format(prefix) + url = f'https://{prefix}.vendhq.com/api/users' data = self.get_json(url, headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }) return data['users'][0] if data.get('users') else {} diff --git a/social_core/backends/yahoo.py b/social_core/backends/yahoo.py index d6ff380e..68bd2d90 100644 --- a/social_core/backends/yahoo.py +++ b/social_core/backends/yahoo.py @@ -102,7 +102,7 @@ def user_data(self, access_token, *args, **kwargs): url = 'https://api.login.yahoo.com/openid/v1/userinfo' return self.get_json(url, headers={ - 'Authorization': 'Bearer {0}'.format(access_token) + 'Authorization': f'Bearer {access_token}' }, method='GET') @handle_http_errors diff --git a/social_core/backends/zoom.py b/social_core/backends/zoom.py index 3c68a725..e26b821b 100644 --- a/social_core/backends/zoom.py +++ b/social_core/backends/zoom.py @@ -54,7 +54,7 @@ def auth_complete_params(self, state=None): def auth_headers(self): return { 'Authorization': b'Basic ' + base64.urlsafe_b64encode( - '{0}:{1}'.format(*self.get_key_and_secret()).encode() + '{}:{}'.format(*self.get_key_and_secret()).encode() ) } diff --git a/social_core/exceptions.py b/social_core/exceptions.py index 461fefa2..d5b38ccf 100644 --- a/social_core/exceptions.py +++ b/social_core/exceptions.py @@ -8,14 +8,14 @@ def __init__(self, backend_name): self.backend_name = backend_name def __str__(self): - return 'Incorrect authentication service "{0}"'.format( + return 'Incorrect authentication service "{}"'.format( self.backend_name ) class MissingBackend(WrongBackend): def __str__(self): - return 'Missing backend "{0}" entry'.format(self.backend_name) + return f'Missing backend "{self.backend_name}" entry' class NotAllowedToDisconnect(SocialAuthBaseException): @@ -36,7 +36,7 @@ def __str__(self): msg = super().__str__() if msg == 'access_denied': return 'Authentication process was canceled' - return 'Authentication failed: {0}'.format(msg) + return f'Authentication failed: {msg}' class AuthCanceled(AuthException): @@ -48,7 +48,7 @@ def __init__(self, *args, **kwargs): def __str__(self): msg = super().__str__() if msg: - return 'Authentication process canceled: {0}'.format(msg) + return f'Authentication process canceled: {msg}' return 'Authentication process canceled' @@ -56,14 +56,14 @@ class AuthUnknownError(AuthException): """Unknown auth process error.""" def __str__(self): msg = super().__str__() - return 'An unknown error happened while authenticating {0}'.format(msg) + return f'An unknown error happened while authenticating {msg}' class AuthTokenError(AuthException): """Auth token error.""" def __str__(self): msg = super().__str__() - return 'Token error: {0}'.format(msg) + return f'Token error: {msg}' class AuthMissingParameter(AuthException): @@ -73,7 +73,7 @@ def __init__(self, backend, parameter, *args, **kwargs): super().__init__(backend, *args, **kwargs) def __str__(self): - return 'Missing needed parameter {0}'.format(self.parameter) + return f'Missing needed parameter {self.parameter}' class AuthStateMissing(AuthException): diff --git a/social_core/strategy.py b/social_core/strategy.py index 9d7a7e59..4c0d74d4 100644 --- a/social_core/strategy.py +++ b/social_core/strategy.py @@ -116,7 +116,7 @@ def random_string(self, length=12, chars=ALLOWED_CHARS): random.SystemRandom() except NotImplementedError: key = self.setting('SECRET_KEY', '') - seed = '{0}{1}{2}'.format(random.getstate(), time.time(), key) + seed = f'{random.getstate()}{time.time()}{key}' random.seed(hashlib.sha256(seed.encode()).digest()) return ''.join([random.choice(chars) for i in range(length)]) diff --git a/social_core/tests/backends/legacy.py b/social_core/tests/backends/legacy.py index 45443cea..8393d3ba 100644 --- a/social_core/tests/backends/legacy.py +++ b/social_core/tests/backends/legacy.py @@ -13,14 +13,14 @@ class BaseLegacyTest(BaseBackendTest): def setUp(self): super().setUp() self.strategy.set_settings({ - 'SOCIAL_AUTH_{0}_FORM_URL'.format(self.name): - self.strategy.build_absolute_uri('/login/{0}'.format( + f'SOCIAL_AUTH_{self.name}_FORM_URL': + self.strategy.build_absolute_uri('/login/{}'.format( self.backend.name)) }) def extra_settings(self): - return {'SOCIAL_AUTH_{0}_FORM_URL'.format(self.name): - '/login/{0}'.format(self.backend.name)} + return {f'SOCIAL_AUTH_{self.name}_FORM_URL': + f'/login/{self.backend.name}'} def do_start(self): start_url = self.strategy.build_absolute_uri(self.backend.start().url) diff --git a/social_core/tests/backends/test_auth0.py b/social_core/tests/backends/test_auth0.py index be8406eb..332f4535 100644 --- a/social_core/tests/backends/test_auth0.py +++ b/social_core/tests/backends/test_auth0.py @@ -42,7 +42,7 @@ class Auth0OAuth2Test(OAuth2Test): 'name': 'John Doe', 'picture': 'http://example.com/image.png', 'sub': '123456', - 'iss': 'https://{}/'.format(DOMAIN), + 'iss': f'https://{DOMAIN}/', }, JWK_KEY, algorithm='RS256') }) expected_username = 'foobar' diff --git a/social_core/tests/backends/test_discourse.py b/social_core/tests/backends/test_discourse.py index a6c25ff5..43524904 100644 --- a/social_core/tests/backends/test_discourse.py +++ b/social_core/tests/backends/test_discourse.py @@ -35,9 +35,9 @@ def do_start(self): # NOTE: the signature was verified using the 'foo' key, like so: # hmac.new('foo', sso, sha256).hexdigest() sig = '04063f17c99a97b1a765c1e0d7bbb61afb8471d79a39ddcd6af5ba3c93eb10e1' - response_query_params = 'sso={0}&sig={1}'.format(sso, sig) + response_query_params = f'sso={sso}&sig={sig}' - response_url = '{0}?{1}'.format(return_url, response_query_params) + response_url = f'{return_url}?{response_query_params}' HTTPretty.register_uri( HTTPretty.GET, start_url, status=301, location=response_url ) diff --git a/social_core/tests/backends/test_open_id_connect.py b/social_core/tests/backends/test_open_id_connect.py index 4511ae4f..928571f3 100644 --- a/social_core/tests/backends/test_open_id_connect.py +++ b/social_core/tests/backends/test_open_id_connect.py @@ -79,9 +79,9 @@ def jwks(_request, _uri, headers): def extra_settings(self): settings = super().extra_settings() settings.update({ - 'SOCIAL_AUTH_{0}_KEY'.format(self.name): self.client_key, - 'SOCIAL_AUTH_{0}_SECRET'.format(self.name): self.client_secret, - 'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name): + f'SOCIAL_AUTH_{self.name}_KEY': self.client_key, + f'SOCIAL_AUTH_{self.name}_SECRET': self.client_secret, + f'SOCIAL_AUTH_{self.name}_ID_TOKEN_DECRYPTION_KEY': self.client_secret }) return settings diff --git a/social_core/tests/test_utils.py b/social_core/tests/test_utils.py index 19543f19..a54b296d 100644 --- a/social_core/tests/test_utils.py +++ b/social_core/tests/test_utils.py @@ -45,7 +45,7 @@ def test_valid_relative_redirect(self): def test_multiple_hosts(self): allowed_hosts = ['myapp1.com', 'myapp2.com'] for host in allowed_hosts: - url = 'http://{}/path/'.format(host) + url = f'http://{host}/path/' self.assertEqual(sanitize_redirect(allowed_hosts, url), url) def test_multiple_hosts_wrong_host(self): diff --git a/social_core/utils.py b/social_core/utils.py index 46b14b39..1970ef7f 100644 --- a/social_core/utils.py +++ b/social_core/utils.py @@ -265,7 +265,7 @@ def append_slash(url): 'http://www.example.com/api/user/1/' """ if url and not url.endswith('/'): - url = '{0}/'.format(url) + url = f'{url}/' return url From 41346aa192b65010a34069f5c3cc264dff505504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 6 Dec 2021 11:36:05 +0100 Subject: [PATCH 15/21] pyupgrade: Upgrade legacy code - use OSError instead of IOError - remove not needed r argument to open() --- social_core/backends/professionali.py | 2 +- social_core/backends/vk.py | 2 +- social_core/tests/backends/test_saml.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/social_core/backends/professionali.py b/social_core/backends/professionali.py index 6cb00094..4e7e286c 100644 --- a/social_core/backends/professionali.py +++ b/social_core/backends/professionali.py @@ -44,7 +44,7 @@ def user_data(self, access_token, response, *args, **kwargs): } try: return self.get_json(url, params)[0] - except (TypeError, KeyError, IOError, ValueError, IndexError): + except (TypeError, KeyError, OSError, ValueError, IndexError): return None def get_json(self, url, *args, **kwargs): diff --git a/social_core/backends/vk.py b/social_core/backends/vk.py index a32f04f7..af6353e6 100644 --- a/social_core/backends/vk.py +++ b/social_core/backends/vk.py @@ -196,5 +196,5 @@ def vk_api(backend, method, data): try: return backend.get_json(url, params=data) - except (TypeError, KeyError, IOError, ValueError, IndexError): + except (TypeError, KeyError, OSError, ValueError, IndexError): return None diff --git a/social_core/tests/backends/test_saml.py b/social_core/tests/backends/test_saml.py index 985acf87..40a0837f 100644 --- a/social_core/tests/backends/test_saml.py +++ b/social_core/tests/backends/test_saml.py @@ -35,7 +35,7 @@ class SAMLTest(BaseBackendTest): def extra_settings(self): name = path.join(DATA_DIR, 'saml_config.json') - with open(name, 'r') as config_file: + with open(name) as config_file: config_str = config_file.read() return json.loads(config_str) @@ -60,7 +60,7 @@ def install_http_intercepts(self, start_url, return_url): # data in the query string. A pre-recorded correct response # is kept in this .txt file: name = path.join(DATA_DIR, 'saml_response.txt') - with open(name, 'r') as response_file: + with open(name) as response_file: response_url = response_file.read() HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=response_url) From 1ae36292aeb27b61d133499dcf473be7725d1f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 6 Dec 2021 11:46:29 +0100 Subject: [PATCH 16/21] pyupgrade: Upgrade setup.py - use OSError instead of IOError - remove no longer needed coding - utilize f-string --- setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index e94f7674..897f4214 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import re from os.path import join, dirname @@ -21,7 +20,7 @@ def long_description(): try: return open(join(dirname(__file__), 'README.md')).read() - except IOError: + except OSError: return None @@ -33,12 +32,12 @@ def read_version(): def read_requirements(filename): - with open(filename, 'r') as file: + with open(filename) as file: return [line for line in file.readlines() if not line.startswith('-')] def read_tests_requirements(filename): - return read_requirements('social_core/tests/{0}'.format(filename)) + return read_requirements(f'social_core/tests/{filename}') requirements = read_requirements('requirements-base.txt') From 34ab7905c38bbdcb5f83578419a3d91324c25a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 6 Dec 2021 11:48:16 +0100 Subject: [PATCH 17/21] backends: Convert discord to unix new lines Make it consistent with rest of the code. --- social_core/backends/discord.py | 62 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/social_core/backends/discord.py b/social_core/backends/discord.py index 24cd847f..25d09127 100644 --- a/social_core/backends/discord.py +++ b/social_core/backends/discord.py @@ -1,31 +1,31 @@ -""" -Discord Auth OAuth2 backend, docs at: - https://discord.com/developers/docs/topics/oauth2 -""" -from .oauth import BaseOAuth2 - - -class DiscordOAuth2(BaseOAuth2): - name = 'discord' - HOSTNAME = 'discord.com' - AUTHORIZATION_URL = 'https://%s/api/oauth2/authorize' % HOSTNAME - ACCESS_TOKEN_URL = 'https://%s/api/oauth2/token' % HOSTNAME - ACCESS_TOKEN_METHOD = 'POST' - REVOKE_TOKEN_URL = 'https://%s/api/oauth2/token/revoke' % HOSTNAME - REVOKE_TOKEN_METHOD = 'GET' - DEFAULT_SCOPE = ['identify'] - SCOPE_SEPARATOR = '+' - REDIRECT_STATE = False - EXTRA_DATA = [ - ('expires_in', 'expires'), - ('refresh_token', 'refresh_token') - ] - - def get_user_details(self, response): - return {'username': response.get('username'), - 'email': response.get('email') or ''} - - def user_data(self, access_token, *args, **kwargs): - url = 'https://%s/api/users/@me' % self.HOSTNAME - auth_header = {'Authorization': 'Bearer %s' % access_token} - return self.get_json(url, headers=auth_header) +""" +Discord Auth OAuth2 backend, docs at: + https://discord.com/developers/docs/topics/oauth2 +""" +from .oauth import BaseOAuth2 + + +class DiscordOAuth2(BaseOAuth2): + name = 'discord' + HOSTNAME = 'discord.com' + AUTHORIZATION_URL = 'https://%s/api/oauth2/authorize' % HOSTNAME + ACCESS_TOKEN_URL = 'https://%s/api/oauth2/token' % HOSTNAME + ACCESS_TOKEN_METHOD = 'POST' + REVOKE_TOKEN_URL = 'https://%s/api/oauth2/token/revoke' % HOSTNAME + REVOKE_TOKEN_METHOD = 'GET' + DEFAULT_SCOPE = ['identify'] + SCOPE_SEPARATOR = '+' + REDIRECT_STATE = False + EXTRA_DATA = [ + ('expires_in', 'expires'), + ('refresh_token', 'refresh_token') + ] + + def get_user_details(self, response): + return {'username': response.get('username'), + 'email': response.get('email') or ''} + + def user_data(self, access_token, *args, **kwargs): + url = 'https://%s/api/users/@me' % self.HOSTNAME + auth_header = {'Authorization': 'Bearer %s' % access_token} + return self.get_json(url, headers=auth_header) From be6f4bb47e225bc9e152bf6e374a49df98de2201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Mon, 6 Dec 2021 11:49:56 +0100 Subject: [PATCH 18/21] ci: Add pre-commit.ci configuration It can be extended in the future to include more code formatting tools. --- .pre-commit-config.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..8e13fd73 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-merge-conflict + - id: check-json + - id: debug-statements + - id: mixed-line-ending + args: [--fix=lf] + - repo: https://github.com/asottile/pyupgrade + rev: v2.29.1 + hooks: + - id: pyupgrade + args: [--py36-plus] + - repo: meta + hooks: + - id: check-hooks-apply + - id: check-useless-excludes From 911957bc2c0cf0f2e3419e1f1e7caaf25059dc6d Mon Sep 17 00:00:00 2001 From: LincolnPuzey Date: Fri, 10 Dec 2021 16:01:51 +0800 Subject: [PATCH 19/21] Don't try to import cPickle module Instead just import `pickle` module. Python3+ automatically uses cPickle if it is available. --- social_core/store.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/social_core/store.py b/social_core/store.py index aa17223a..0851aed7 100644 --- a/social_core/store.py +++ b/social_core/store.py @@ -1,10 +1,6 @@ +import pickle import time -try: - import cPickle as pickle -except ImportError: - import pickle - from openid.store.interface import OpenIDStore as BaseOpenIDStore from openid.store.nonce import SKEW From 5c8f653ed04de0724e55de096a13e0d08da86fc0 Mon Sep 17 00:00:00 2001 From: Eric B Date: Thu, 22 Apr 2021 13:22:53 -0700 Subject: [PATCH 20/21] Fix Properly join configuration URLs A Change was made in 3.4.0.0 commit (0a49eb5) which creates an improper "well-known" URL. Change it back please --- social_core/backends/okta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/social_core/backends/okta.py b/social_core/backends/okta.py index 3e0e2cbc..d37e5f1c 100644 --- a/social_core/backends/okta.py +++ b/social_core/backends/okta.py @@ -24,7 +24,7 @@ def _url(self, path): def oidc_config(self): return self.get_json( self._url( - './.well-known/openid-configuration?client_id={}'.format( + '/.well-known/openid-configuration?client_id={}'.format( self.setting('KEY') ) ) From 1ea27e8989657bb35dd37b6ee2e038e1358fbc96 Mon Sep 17 00:00:00 2001 From: Lukasz Lacinski Date: Thu, 18 Mar 2021 01:11:16 -0500 Subject: [PATCH 21/21] Set a JWT signature algorithm for the Globus backend to RS512 --- social_core/backends/globus.py | 1 + 1 file changed, 1 insertion(+) diff --git a/social_core/backends/globus.py b/social_core/backends/globus.py index a9368081..818f0fbf 100644 --- a/social_core/backends/globus.py +++ b/social_core/backends/globus.py @@ -10,6 +10,7 @@ class GlobusOpenIdConnect(OpenIdConnectAuth): name = 'globus' OIDC_ENDPOINT = 'https://auth.globus.org' + JWT_ALGORITHMS = ['RS256', 'RS512'] EXTRA_DATA = [ ('expires_in', 'expires_in', True), ('refresh_token', 'refresh_token', True),