Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-Authlib for openSUSE:Factory checked in at 2026-01-12 11:50:08 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Authlib (Old) and /work/SRC/openSUSE:Factory/.python-Authlib.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Authlib" Mon Jan 12 11:50:08 2026 rev:27 rq:1326736 version:1.6.6 Changes: -------- --- /work/SRC/openSUSE:Factory/python-Authlib/python-Authlib.changes 2025-10-13 15:37:36.854961288 +0200 +++ /work/SRC/openSUSE:Factory/.python-Authlib.new.1928/python-Authlib.changes 2026-01-12 11:50:28.066509416 +0100 @@ -1,0 +2,10 @@ +Fri Jan 9 08:29:28 UTC 2026 - John Paul Adrian Glaubitz <[email protected]> + +- Update to 1.6.6 (bsc#1256414, CVE-2025-68158) + * ``get_jwt_config`` takes a ``client`` parameter, #844. + * Fix incorrect signature when ``Content-Type`` is x-www-form-urlencoded + for OAuth 1.0 Client, #778. + * Use ``expires_in`` in ``OAuth2Token`` when ``expires_at`` is unparsable, #842. + * Always track ``state`` in session for OAuth client integrations. + +------------------------------------------------------------------- Old: ---- authlib-1.6.5.tar.gz New: ---- authlib-1.6.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Authlib.spec ++++++ --- /var/tmp/diff_new_pack.qgOH7e/_old 2026-01-12 11:50:29.046550790 +0100 +++ /var/tmp/diff_new_pack.qgOH7e/_new 2026-01-12 11:50:29.050550959 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-Authlib # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %define modname authlib %{?sle15_python_module_pythons} Name: python-Authlib -Version: 1.6.5 +Version: 1.6.6 Release: 0 Summary: Python library for building OAuth and OpenID Connect servers License: BSD-3-Clause ++++++ authlib-1.6.5.tar.gz -> authlib-1.6.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/authlib/__init__.py new/authlib-1.6.6/authlib/__init__.py --- old/authlib-1.6.5/authlib/__init__.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/authlib/__init__.py 2025-12-12 08:59:43.000000000 +0100 @@ -1,4 +1,5 @@ -"""authlib. +""" +authlib ~~~~~~~ The ultimate Python library in building OAuth 1.0, OAuth 2.0 and OpenID diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/authlib/consts.py new/authlib-1.6.6/authlib/consts.py --- old/authlib-1.6.5/authlib/consts.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/authlib/consts.py 2025-12-12 08:59:43.000000000 +0100 @@ -1,5 +1,5 @@ name = "Authlib" -version = "1.6.5" +version = "1.6.6" author = "Hsiaoming Yang <[email protected]>" homepage = "https://authlib.org" default_user_agent = f"{name}/{version} (+{homepage})" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/authlib/integrations/base_client/framework_integration.py new/authlib-1.6.6/authlib/integrations/base_client/framework_integration.py --- old/authlib-1.6.5/authlib/integrations/base_client/framework_integration.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/authlib/integrations/base_client/framework_integration.py 2025-12-12 08:59:43.000000000 +0100 @@ -20,11 +20,9 @@ def _clear_session_state(self, session): now = time.time() + prefix = f"_state_{self.name}" for key in dict(session): - if "_authlib_" in key: - # TODO: remove in future - session.pop(key) - elif key.startswith("_state_"): + if key.startswith(prefix): value = session[key] exp = value.get("exp") if not exp or exp < now: @@ -32,29 +30,32 @@ def get_state_data(self, session, state): key = f"_state_{self.name}_{state}" + session_data = session.get(key) + if not session_data: + return None if self.cache: - value = self._get_cache_data(key) + cached_value = self._get_cache_data(key) else: - value = session.get(key) - if value: - return value.get("data") + cached_value = session_data + if cached_value: + return cached_value.get("data") return None def set_state_data(self, session, state, data): key = f"_state_{self.name}_{state}" + now = time.time() if self.cache: self.cache.set(key, json.dumps({"data": data}), self.expires_in) + session[key] = {"exp": now + self.expires_in} else: - now = time.time() session[key] = {"data": data, "exp": now + self.expires_in} def clear_state_data(self, session, state): key = f"_state_{self.name}_{state}" if self.cache: self.cache.delete(key) - else: - session.pop(key, None) - self._clear_session_state(session) + session.pop(key, None) + self._clear_session_state(session) def update_token(self, token, refresh_token=None, access_token=None): raise NotImplementedError() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/authlib/oauth1/rfc5849/client_auth.py new/authlib-1.6.6/authlib/oauth1/rfc5849/client_auth.py --- old/authlib-1.6.5/authlib/oauth1/rfc5849/client_auth.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/authlib/oauth1/rfc5849/client_auth.py 2025-12-12 08:59:43.000000000 +0100 @@ -172,6 +172,8 @@ if CONTENT_TYPE_FORM_URLENCODED in content_type: headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED + if isinstance(body, bytes): + body = body.decode() uri, headers, body = self.sign(method, uri, headers, body) elif self.force_include_body: # To allow custom clients to work on non form encoded bodies. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/authlib/oauth2/rfc6749/wrappers.py new/authlib-1.6.6/authlib/oauth2/rfc6749/wrappers.py --- old/authlib-1.6.5/authlib/oauth2/rfc6749/wrappers.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/authlib/oauth2/rfc6749/wrappers.py 2025-12-12 08:59:43.000000000 +0100 @@ -4,15 +4,26 @@ class OAuth2Token(dict): def __init__(self, params): if params.get("expires_at"): - params["expires_at"] = int(params["expires_at"]) + try: + params["expires_at"] = int(params["expires_at"]) + except ValueError: + # If expires_at is not parseable, fall back to expires_in if available + # Otherwise leave expires_at untouched + if params.get("expires_in"): + params["expires_at"] = int(time.time()) + int(params["expires_in"]) + elif params.get("expires_in"): params["expires_at"] = int(time.time()) + int(params["expires_in"]) + super().__init__(params) def is_expired(self, leeway=60): expires_at = self.get("expires_at") if not expires_at: return None + # Only check expiration if expires_at is an integer + if not isinstance(expires_at, int): + return None # small timedelta to consider token as expired before it actually expires expiration_threshold = expires_at - leeway return expiration_threshold < time.time() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/authlib/oidc/core/grants/code.py new/authlib-1.6.6/authlib/oidc/core/grants/code.py --- old/authlib-1.6.5/authlib/oidc/core/grants/code.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/authlib/oidc/core/grants/code.py 2025-12-12 08:59:43.000000000 +0100 @@ -8,6 +8,7 @@ """ import logging +import warnings from authlib.oauth2.rfc6749 import OAuth2Request @@ -20,7 +21,7 @@ class OpenIDToken: - def get_jwt_config(self, grant): # pragma: no cover + def get_jwt_config(self, grant, client): # pragma: no cover """Get the JWT configuration for OpenIDCode extension. The JWT configuration will be used to generate ``id_token``. If ``alg`` is undefined, the ``id_token_signed_response_alg`` client @@ -29,15 +30,16 @@ will be used. Developers MUST implement this method in subclass, e.g.:: - def get_jwt_config(self, grant): + def get_jwt_config(self, grant, client): return { "key": read_private_key_file(key_path), - "alg": "RS256", + "alg": client.id_token_signed_response_alg or "RS256", "iss": "issuer-identity", "exp": 3600, } :param grant: AuthorizationCodeGrant instance + :param client: OAuth2 client instance :return: dict """ raise NotImplementedError() @@ -78,7 +80,17 @@ request: OAuth2Request = grant.request authorization_code = request.authorization_code - config = self.get_jwt_config(grant) + try: + config = self.get_jwt_config(grant, request.client) + except TypeError: + warnings.warn( + "get_jwt_config(self, grant) is deprecated and will be removed in version 1.8. " + "Use get_jwt_config(self, grant, client) instead.", + DeprecationWarning, + stacklevel=2, + ) + config = self.get_jwt_config(grant) + config["aud"] = self.get_audiences(request) # Per OpenID Connect Registration 1.0 Section 2: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/authlib/oidc/core/grants/implicit.py new/authlib-1.6.6/authlib/oidc/core/grants/implicit.py --- old/authlib-1.6.5/authlib/oidc/core/grants/implicit.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/authlib/oidc/core/grants/implicit.py 2025-12-12 08:59:43.000000000 +0100 @@ -1,4 +1,5 @@ import logging +import warnings from authlib.oauth2.rfc6749 import AccessDeniedError from authlib.oauth2.rfc6749 import ImplicitGrant @@ -36,19 +37,20 @@ """ raise NotImplementedError() - def get_jwt_config(self): + def get_jwt_config(self, client): """Get the JWT configuration for OpenIDImplicitGrant. The JWT configuration will be used to generate ``id_token``. Developers MUST implement this method in subclass, e.g.:: - def get_jwt_config(self): + def get_jwt_config(self, client): return { "key": read_private_key_file(key_path), - "alg": "RS256", + "alg": client.id_token_signed_response_alg or "RS256", "iss": "issuer-identity", "exp": 3600, } + :param client: OAuth2 client instance :return: dict """ raise NotImplementedError() @@ -143,7 +145,17 @@ return params def process_implicit_token(self, token, code=None): - config = self.get_jwt_config() + try: + config = self.get_jwt_config(self.request.client) + except TypeError: + warnings.warn( + "get_jwt_config(self) is deprecated and will be removed in version 1.8. " + "Use get_jwt_config(self, client) instead.", + DeprecationWarning, + stacklevel=2, + ) + config = self.get_jwt_config() + config["aud"] = self.get_audiences(self.request) config["nonce"] = self.request.payload.data.get("nonce") if code is not None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/docs/changelog.rst new/authlib-1.6.6/docs/changelog.rst --- old/authlib-1.6.5/docs/changelog.rst 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/docs/changelog.rst 2025-12-12 08:59:43.000000000 +0100 @@ -6,6 +6,16 @@ Here you can see the full list of changes between each Authlib release. +Version 1.6.6 +------------- + +**Released on Dec 12, 2025** + +- ``get_jwt_config`` takes a ``client`` parameter, :pr:`844`. +- Fix incorrect signature when ``Content-Type`` is x-www-form-urlencoded for OAuth 1.0 Client, :pr:`778`. +- Use ``expires_in`` in ``OAuth2Token`` when ``expires_at`` is unparsable, :pr:`842`. +- Always track ``state`` in session for OAuth client integrations. + Version 1.6.5 ------------- @@ -57,12 +67,16 @@ - Fix issue when :rfc:`RFC9207 <9207>` is enabled and the authorization endpoint response is not a redirection. :pr:`733` - Fix missing ``state`` parameter in authorization error responses. :issue:`525` -- Support for ``acr`` and ``amr`` claims in ``id_token``. :issue:`734` - Support for the ``none`` JWS algorithm. - Fix ``response_types`` strict order during dynamic client registration. :issue:`760` - Implement :rfc:`RFC9101 The OAuth 2.0 Authorization Framework: JWT-Secured Authorization Request (JAR) <9101>`. :issue:`723` - OIDC :class:`UserInfo endpoint <authlib.oidc.core.userinfo.UserInfoEndpoint>` support. :issue:`459` +**Breaking changes**: + +- Support for ``acr`` and ``amr`` claims in ``id_token``. :issue:`734` + The ``OAuth2AuthorizationCodeMixin`` must have a migration to support the new fields. + Version 1.5.2 ------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/docs/client/requests.rst new/authlib-1.6.6/docs/client/requests.rst --- old/authlib-1.6.5/docs/client/requests.rst 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/docs/client/requests.rst 2025-12-12 08:59:43.000000000 +0100 @@ -159,24 +159,13 @@ Self-signed certificate mutual-TLS method internet standard is defined in `RFC8705 Section 2.2`_ . -For specifics development purposes only, you may need to -**disable SSL verification**. +You can use the environment variables CURL_CA_BUNDLE and REQUESTS_CA_BUNDLE +to specify a CA certificate file for validating your self-signed certificate. -You can force all requests to disable SSL verification by setting -your environment variable ``CURL_CA_BUNDLE=""``. +.. code-block:: bash -This solutions works because Python requests (and most of the packages) -overwrites the default value for ssl verifications from environment -variables ``CURL_CA_BUNDLE`` and ``REQUESTS_CA_BUNDLE``. - -This hack will **only work** with ``CURL_CA_BUNDLE``, as you can see -in `requests/sessions.py`_ :: - - verify = (os.environ.get('REQUESTS_CA_BUNDLE') - or os.environ.get('CURL_CA_BUNDLE')) + REQUESTS_CA_BUNDLE=/path/to/ca-cert.pem Please remember to set the env variable only in you development environment. - .. _RFC8705 Section 2.2: https://tools.ietf.org/html/rfc8705#section-2.2 -.. _requests/sessions.py: https://github.com/requests/requests/blob/master/requests/sessions.py#L706 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/clients/test_flask/test_oauth_client.py new/authlib-1.6.6/tests/clients/test_flask/test_oauth_client.py --- old/authlib-1.6.5/tests/clients/test_flask/test_oauth_client.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/clients/test_flask/test_oauth_client.py 2025-12-12 08:59:43.000000000 +0100 @@ -150,9 +150,13 @@ assert resp.status_code == 302 url = resp.headers.get("Location") assert "oauth_token=foo" in url + session_data = session["_state_dev_foo"] + assert "exp" in session_data + assert "data" not in session_data with app.test_request_context("/?oauth_token=foo"): with mock.patch("requests.sessions.Session.send") as send: + session["_state_dev_foo"] = session_data send.return_value = mock_send_value("oauth_token=a&oauth_token_secret=b") token = client.authorize_access_token() assert token["oauth_token"] == "a" @@ -207,7 +211,44 @@ assert session.update_token is not None -def test_oauth2_authorize(): +def test_oauth2_authorize_cache(): + app = Flask(__name__) + app.secret_key = "!" + cache = SimpleCache() + oauth = OAuth(app, cache=cache) + client = oauth.register( + "dev", + client_id="dev", + client_secret="dev", + api_base_url="https://resource.test/api", + access_token_url="https://provider.test/token", + authorize_url="https://provider.test/authorize", + ) + with app.test_request_context(): + resp = client.authorize_redirect("https://client.test/callback") + assert resp.status_code == 302 + url = resp.headers.get("Location") + assert "state=" in url + state = dict(url_decode(urlparse.urlparse(url).query))["state"] + assert state is not None + session_data = session[f"_state_dev_{state}"] + assert "exp" in session_data + assert "data" not in session_data + + with app.test_request_context(path=f"/?code=a&state={state}"): + # session is cleared in tests + session[f"_state_dev_{state}"] = session_data + + with mock.patch("requests.sessions.Session.send") as send: + send.return_value = mock_send_value(get_bearer_token()) + token = client.authorize_access_token() + assert token["access_token"] == "a" + + with app.test_request_context(): + assert client.token is None + + +def test_oauth2_authorize_session(): app = Flask(__name__) app.secret_key = "!" oauth = OAuth(app) @@ -227,11 +268,13 @@ assert "state=" in url state = dict(url_decode(urlparse.urlparse(url).query))["state"] assert state is not None - data = session[f"_state_dev_{state}"] + session_data = session[f"_state_dev_{state}"] + assert "exp" in session_data + assert "data" in session_data with app.test_request_context(path=f"/?code=a&state={state}"): # session is cleared in tests - session[f"_state_dev_{state}"] = data + session[f"_state_dev_{state}"] = session_data with mock.patch("requests.sessions.Session.send") as send: send.return_value = mock_send_value(get_bearer_token()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/clients/test_requests/test_oauth2_session.py new/authlib-1.6.6/tests/clients/test_requests/test_oauth2_session.py --- old/authlib-1.6.5/tests/clients/test_requests/test_oauth2_session.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/clients/test_requests/test_oauth2_session.py 2025-12-12 08:59:43.000000000 +0100 @@ -323,6 +323,42 @@ assert not sess.token.is_expired(sess.leeway) +def test_expires_in_used_when_expires_at_unparseable(): + """Test that expires_in is used as fallback when expires_at is unparsable.""" + token = dict( + access_token="a", + token_type="bearer", + expires_in=3600, # 1 hour from now + expires_at="2024-01-01T00:00:00Z", # Unparsable - should fall back to expires_in + ) + sess = OAuth2Session("foo", token=token) + + # The token should use expires_in since expires_at is unparsable + # So it should be considered expired with leeway > 3600 + assert sess.token.is_expired(leeway=3700) is True + # And not expired with leeway < 3600 + assert sess.token.is_expired(leeway=0) is False + # expires_at should be calculated from expires_in + assert isinstance(sess.token["expires_at"], int) + + +def test_unparseable_expires_at_returns_none(): + """Test that is_expired returns None when expires_at is unparsable and no expires_in.""" + token = dict( + access_token="a", + token_type="bearer", + expires_at="2024-01-01T00:00:00Z", # Unparsable date string + ) + sess = OAuth2Session("foo", token=token) + + # Should return None since we can't determine expiration + assert sess.token.is_expired() is None + # The unparsable expires_at should be preserved in the token + assert sess.token["expires_at"] == "2024-01-01T00:00:00Z" + # No expires_in should be calculated + assert "expires_in" not in sess.token + + def test_token_expired(): token = dict(access_token="a", token_type="bearer", expires_at=100) sess = OAuth2Session("foo", token=token) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/django/test_oauth2/conftest.py new/authlib-1.6.6/tests/django/test_oauth2/conftest.py --- old/authlib-1.6.5/tests/django/test_oauth2/conftest.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/django/test_oauth2/conftest.py 2025-12-12 08:59:43.000000000 +0100 @@ -31,3 +31,19 @@ user.save() yield user user.delete() + + [email protected] +def token(user): + token = OAuth2Token( + user_id=user.pk, + client_id="client-id", + token_type="bearer", + access_token="a1", + refresh_token="r1", + scope="profile", + expires_in=3600, + ) + token.save() + yield token + token.delete() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/django/test_oauth2/test_refresh_token.py new/authlib-1.6.6/tests/django/test_oauth2/test_refresh_token.py --- old/authlib-1.6.5/tests/django/test_oauth2/test_refresh_token.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/django/test_oauth2/test_refresh_token.py 2025-12-12 08:59:43.000000000 +0100 @@ -51,22 +51,6 @@ client.delete() [email protected] -def token(user): - token = OAuth2Token( - user_id=user.pk, - client_id="client-id", - token_type="bearer", - access_token="a1", - refresh_token="r1", - scope="profile", - expires_in=3600, - ) - token.save() - yield token - token.delete() - - def test_invalid_client(factory, server): request = factory.post( "/oauth/token", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/django/test_oauth2/test_resource_protector.py new/authlib-1.6.6/tests/django/test_oauth2/test_resource_protector.py --- old/authlib-1.6.5/tests/django/test_oauth2/test_resource_protector.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/django/test_oauth2/test_resource_protector.py 2025-12-12 08:59:43.000000000 +0100 @@ -26,21 +26,6 @@ client.delete() [email protected] -def token(user, client): - token = OAuth2Token( - user_id=user.pk, - client_id=client.client_id, - token_type="bearer", - access_token="a1", - scope="profile", - expires_in=3600, - ) - token.save() - yield token - token.delete() - - def test_invalid_token(factory): @require_oauth("profile") def get_user_profile(request): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/django/test_oauth2/test_revocation_endpoint.py new/authlib-1.6.6/tests/django/test_oauth2/test_revocation_endpoint.py --- old/authlib-1.6.5/tests/django/test_oauth2/test_revocation_endpoint.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/django/test_oauth2/test_revocation_endpoint.py 2025-12-12 08:59:43.000000000 +0100 @@ -5,7 +5,6 @@ from authlib.integrations.django_oauth2 import RevocationEndpoint from .models import Client -from .models import OAuth2Token from .oauth2_server import create_basic_auth ENDPOINT_NAME = RevocationEndpoint.ENDPOINT_NAME @@ -31,22 +30,6 @@ client.delete() [email protected] -def token(user, client): - token = OAuth2Token( - user_id=user.pk, - client_id="client-id", - token_type="bearer", - access_token="a1", - refresh_token="r1", - scope="profile", - expires_in=3600, - ) - token.save() - yield token - token.delete() - - def test_invalid_client(factory, server): request = factory.post("/oauth/revoke") resp = server.create_endpoint_response(ENDPOINT_NAME, request) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/conftest.py new/authlib-1.6.6/tests/flask/test_oauth2/conftest.py --- old/authlib-1.6.5/tests/flask/test_oauth2/conftest.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/conftest.py 2025-12-12 08:59:43.000000000 +0100 @@ -6,6 +6,7 @@ from tests.flask.test_oauth2.oauth2_server import create_authorization_server from .models import Client +from .models import Token from .models import User @@ -83,3 +84,20 @@ @pytest.fixture def server(app): return create_authorization_server(app) + + [email protected] +def token(db): + token = Token( + user_id=1, + client_id="client-id", + token_type="bearer", + access_token="a1", + refresh_token="r1", + scope="profile", + expires_in=3600, + ) + db.session.add(token) + db.session.commit() + yield token + db.session.delete(token) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_client_configuration_endpoint.py new/authlib-1.6.6/tests/flask/test_oauth2/test_client_configuration_endpoint.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_client_configuration_endpoint.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_client_configuration_endpoint.py 2025-12-12 08:59:43.000000000 +0100 @@ -83,23 +83,6 @@ return client [email protected](autouse=True) -def token(db, user, client): - token = Token( - user_id=user.id, - client_id=client.id, - token_type="bearer", - access_token="a1", - refresh_token="r1", - scope="openid profile", - expires_in=3600, - ) - db.session.add(token) - db.session.commit() - yield token - db.session.delete(token) - - def test_read_client(test_client, client, token): assert client.client_name == "Authlib" headers = {"Authorization": f"bearer {token.access_token}"} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_introspection_endpoint.py new/authlib-1.6.6/tests/flask/test_oauth2/test_introspection_endpoint.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_introspection_endpoint.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_introspection_endpoint.py 2025-12-12 08:59:43.000000000 +0100 @@ -58,23 +58,6 @@ return client [email protected] -def token(db): - token = Token( - user_id=1, - client_id="client-id", - token_type="bearer", - access_token="a1", - refresh_token="r1", - scope="profile", - expires_in=3600, - ) - db.session.add(token) - db.session.commit() - yield db - db.session.delete(token) - - def test_invalid_client(test_client): rv = test_client.post("/oauth/introspect") resp = json.loads(rv.data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_oauth2_server.py new/authlib-1.6.6/tests/flask/test_oauth2/test_oauth2_server.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_oauth2_server.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_oauth2_server.py 2025-12-12 08:59:43.000000000 +0100 @@ -85,23 +85,7 @@ assert data["error"] == "unsupported_grant_type" [email protected](autouse=True) -def token(db): - token = Token( - user_id=1, - client_id="client-id", - token_type="bearer", - access_token="a1", - scope="profile", - expires_in=3600, - ) - db.session.add(token) - db.session.commit() - yield token - db.session.delete(token) - - -def test_invalid_token(test_client): +def test_invalid_token(test_client, token): rv = test_client.get("/user") assert rv.status_code == 401 resp = json.loads(rv.data) @@ -136,7 +120,7 @@ assert rv.status_code == 401 -def test_insufficient_token(test_client): +def test_insufficient_token(test_client, token): headers = create_bearer_header("a1") rv = test_client.get("/user/email", headers=headers) assert rv.status_code == 403 @@ -144,7 +128,7 @@ assert resp["error"] == "insufficient_scope" -def test_access_resource(test_client): +def test_access_resource(test_client, token): headers = create_bearer_header("a1") rv = test_client.get("/user", headers=headers) @@ -160,7 +144,7 @@ assert resp["status"] == "ok" -def test_scope_operator(test_client): +def test_scope_operator(test_client, token): headers = create_bearer_header("a1") rv = test_client.get("/operator-and", headers=headers) assert rv.status_code == 403 @@ -171,7 +155,7 @@ assert rv.status_code == 200 -def test_optional_token(test_client): +def test_optional_token(test_client, token): rv = test_client.get("/optional") assert rv.status_code == 200 resp = json.loads(rv.data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_openid_code_grant.py new/authlib-1.6.6/tests/flask/test_oauth2/test_openid_code_grant.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_openid_code_grant.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_openid_code_grant.py 2025-12-12 08:59:43.000000000 +0100 @@ -15,6 +15,7 @@ from authlib.oidc.core.grants import OpenIDCode as _OpenIDCode from tests.util import read_file_path +from .models import Client from .models import CodeGrantMixin from .models import exists_nonce from .models import save_authorization_code @@ -54,7 +55,7 @@ return save_authorization_code(code, request) class OpenIDCode(_OpenIDCode): - def get_jwt_config(self, grant): + def get_jwt_config(self, grant, client): key = current_app.config.get("OAUTH2_JWT_KEY") alg = current_app.config.get("OAUTH2_JWT_ALG") iss = current_app.config.get("OAUTH2_JWT_ISS") @@ -419,3 +420,64 @@ claims_options={"iss": {"value": "Authlib"}}, ) claims.validate() + + +def test_deprecated_get_jwt_config_signature(test_client, server, db, user): + """Using the old get_jwt_config(self, grant) signature should emit a DeprecationWarning.""" + + class DeprecatedOpenIDCode(_OpenIDCode): + def get_jwt_config(self, grant): + return {"key": "secret", "alg": "HS256", "iss": "Authlib", "exp": 3600} + + def exists_nonce(self, nonce, request): + return exists_nonce(nonce, request) + + def generate_user_info(self, user, scopes): + return user.generate_user_info(scopes) + + class AuthorizationCodeGrant(CodeGrantMixin, _AuthorizationCodeGrant): + def save_authorization_code(self, code, request): + return save_authorization_code(code, request) + + server.register_grant(AuthorizationCodeGrant, [DeprecatedOpenIDCode()]) + + client = Client( + user_id=user.id, + client_id="deprecated-client", + client_secret="secret", + ) + client.set_client_metadata( + { + "redirect_uris": ["https://client.test"], + "scope": "openid profile", + "response_types": ["code"], + "grant_types": ["authorization_code"], + } + ) + db.session.add(client) + db.session.commit() + + rv = test_client.post( + "/oauth/authorize", + data={ + "response_type": "code", + "client_id": "deprecated-client", + "state": "bar", + "scope": "openid profile", + "redirect_uri": "https://client.test", + "user_id": "1", + }, + ) + params = dict(url_decode(urlparse.urlparse(rv.location).query)) + code = params["code"] + + with pytest.warns(DeprecationWarning, match="get_jwt_config.*version 1.8"): + test_client.post( + "/oauth/token", + data={ + "grant_type": "authorization_code", + "redirect_uri": "https://client.test", + "code": code, + }, + headers=create_basic_header("deprecated-client", "secret"), + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_openid_hybrid_grant.py new/authlib-1.6.6/tests/flask/test_oauth2/test_openid_hybrid_grant.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_openid_hybrid_grant.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_openid_hybrid_grant.py 2025-12-12 08:59:43.000000000 +0100 @@ -26,7 +26,7 @@ return save_authorization_code(code, request) class OpenIDCode(_OpenIDCode): - def get_jwt_config(self, grant): + def get_jwt_config(self, grant, client): return dict(JWT_CONFIG) def exists_nonce(self, nonce, request): @@ -39,7 +39,7 @@ def save_authorization_code(self, code, request): return save_authorization_code(code, request) - def get_jwt_config(self): + def get_jwt_config(self, client): return dict(JWT_CONFIG) def exists_nonce(self, nonce, request): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_openid_implict_grant.py new/authlib-1.6.6/tests/flask/test_oauth2/test_openid_implict_grant.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_openid_implict_grant.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_openid_implict_grant.py 2025-12-12 08:59:43.000000000 +0100 @@ -5,9 +5,12 @@ from authlib.common.urls import url_decode from authlib.common.urls import urlparse from authlib.jose import JsonWebToken +from authlib.oauth2.rfc6749.requests import BasicOAuth2Payload +from authlib.oauth2.rfc6749.requests import OAuth2Request from authlib.oidc.core import ImplicitIDToken from authlib.oidc.core.grants import OpenIDImplicitGrant as _OpenIDImplicitGrant +from .models import Client from .models import exists_nonce authorize_url = "/oauth/authorize?response_type=token&client_id=client-id" @@ -16,7 +19,7 @@ @pytest.fixture(autouse=True) def server(server): class OpenIDImplicitGrant(_OpenIDImplicitGrant): - def get_jwt_config(self): + def get_jwt_config(self, client): alg = current_app.config.get("OAUTH2_JWT_ALG", "HS256") return dict(key="secret", alg=alg, iss="Authlib", exp=3600) @@ -259,3 +262,42 @@ ) params = dict(url_decode(urlparse.urlparse(rv.location).fragment)) assert params["error"] == "invalid_request" + + +def test_deprecated_get_jwt_config_signature(user): + """Using the old get_jwt_config(self) signature should emit a DeprecationWarning.""" + + class DeprecatedImplicitGrant(_OpenIDImplicitGrant): + def get_jwt_config(self): + return {"key": "secret", "alg": "HS256", "iss": "Authlib", "exp": 3600} + + def generate_user_info(self, user, scopes): + return user.generate_user_info(scopes) + + def exists_nonce(self, nonce, request): + return exists_nonce(nonce, request) + + client = Client( + user_id=user.id, + client_id="deprecated-client", + client_secret="secret", + ) + client.set_client_metadata( + { + "redirect_uris": ["https://client.test/callback"], + "scope": "openid profile", + "token_endpoint_auth_method": "none", + "response_types": ["id_token"], + } + ) + + request = OAuth2Request("POST", "https://server.test/authorize") + request.payload = BasicOAuth2Payload({"nonce": "test-nonce"}) + request.client = client + request.user = user + + grant = DeprecatedImplicitGrant(request, client) + token = {"scope": "openid", "expires_in": 3600} + + with pytest.warns(DeprecationWarning, match="get_jwt_config.*version 1.8"): + grant.process_implicit_token(token) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_password_grant.py new/authlib-1.6.6/tests/flask/test_oauth2/test_password_grant.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_password_grant.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_password_grant.py 2025-12-12 08:59:43.000000000 +0100 @@ -26,7 +26,7 @@ class IDToken(OpenIDToken): - def get_jwt_config(self, grant): + def get_jwt_config(self, grant, client): return { "iss": "Authlib", "key": "secret", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_refresh_token.py new/authlib-1.6.6/tests/flask/test_oauth2/test_refresh_token.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_refresh_token.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_refresh_token.py 2025-12-12 08:59:43.000000000 +0100 @@ -47,23 +47,6 @@ return client [email protected] -def token(db): - token = Token( - user_id=1, - client_id="client-id", - token_type="bearer", - access_token="a1", - refresh_token="r1", - scope="profile", - expires_in=3600, - ) - db.session.add(token) - db.session.commit() - yield token - db.session.delete(token) - - def test_invalid_client(test_client): rv = test_client.post( "/oauth/token", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/authlib-1.6.5/tests/flask/test_oauth2/test_revocation_endpoint.py new/authlib-1.6.6/tests/flask/test_oauth2/test_revocation_endpoint.py --- old/authlib-1.6.5/tests/flask/test_oauth2/test_revocation_endpoint.py 2025-10-02 15:31:28.000000000 +0200 +++ new/authlib-1.6.6/tests/flask/test_oauth2/test_revocation_endpoint.py 2025-12-12 08:59:43.000000000 +0100 @@ -34,23 +34,6 @@ return client [email protected] -def token(db, user): - token = Token( - user_id=1, - client_id="client-id", - token_type="bearer", - access_token="a1", - refresh_token="r1", - scope="profile", - expires_in=3600, - ) - db.session.add(token) - db.session.commit() - yield token - db.session.delete(token) - - def test_invalid_client(test_client): rv = test_client.post("/oauth/revoke") resp = json.loads(rv.data)
