Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-social-auth-core for openSUSE:Factory checked in at 2022-06-15 00:33:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-social-auth-core (Old) and /work/SRC/openSUSE:Factory/.python-social-auth-core.new.1548 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-social-auth-core" Wed Jun 15 00:33:40 2022 rev:17 rq:982613 version:4.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-social-auth-core/python-social-auth-core.changes 2022-04-20 17:03:54.335187865 +0200 +++ /work/SRC/openSUSE:Factory/.python-social-auth-core.new.1548/python-social-auth-core.changes 2022-06-15 00:34:41.162736924 +0200 @@ -1,0 +2,15 @@ +Mon Jun 13 10:44:07 UTC 2022 - Ben Greiner <c...@bnavigator.de> + +- Update to 4.3.0 + * Add backend for Hashicorp Vault OIDC backend + * Add generic OpenID Connect backend + * Add Grafana OAuth2 backend + * Add MusicBrainz OAuth2 backend + * Fixed redirect state for Keycloak backend + * Add fallback to RSA256 in OpenID Connect when alg is not set + * Fixed Azure backend so it can be used with all Azure authority + hosts +- Don't test extra saml for which the distro does not have the right + packages + +------------------------------------------------------------------- Old: ---- social-core-4.2.0.tar.gz New: ---- social-core-4.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-social-auth-core.spec ++++++ --- /var/tmp/diff_new_pack.BHrO4I/_old 2022-06-15 00:34:42.358738681 +0200 +++ /var/tmp/diff_new_pack.BHrO4I/_new 2022-06-15 00:34:42.362738687 +0200 @@ -17,42 +17,49 @@ # -%{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 %define modname social-core +# saml is optional: packages in TW and Leap not compatible +%bcond_with saml + Name: python-social-auth-core -Version: 4.2.0 +Version: 4.3.0 Release: 0 Summary: Python Social Auth Core License: BSD-3-Clause URL: https://github.com/python-social-auth/social-core Source: https://github.com/python-social-auth/%{modname}/archive/%{version}.tar.gz#/%{modname}-%{version}.tar.gz BuildRequires: %{python_module PyJWT >= 2.0.0} -BuildRequires: %{python_module coverage >= 3.6} BuildRequires: %{python_module cryptography >= 2.1.1} BuildRequires: %{python_module defusedxml >= 0.5.0} -BuildRequires: %{python_module httpretty} BuildRequires: %{python_module oauthlib >= 1.0.3} -BuildRequires: %{python_module pytest} -BuildRequires: %{python_module python-jose >= 3.0.0} BuildRequires: %{python_module python3-openid >= 3.0.10} -BuildRequires: %{python_module python3-saml >= 1.2.1} BuildRequires: %{python_module requests >= 2.9.1} BuildRequires: %{python_module requests-oauthlib >= 0.6.1} BuildRequires: %{python_module setuptools} BuildRequires: ca-certificates BuildRequires: fdupes BuildRequires: python-rpm-macros +# SECTION test requirements +BuildRequires: %{python_module pytest} +BuildRequires: %{python_module httpretty >= 0.9.6} +#/SECTION +# SECTION optional requirements for tests +BuildRequires: %{python_module python-jose >= 3.0.0} +%if %{with saml} +BuildRequires: %{python_module lxml < 4.7} +BuildRequires: %{python_module python3-saml >= 1.2.1} +%endif +#/SECTION Requires: python-PyJWT >= 2.0.0 Requires: python-cryptography >= 2.1.1 +Requires: python-defusedxml >= 0.5.0 Requires: python-oauthlib >= 1.0.3 -Requires: python-python-jose >= 3.0.0 +Requires: python-python3-openid >= 3.0.10 Requires: python-requests >= 2.9.1 Requires: python-requests-oauthlib >= 0.6.1 +Recommends: python-python-jose >= 3.0.0 BuildArch: noarch -Requires: python-defusedxml >= 0.5.0 -Requires: python-python3-openid >= 3.0.10 -Recommends: python-python3-saml >= 1.2.1 %python_subpackages %description @@ -75,14 +82,15 @@ %python_expand %fdupes %{buildroot}%{$python_sitelib} %check -# python3 only: assertRaisesRegexp -> assertRaisesRegex -# skipped tests are online based -rm -rf _build.python2 -%pytest -k 'not (test_login or test_partial_pipeline)' +%if !%{with saml} +donttest+=" or test_saml" +%endif +%pytest -k "not (dummyprefix $donttest)" %files %{python_files} %doc CHANGELOG.md README.md %license LICENSE -%{python_sitelib}/* +%{python_sitelib}/social_core +%{python_sitelib}/social_auth_core-%{version}*-info %changelog ++++++ social-core-4.2.0.tar.gz -> social-core-4.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/CHANGELOG.md new/social-core-4.3.0/CHANGELOG.md --- old/social-core-4.2.0/CHANGELOG.md 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/CHANGELOG.md 2022-06-13 09:08:54.000000000 +0200 @@ -5,6 +5,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [4.3.0](https://github.com/python-social-auth/social-core/releases/tag/4.3.0) - 2022-06-13 + +### Added +- Add backend for Hashicorp Vault OIDC backend +- Add generic OpenID Connect backend +- Add Grafana OAuth2 backend +- Add MusicBrainz OAuth2 backend + +### Changed +- Fixed redirect state for Keycloak backend +- Add fallback to RSA256 in OpenID Connect when alg is not set +- Fixed Azure backend so it can be used with all Azure authority hosts + + ## [4.2.0](https://github.com/python-social-auth/social-core/releases/tag/4.2.0) - 2022-01-17 ### Added diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/__init__.py new/social-core-4.3.0/social_core/__init__.py --- old/social-core-4.2.0/social_core/__init__.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/__init__.py 2022-06-13 09:08:54.000000000 +0200 @@ -1 +1 @@ -__version__ = '4.2.0' +__version__ = '4.3.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/azuread.py new/social-core-4.3.0/social_core/backends/azuread.py --- old/social-core-4.2.0/social_core/backends/azuread.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/azuread.py 2022-06-13 09:08:54.000000000 +0200 @@ -40,9 +40,9 @@ class AzureADOAuth2(BaseOAuth2): name = 'azuread-oauth2' SCOPE_SEPARATOR = ' ' - AUTHORIZATION_URL = \ - 'https://login.microsoftonline.com/common/oauth2/authorize' - ACCESS_TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/token' + BASE_URL = 'https://{authority_host}/{tenant_id}' + AUTHORIZATION_URL = '{base_url}/oauth2/authorize' + ACCESS_TOKEN_URL = '{base_url}/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False DEFAULT_SCOPE = ['openid', 'profile', 'user_impersonation', 'email'] @@ -58,6 +58,24 @@ ('token_type', 'token_type') ] + @property + def authority_host(self): + return self.setting('AUTHORITY_HOST', 'login.microsoftonline.com') + + @property + def tenant_id(self): + return 'common' + + @property + def base_url(self): + return self.BASE_URL.format(authority_host=self.authority_host, tenant_id=self.tenant_id) + + def authorization_url(self): + return self.AUTHORIZATION_URL.format(base_url=self.base_url) + + def access_token_url(self): + return self.ACCESS_TOKEN_URL.format(base_url=self.base_url) + def get_user_id(self, details, response): """Use upn as unique id""" return response.get('upn') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/azuread_b2c.py new/social-core-4.3.0/social_core/backends/azuread_b2c.py --- old/social-core-4.2.0/social_core/backends/azuread_b2c.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/azuread_b2c.py 2022-06-13 09:08:54.000000000 +0200 @@ -53,7 +53,6 @@ class AzureADB2COAuth2(AzureADOAuth2): name = 'azuread-b2c-oauth2' - BASE_URL = 'https://login.microsoftonline.com/{tenant_id}' AUTHORIZATION_URL = '{base_url}/oauth2/v2.0/authorize' OPENID_CONFIGURATION_URL = '{base_url}/v2.0/.well-known/openid-configuration?p={policy}' ACCESS_TOKEN_URL = '{base_url}/oauth2/v2.0/token?p={policy}' @@ -84,10 +83,6 @@ 'required and should start with `b2c_`') return policy - @property - def base_url(self): - return self.BASE_URL.format(tenant_id=self.tenant_id) - def openid_configuration_url(self): return self.OPENID_CONFIGURATION_URL.format(base_url=self.base_url, policy=self.policy) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/azuread_tenant.py new/social-core-4.3.0/social_core/backends/azuread_tenant.py --- old/social-core-4.2.0/social_core/backends/azuread_tenant.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/azuread_tenant.py 2022-06-13 09:08:54.000000000 +0200 @@ -47,27 +47,18 @@ class AzureADTenantOAuth2(AzureADOAuth2): name = 'azuread-tenant-oauth2' OPENID_CONFIGURATION_URL = \ - 'https://login.microsoftonline.com/{tenant_id}/.well-known/openid-configuration' - AUTHORIZATION_URL = \ - 'https://login.microsoftonline.com/{tenant_id}/oauth2/authorize' - ACCESS_TOKEN_URL = 'https://login.microsoftonline.com/{tenant_id}/oauth2/token' - JWKS_URL = 'https://login.microsoftonline.com/{tenant_id}/discovery/keys' + '{base_url}/.well-known/openid-configuration' + JWKS_URL = '{base_url}/discovery/keys' @property def tenant_id(self): return self.setting('TENANT_ID', 'common') def openid_configuration_url(self): - return self.OPENID_CONFIGURATION_URL.format(tenant_id=self.tenant_id) - - def authorization_url(self): - return self.AUTHORIZATION_URL.format(tenant_id=self.tenant_id) - - def access_token_url(self): - return self.ACCESS_TOKEN_URL.format(tenant_id=self.tenant_id) + return self.OPENID_CONFIGURATION_URL.format(base_url=self.base_url) def jwks_url(self): - return self.JWKS_URL.format(tenant_id=self.tenant_id) + return self.JWKS_URL.format(base_url=self.base_url) def get_certificate(self, kid): # retrieve keys from jwks_url @@ -112,11 +103,10 @@ class AzureADV2TenantOAuth2(AzureADTenantOAuth2): name = 'azuread-v2-tenant-oauth2' - OPENID_CONFIGURATION_URL = \ - 'https://login.microsoftonline.com/{tenant_id}/v2.0/.well-known/openid-configuration' - AUTHORIZATION_URL = 'https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize' - ACCESS_TOKEN_URL = 'https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token' - JWKS_URL = 'https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys' + OPENID_CONFIGURATION_URL = '{base_url}/v2.0/.well-known/openid-configuration' + AUTHORIZATION_URL = '{base_url}/oauth2/v2.0/authorize' + ACCESS_TOKEN_URL = '{base_url}/oauth2/v2.0/token' + JWKS_URL = '{base_url}/discovery/v2.0/keys' DEFAULT_SCOPE = ['openid', 'profile', 'offline_access'] def get_user_id(self, details, response): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/grafana.py new/social-core-4.3.0/social_core/backends/grafana.py --- old/social-core-4.2.0/social_core/backends/grafana.py 1970-01-01 01:00:00.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/grafana.py 2022-06-13 09:08:54.000000000 +0200 @@ -0,0 +1,25 @@ +from social_core.backends.oauth import BaseOAuth2 + + +class GrafanaOAuth2(BaseOAuth2): + """Grafana OAuth authentication backend""" + name = 'grafana' + AUTHORIZATION_URL = 'https://grafana.com/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://grafana.com/api/oauth2/token' + ACCESS_TOKEN_METHOD = 'POST' + DEFAULT_SCOPE = ['profile', 'email'] + SCOPE_SEPARATOR = ',' + USER_DETAILS_URL = 'https://grafana.com/api/oauth2/user' + + def get_user_details(self, response): + """Return user details from Grafana account""" + return {'username': response.get('login'), + 'email': response.get('email') or '', + 'first_name': response.get('name'), + 'last_name': '-'} + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + return self.get_json(self.USER_DETAILS_URL, **{ + 'headers': {'Authorization': f'Bearer {access_token}'} + }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/keycloak.py new/social-core-4.3.0/social_core/backends/keycloak.py --- old/social-core-4.2.0/social_core/backends/keycloak.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/keycloak.py 2022-06-13 09:08:54.000000000 +0200 @@ -88,6 +88,7 @@ name = 'keycloak' ID_KEY = 'username' ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False def authorization_url(self): return self.setting('AUTHORIZATION_URL') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/musicbrainz.py new/social-core-4.3.0/social_core/backends/musicbrainz.py --- old/social-core-4.2.0/social_core/backends/musicbrainz.py 1970-01-01 01:00:00.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/musicbrainz.py 2022-06-13 09:08:54.000000000 +0200 @@ -0,0 +1,28 @@ +from social_core.backends.oauth import BaseOAuth2 + +class MusicBrainzOAuth2(BaseOAuth2): + """MusicBrainz OAuth authentication backend""" + name = 'musicbrainz' + AUTHORIZATION_URL = 'https://musicbrainz.org/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://musicbrainz.org/oauth2/token' + ACCESS_TOKEN_METHOD = 'POST' + ID_KEY = 'metabrainz_user_id' + DEFAULT_SCOPE = ['profile', 'email'] + SCOPE_SEPARATOR = ' ' + REDIRECT_STATE = False + EXTRA_DATA = [ + ('metabrainz_user_id', 'id'), + ('expires_in', 'expires'), + ] + + def get_user_details(self, response): + """Return user details from MusicBrainz account""" + return {'username': response.get('sub'), + 'email': response.get('email') or '', + 'first_name': response.get('sub')} + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + return self.get_json('https://musicbrainz.org/oauth2/userinfo', params={ + 'access_token': access_token + }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/oauth.py new/social-core-4.3.0/social_core/backends/oauth.py --- old/social-core-4.2.0/social_core/backends/oauth.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/oauth.py 2022-06-13 09:08:54.000000000 +0200 @@ -297,14 +297,18 @@ class BaseOAuth2(OAuthAuth): """Base class for OAuth2 providers. - OAuth2 draft details at: - http://tools.ietf.org/html/draft-ietf-oauth-v2-10 + OAuth2 details at: + https://datatracker.ietf.org/doc/html/rfc6749 """ REFRESH_TOKEN_URL = None REFRESH_TOKEN_METHOD = 'POST' RESPONSE_TYPE = 'code' REDIRECT_STATE = True STATE_PARAMETER = True + USE_BASIC_AUTH = False + + def use_basic_auth(self): + return self.USE_BASIC_AUTH def auth_params(self, state=None): client_id, client_secret = self.get_key_and_secret() @@ -332,16 +336,22 @@ return f'{self.authorization_url()}?{params}' def auth_complete_params(self, state=None): - client_id, client_secret = self.get_key_and_secret() - return { + params = { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code - 'client_id': client_id, - 'client_secret': client_secret, 'redirect_uri': self.get_redirect_uri(state) } + if not self.use_basic_auth(): + client_id, client_secret = self.get_key_and_secret() + params.update({ + 'client_id': client_id, + 'client_secret': client_secret, + }) + return params def auth_complete_credentials(self): + if self.use_basic_auth(): + return self.get_key_and_secret() return None def auth_headers(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/open_id_connect.py new/social-core-4.3.0/social_core/backends/open_id_connect.py --- old/social-core-4.2.0/social_core/backends/open_id_connect.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/open_id_connect.py 2022-06-13 09:08:54.000000000 +0200 @@ -26,7 +26,15 @@ """ Base class for Open ID Connect backends. Currently only the code response type is supported. + + It can also be directly instantiated as a generic OIDC backend. + To use it you will need to set at minimum: + + SOCIAL_AUTH_OIDC_OIDC_ENDPOINT = 'https://.....' # endpoint without /.well-known/openid-configuration + SOCIAL_AUTH_OIDC_KEY = '<client_id>' + SOCIAL_AUTH_OIDC_SECRET = '<client_secret>' """ + name = 'oidc' # Override OIDC_ENDPOINT in your subclass to enable autoconfig of OIDC OIDC_ENDPOINT = None ID_TOKEN_MAX_AGE = 600 @@ -37,46 +45,58 @@ REVOKE_TOKEN_METHOD = 'GET' ID_KEY = 'sub' USERNAME_KEY = 'preferred_username' + JWT_ALGORITHMS = ['RS256'] + JWT_DECODE_OPTIONS = dict() + # When these options are unspecified, server will choose via openid autoconfiguration ID_TOKEN_ISSUER = '' ACCESS_TOKEN_URL = '' AUTHORIZATION_URL = '' REVOKE_TOKEN_URL = '' USERINFO_URL = '' JWKS_URI = '' - JWT_ALGORITHMS = ['RS256'] - JWT_DECODE_OPTIONS = dict() + TOKEN_ENDPOINT_AUTH_METHOD = '' def __init__(self, *args, **kwargs): self.id_token = None super().__init__(*args, **kwargs) def authorization_url(self): - return self.AUTHORIZATION_URL or \ + return self.setting('AUTHORIZATION_URL', self.AUTHORIZATION_URL) or \ self.oidc_config().get('authorization_endpoint') def access_token_url(self): - return self.ACCESS_TOKEN_URL or \ + return self.setting('ACCESS_TOKEN_URL', self.ACCESS_TOKEN_URL) or \ self.oidc_config().get('token_endpoint') def revoke_token_url(self, token, uid): - return self.REVOKE_TOKEN_URL or \ + return self.setting('REVOKE_TOKEN_URL', self.REVOKE_TOKEN_URL) or \ self.oidc_config().get('revocation_endpoint') def id_token_issuer(self): - return self.ID_TOKEN_ISSUER or \ + return self.setting('ID_TOKEN_ISSUER', self.ID_TOKEN_ISSUER) or \ self.oidc_config().get('issuer') def userinfo_url(self): - return self.USERINFO_URL or \ + return self.setting('USERINFO_URL', self.USERINFO_URL) or \ self.oidc_config().get('userinfo_endpoint') def jwks_uri(self): - return self.JWKS_URI or \ + return self.setting('JWKS_URI', self.JWKS_URI) or \ self.oidc_config().get('jwks_uri') + def use_basic_auth(self): + method = self.setting('TOKEN_ENDPOINT_AUTH_METHOD', self.TOKEN_ENDPOINT_AUTH_METHOD) + if method: + return method == 'client_secret_basic' + methods = self.oidc_config().get('token_endpoint_auth_methods_supported', []) + return not methods or 'client_secret_basic' in methods + + def oidc_endpoint(self): + return self.setting('OIDC_ENDPOINT', self.OIDC_ENDPOINT) + @cache(ttl=86400) def oidc_config(self): - return self.get_json(self.OIDC_ENDPOINT + + return self.get_json(self.oidc_endpoint() + '/.well-known/openid-configuration') @cache(ttl=86400) @@ -159,6 +179,8 @@ for key in keys: if kid is None or kid == key.get('kid'): + if 'alg' not in key: + key['alg'] = self.setting('JWT_ALGORITHMS', self.JWT_ALGORITHMS)[0] rsakey = jwk.construct(key) message, encoded_sig = id_token.rsplit('.', 1) decoded_sig = base64url_decode(encoded_sig.encode('utf-8')) @@ -184,11 +206,11 @@ claims = jwt.decode( id_token, rsakey.to_pem().decode('utf-8'), - algorithms=self.JWT_ALGORITHMS, + algorithms=self.setting('JWT_ALGORITHMS', self.JWT_ALGORITHMS), audience=client_id, issuer=self.id_token_issuer(), access_token=access_token, - options=self.JWT_DECODE_OPTIONS, + options=self.setting('JWT_DECODE_OPTIONS', self.JWT_DECODE_OPTIONS), ) except ExpiredSignatureError: raise AuthTokenError(self, 'Signature has expired') @@ -219,11 +241,12 @@ }) def get_user_details(self, response): - username_key = self.setting('USERNAME_KEY', default=self.USERNAME_KEY) + username_key = self.setting('USERNAME_KEY', self.USERNAME_KEY) return { 'username': response.get(username_key), 'email': response.get('email'), 'fullname': response.get('name'), 'first_name': response.get('given_name'), 'last_name': response.get('family_name'), + 'groups': response.get('groups'), # not standardized but widely implemented } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/saml.py new/social-core-4.3.0/social_core/backends/saml.py --- old/social-core-4.2.0/social_core/backends/saml.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/saml.py 2022-06-13 09:08:54.000000000 +0200 @@ -269,7 +269,6 @@ 'https': 'on' if self.strategy.request_is_secure() else 'off', 'http_host': self.strategy.request_host(), 'script_name': self.strategy.request_path(), - 'server_port': self.strategy.request_port(), 'get_data': self.strategy.request_get(), 'post_data': self.strategy.request_post(), } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/backends/vault.py new/social-core-4.3.0/social_core/backends/vault.py --- old/social-core-4.2.0/social_core/backends/vault.py 1970-01-01 01:00:00.000000000 +0100 +++ new/social-core-4.3.0/social_core/backends/vault.py 2022-06-13 09:08:54.000000000 +0200 @@ -0,0 +1,18 @@ +""" +Backend for Hashicorp Vault OIDC Identity Provider in Vault 1.9+ +https://www.vaultproject.io/docs/secrets/identity/oidc-provider +""" +import base64 + +from social_core.backends.open_id_connect import OpenIdConnectAuth +from social_core.utils import cache + + + +class VaultOpenIdConnect(OpenIdConnectAuth): + """ + Vault OIDC authentication backend + + This is an alias for the generic OIDC backend + """ + name = 'vault' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/tests/backends/test_grafana.py new/social-core-4.3.0/social_core/tests/backends/test_grafana.py --- old/social-core-4.2.0/social_core/tests/backends/test_grafana.py 1970-01-01 01:00:00.000000000 +0100 +++ new/social-core-4.3.0/social_core/tests/backends/test_grafana.py 2022-06-13 09:08:54.000000000 +0200 @@ -0,0 +1,24 @@ +import json + +from .oauth import OAuth2Test + + +class GrafanaOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.grafana.GrafanaOAuth2' + user_data_url = 'https://grafana.com/api/oauth2/user' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer', + }) + user_data_body = json.dumps({ + 'login': 'fooboy', + 'email': 'f...@bar.com', + 'name': 'Foo Bar' + }) + expected_username = 'fooboy' + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/tests/backends/test_musicbrainz.py new/social-core-4.3.0/social_core/tests/backends/test_musicbrainz.py --- old/social-core-4.2.0/social_core/tests/backends/test_musicbrainz.py 1970-01-01 01:00:00.000000000 +0100 +++ new/social-core-4.3.0/social_core/tests/backends/test_musicbrainz.py 2022-06-13 09:08:54.000000000 +0200 @@ -0,0 +1,28 @@ +import json + +from httpretty import HTTPretty + +from ...exceptions import AuthFailed +from .oauth import OAuth2Test + + +class MusicBrainzAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.musicbrainz.MusicBrainzOAuth2' + user_data_url = 'https://musicbrainz.org/oauth2/userinfo' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'GjtKfJS6G4lupbQcCOiTKo4HcLXUgI1p', + 'expires_in': 3600, + 'token_type': 'Bearer', + 'refresh_token': 'GjSCBBjp4fnbE0AKo3uFu9qq9K2fFm4u' + }) + user_data_body = json.dumps({ + 'sub': 'foobar', + 'email': 'f...@bar.com', + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/tests/backends/test_okta.py new/social-core-4.3.0/social_core/tests/backends/test_okta.py --- old/social-core-4.2.0/social_core/tests/backends/test_okta.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/tests/backends/test_okta.py 2022-06-13 09:08:54.000000000 +0200 @@ -132,7 +132,8 @@ self.public_key = JWK_PUBLIC_KEY.copy() HTTPretty.register_uri(HTTPretty.GET, - self.backend.OIDC_ENDPOINT + '/.well-known/openid-configuration', + # Note: okta.py strips the /oauth2 prefix using urljoin with absolute path + 'https://dev-000000.oktapreview.com/.well-known/openid-configuration?client_id=a-key', status=200, body=self.openid_config_body) oidc_config = json.loads(self.openid_config_body) @@ -147,3 +148,14 @@ self.backend.JWKS_URI = oidc_config.get('jwks_uri') self.backend.ID_TOKEN_ISSUER = oidc_config.get('issuer') + + 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/tests/backends/test_open_id_connect.py new/social-core-4.3.0/social_core/tests/backends/test_open_id_connect.py --- old/social-core-4.2.0/social_core/tests/backends/test_open_id_connect.py 2022-01-17 14:26:01.000000000 +0100 +++ new/social-core-4.3.0/social_core/tests/backends/test_open_id_connect.py 2022-06-13 09:08:54.000000000 +0200 @@ -54,7 +54,11 @@ issuer = None # id_token issuer openid_config_body = None key = None - access_token_kwargs = {} + # Avoid sharing access_token_kwargs between different subclasses + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + cls.access_token_kwargs = getattr(cls, 'access_token_kwargs', {}) + def setUp(self): super().setUp() @@ -62,7 +66,7 @@ self.public_key = JWK_PUBLIC_KEY.copy() HTTPretty.register_uri(HTTPretty.GET, - self.backend.OIDC_ENDPOINT + '/.well-known/openid-configuration', + self.backend.oidc_endpoint() + '/.well-known/openid-configuration', status=200, body=self.openid_config_body ) @@ -200,12 +204,46 @@ self.authtoken_raised('Token error: Signature verification failed', kid='doesnotexist') +class BaseOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): + backend_path = \ + 'social_core.backends.open_id_connect.OpenIdConnectAuth' + 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 extra_settings(self): + settings = super().extra_settings() + settings.update({ + f'SOCIAL_AUTH_OIDC_OIDC_ENDPOINT': 'https://example.com/oidc', + }) + return settings + + 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() + + class ExampleOpenIdConnectAuth(OpenIdConnectAuth): name = 'example123' OIDC_ENDPOINT = 'https://example.com/oidc' -class OpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): +class ExampleOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): backend_path = \ 'social_core.tests.backends.test_open_id_connect.ExampleOpenIdConnectAuth' issuer = 'https://example.com' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/social-core-4.2.0/social_core/tests/backends/test_vault.py new/social-core-4.3.0/social_core/tests/backends/test_vault.py --- old/social-core-4.2.0/social_core/tests/backends/test_vault.py 1970-01-01 01:00:00.000000000 +0100 +++ new/social-core-4.3.0/social_core/tests/backends/test_vault.py 2022-06-13 09:08:54.000000000 +0200 @@ -0,0 +1,41 @@ +import json + +from httpretty import HTTPretty + +from .oauth import OAuth2Test +from .test_open_id_connect import OpenIdConnectTestMixin + +class VaultOpenIdConnectTest(OpenIdConnectTestMixin, OAuth2Test): + backend_path = \ + 'social_core.backends.vault.VaultOpenIdConnect' + issuer = 'https://vault.example.net:8200/v1/identity/oidc/provider/default' + openid_config_body = json.dumps({ + 'issuer': 'https://vault.example.net:8200/v1/identity/oidc/provider/default', + 'jwks_uri': 'https://vault.example.net:8200/v1/identity/oidc/provider/default/.well-known/keys', + 'authorization_endpoint': 'https://vault.example.net:8200/ui/vault/identity/oidc/provider/default/authorize', + 'token_endpoint': 'https://vault.example.net:8200/v1/identity/oidc/provider/default/token', + 'userinfo_endpoint': 'https://vault.example.net:8200/v1/identity/oidc/provider/default/userinfo', + 'request_uri_parameter_supported': False, + 'grant_types_supported': [ 'authorization_code' ], + 'token_endpoint_auth_methods_supported': [ 'client_secret_basic' ], + }) + + expected_username = 'cartman' + + def extra_settings(self): + settings = super().extra_settings() + settings.update({ + f'SOCIAL_AUTH_{self.name}_OIDC_ENDPOINT': 'https://vault.example.net:8200/v1/identity/oidc/provider/default', + }) + return settings + + 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()