Version in base suite: 1.2.0-1 Base version: python-authlib_1.2.0-1 Target version: python-authlib_1.2.0-1+deb12u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/python-authlib/python-authlib_1.2.0-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/python-authlib/python-authlib_1.2.0-1+deb12u1.dsc changelog | 27 +++++ gbp.conf | 2 patches/CVE-2024-37568.patch | 48 ++++++++++ patches/CVE-2025-59420.patch | 203 +++++++++++++++++++++++++++++++++++++++++++ patches/CVE-2025-61920.patch | 95 ++++++++++++++++++++ patches/CVE-2025-62706.patch | 53 +++++++++++ patches/CVE-2025-68158.patch | 151 +++++++++++++++++++++++++++++++ patches/series | 5 + tests/control | 6 + tests/unittests3 | 3 10 files changed, 591 insertions(+), 2 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpgeccjyle/python-authlib_1.2.0-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpgeccjyle/python-authlib_1.2.0-1+deb12u1.dsc: no acceptable signature found diff -Nru python-authlib-1.2.0/debian/changelog python-authlib-1.2.0/debian/changelog --- python-authlib-1.2.0/debian/changelog 2022-12-09 22:08:37.000000000 +0000 +++ python-authlib-1.2.0/debian/changelog 2026-02-28 02:41:12.000000000 +0000 @@ -1,3 +1,30 @@ +python-authlib (1.2.0-1+deb12u1) bookworm; urgency=medium + + * Non-maintainer upload by the Debian LTS team. + * d/patches/CVE-2025-68158.patch: Add patch to fix CVE-2025-68158. + - The cache-backed state/request-token storage is not tied to the + initiating user session, so CSRF is possible for any attacker that has + a valid state. + * d/patches/CVE-2025-62706.patch: Add patch to fix CVE-2025-62706. + - Authlib’s JWE zip=DEF path performs unbounded DEFLATE decompression + which can lead to a DoS. + * d/patches/CVE-2025-61920.patch: Add patch to fix CVE-2025-61920. + - Authlib’s JOSE implementation accepts unbounded JWS/JWT header and + signature segments which can lead to a DoS during verification. + * d/patches/CVE-2025-59420.patch: Add patch to fix CVE-2025-59420. + - Authlib’s JWS verification accepts tokens that declare unknown critical + header parameters (crit), violating RFC 7515 “must‑understand” semantics. + An attacker can craft a signed token with a critical header that strict + verifiers reject but Authlib accepts. In mixed‑language fleets, this + enables split‑brain verification and can lead to policy bypass, replay, + or privilege escalation. + * d/patches/CVE-2024-37568.patch: Add patch to fix CVE-2024-37568. + - Unless an algorithm is specified in a jwt.decode call, HMAC verification + is allowed with any asymmetric public key. + * debian/tests/control, debian/tests/unittests3: Enable client and jose tests. + + -- Daniel Leidert Sat, 28 Feb 2026 03:41:12 +0100 + python-authlib (1.2.0-1) unstable; urgency=medium * New upstream release. diff -Nru python-authlib-1.2.0/debian/gbp.conf python-authlib-1.2.0/debian/gbp.conf --- python-authlib-1.2.0/debian/gbp.conf 2022-12-09 22:08:37.000000000 +0000 +++ python-authlib-1.2.0/debian/gbp.conf 2026-02-28 02:41:12.000000000 +0000 @@ -1,6 +1,6 @@ [DEFAULT] upstream-branch = upstream -debian-branch = debian/master +debian-branch = debian/bookworm upstream-tag = upstream/%(version)s debian-tag = debian/%(version)s sign-tags = True diff -Nru python-authlib-1.2.0/debian/patches/CVE-2024-37568.patch python-authlib-1.2.0/debian/patches/CVE-2024-37568.patch --- python-authlib-1.2.0/debian/patches/CVE-2024-37568.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-authlib-1.2.0/debian/patches/CVE-2024-37568.patch 2026-02-28 02:41:12.000000000 +0000 @@ -0,0 +1,48 @@ +From: Hsiaoming Yang +Date: Tue, 4 Jun 2024 11:34:43 +0900 +Subject: fix: prevent OctKey to import ssh/rsa/pem keys + +https://github.com/lepture/authlib/issues/654 + +Reviewed-By: Daniel Leidert +Origin: https://github.com/lepture/authlib/commit/3bea812acefebc9ee108aa24557be3ba8971daf1 +Bug: https://github.com/lepture/authlib/issues/654 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2024-37568 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2024-37568 +--- + authlib/jose/rfc7518/oct_key.py | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/authlib/jose/rfc7518/oct_key.py b/authlib/jose/rfc7518/oct_key.py +index c2e16b1..fddb45d 100644 +--- a/authlib/jose/rfc7518/oct_key.py ++++ b/authlib/jose/rfc7518/oct_key.py +@@ -6,6 +6,16 @@ from authlib.common.security import generate_token + from ..rfc7517 import Key + + ++POSSIBLE_UNSAFE_KEYS = ( ++ b"-----BEGIN ", ++ b"---- BEGIN ", ++ b"ssh-rsa ", ++ b"ssh-dss ", ++ b"ssh-ed25519 ", ++ b"ecdsa-sha2-", ++) ++ ++ + class OctKey(Key): + """Key class of the ``oct`` key type.""" + +@@ -65,6 +75,11 @@ class OctKey(Key): + key._dict_data = raw + else: + raw_key = to_bytes(raw) ++ ++ # security check ++ if raw_key.startswith(POSSIBLE_UNSAFE_KEYS): ++ raise ValueError("This key may not be safe to import") ++ + key = cls(raw_key=raw_key, options=options) + return key + diff -Nru python-authlib-1.2.0/debian/patches/CVE-2025-59420.patch python-authlib-1.2.0/debian/patches/CVE-2025-59420.patch --- python-authlib-1.2.0/debian/patches/CVE-2025-59420.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-authlib-1.2.0/debian/patches/CVE-2025-59420.patch 2026-02-28 02:41:12.000000000 +0000 @@ -0,0 +1,203 @@ +From: Hsiaoming Yang +Date: Tue, 9 Sep 2025 14:47:14 +0900 +Subject: [PATCH 1/3] fix(jose): validate crit header parameters +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + + +From: Hsiaoming Yang +Date: Wed, 10 Sep 2025 18:03:10 +0900 +Subject: [PATCH 2/3] fix(jose): validate crit header when deserialize + + +From: Muhammad Noman Ilyas <113287211+AL-Cybision@users.noreply.github.com> +Date: Sun, 14 Sep 2025 19:41:50 +0500 +Subject: [PATCH 3/3] fix(jose): Reject unprotected ‘crit’ and enforce type; add tests (#823) + + +Reviewed-By: Daniel Leidert +Origin: https://github.com/authlib/authlib/commit/6b1813e4392eb7c168c276099ff7783b176479df +Bug: https://github.com/authlib/authlib/security/advisories/GHSA-9ggr-2464-2j32 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-59420 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2025-59420 +--- + authlib/jose/errors.py | 9 +++++++ + authlib/jose/rfc7515/jws.py | 39 +++++++++++++++++++++++++++++- + tests/jose/test_jws.py | 58 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 105 insertions(+), 1 deletion(-) + +diff --git a/authlib/jose/errors.py b/authlib/jose/errors.py +index b93523f..6d6e722 100644 +--- a/authlib/jose/errors.py ++++ b/authlib/jose/errors.py +@@ -34,6 +34,15 @@ class InvalidHeaderParameterNameError(JoseError): + description=description) + + ++class InvalidCritHeaderParameterNameError(JoseError): ++ error = "invalid_crit_header_parameter_name" ++ ++ def __init__(self, name): ++ description = f"Invalid Header Parameter Name: {name}" ++ super(InvalidCritHeaderParameterNameError, self).__init__( ++ description=description) ++ ++ + class InvalidEncryptionAlgorithmForECDH1PUWithKeyWrappingError(JoseError): + error = 'invalid_encryption_algorithm_for_ECDH_1PU_with_key_wrapping' + +diff --git a/authlib/jose/rfc7515/jws.py b/authlib/jose/rfc7515/jws.py +index 7e1cada..e2abc38 100644 +--- a/authlib/jose/rfc7515/jws.py ++++ b/authlib/jose/rfc7515/jws.py +@@ -10,6 +10,7 @@ from authlib.jose.util import ( + ) + from authlib.jose.errors import ( + DecodeError, ++ InvalidCritHeaderParameterNameError, + MissingAlgorithmError, + UnsupportedAlgorithmError, + BadSignatureError, +@@ -61,6 +62,7 @@ class JsonWebSignature(object): + """ + jws_header = JWSHeader(protected, None) + self._validate_private_headers(protected) ++ self._validate_crit_headers(protected) + algorithm, key = self._prepare_algorithm_key(protected, payload, key) + + protected_segment = json_b64encode(jws_header.protected) +@@ -95,6 +97,7 @@ class JsonWebSignature(object): + raise DecodeError('Not enough segments') + + protected = _extract_header(protected_segment) ++ self._validate_crit_headers(protected) + jws_header = JWSHeader(protected, None) + + payload = _extract_payload(payload_segment) +@@ -132,6 +135,11 @@ class JsonWebSignature(object): + + def _sign(jws_header): + self._validate_private_headers(jws_header) ++ # RFC 7515 §4.1.11: 'crit' MUST be integrity-protected. ++ # Reject if present in unprotected header, and validate only ++ # against the protected header parameters. ++ self._reject_unprotected_crit(jws_header.header) ++ self._validate_crit_headers(jws_header.protected) + _alg, _key = self._prepare_algorithm_key(jws_header, payload, key) + + protected_segment = json_b64encode(jws_header.protected) +@@ -273,6 +281,28 @@ class JsonWebSignature(object): + if k not in names: + raise InvalidHeaderParameterNameError(k) + ++ def _reject_unprotected_crit(self, unprotected_header): ++ """Reject 'crit' when found in the unprotected header (RFC 7515 §4.1.11).""" ++ if unprotected_header and "crit" in unprotected_header: ++ raise InvalidHeaderParameterNameError("crit") ++ ++ def _validate_crit_headers(self, header): ++ if "crit" in header: ++ crit_headers = header["crit"] ++ # Type enforcement for robustness and predictable errors ++ if not isinstance(crit_headers, list) or not all( ++ isinstance(x, str) for x in crit_headers ++ ): ++ raise InvalidHeaderParameterNameError("crit") ++ names = self.REGISTERED_HEADER_PARAMETER_NAMES.copy() ++ if self._private_headers: ++ names = names.union(self._private_headers) ++ for k in crit_headers: ++ if k not in names: ++ raise InvalidCritHeaderParameterNameError(k) ++ elif k not in header: ++ raise InvalidCritHeaderParameterNameError(k) ++ + def _validate_json_jws(self, payload_segment, payload, header_obj, key): + protected_segment = header_obj.get('protected') + if not protected_segment: +@@ -287,7 +317,14 @@ class JsonWebSignature(object): + header = header_obj.get('header') + if header and not isinstance(header, dict): + raise DecodeError('Invalid "header" value') +- ++ # RFC 7515 §4.1.11: 'crit' MUST be integrity-protected. If present in ++ # the unprotected header object, reject the JWS. ++ self._reject_unprotected_crit(header) ++ ++ # Enforce must-understand semantics for names listed in protected ++ # 'crit'. This will also ensure each listed name is present in the ++ # protected header. ++ self._validate_crit_headers(protected) + jws_header = JWSHeader(protected, header) + algorithm, key = self._prepare_algorithm_key(jws_header, payload, key) + signing_input = b'.'.join([protected_segment, payload_segment]) +diff --git a/tests/jose/test_jws.py b/tests/jose/test_jws.py +index 4df832f..a0df918 100644 +--- a/tests/jose/test_jws.py ++++ b/tests/jose/test_jws.py +@@ -184,6 +184,64 @@ class JWSTest(unittest.TestCase): + s = jws.serialize(header, b'hello', 'secret') + self.assertIsInstance(s, dict) + ++ def test_validate_crit_header_with_serialize(self): ++ jws = JsonWebSignature() ++ protected = {'alg': 'HS256', 'kid': '1', 'crit': ['kid']} ++ jws.serialize(protected, b'hello', 'secret') ++ ++ protected = {'alg': 'HS256', 'crit': ['kid']} ++ self.assertRaises( ++ errors.InvalidCritHeaderParameterNameError, ++ jws.serialize, protected, b'hello', 'secret' ++ ) ++ ++ protected = {'alg': 'HS256', 'invalid': '1', 'crit': ['invalid']} ++ self.assertRaises( ++ errors.InvalidCritHeaderParameterNameError, ++ jws.serialize, protected, b'hello', 'secret' ++ ) ++ ++ def test_validate_crit_header_with_deserialize(self): ++ jws = JsonWebSignature() ++ ++ case1 = "eyJhbGciOiJIUzI1NiIsImNyaXQiOlsia2lkIl19.aGVsbG8.RVimhJH2LRGAeHy0ZcbR9xsgKhzhxIBkHs7S_TDgWvc" ++ self.assertRaises( ++ errors.InvalidCritHeaderParameterNameError, ++ jws.deserialize, case1, 'secret' ++ ) ++ ++ case2 = ( ++ "eyJhbGciOiJIUzI1NiIsImludmFsaWQiOiIxIiwiY3JpdCI6WyJpbnZhbGlkIl19." ++ "aGVsbG8.ifW_D1AQWzggrpd8npcnmpiwMD9dp5FTX66lCkYFENM" ++ ) ++ self.assertRaises( ++ errors.InvalidCritHeaderParameterNameError, ++ jws.deserialize, case2, 'secret' ++ ) ++ ++ def test_unprotected_crit_rejected_in_json_serialize(self): ++ jws = JsonWebSignature() ++ protected = {'alg': 'HS256', 'kid': 'a'} ++ # Place 'crit' in unprotected header; must be rejected ++ header = {'protected': protected, 'header': {'kid': 'a', 'crit': ['kid']}} ++ self.assertRaises( ++ errors.InvalidHeaderParameterNameError, ++ jws.serialize_json, header, b'hello', 'secret' ++ ) ++ ++ def test_unprotected_crit_rejected_in_json_deserialize(self): ++ jws = JsonWebSignature() ++ protected = {'alg': 'HS256', 'kid': 'a'} ++ header = {'protected': protected, 'header': {'kid': 'a'}} ++ data = jws.serialize_json(header, b'hello', 'secret') ++ # Tamper by adding 'crit' into the unprotected header; must be rejected ++ data_tampered = dict(data) ++ data_tampered['header'] = {'kid': 'a', 'crit': ['kid']} ++ self.assertRaises( ++ errors.InvalidHeaderParameterNameError, ++ jws.deserialize_json, data_tampered, 'secret' ++ ) ++ + def test_ES512_alg(self): + jws = JsonWebSignature() + private_key = read_file_path('secp521r1-private.json') diff -Nru python-authlib-1.2.0/debian/patches/CVE-2025-61920.patch python-authlib-1.2.0/debian/patches/CVE-2025-61920.patch --- python-authlib-1.2.0/debian/patches/CVE-2025-61920.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-authlib-1.2.0/debian/patches/CVE-2025-61920.patch 2026-02-28 02:41:12.000000000 +0000 @@ -0,0 +1,95 @@ +From: Hsiaoming Yang +Date: Thu, 2 Oct 2025 22:26:41 +0900 +Subject: [PATCH] fix(jose): add size limitation to prevent DoS + +Reviewed-By: Daniel Leidert +Origin: https://github.com/authlib/authlib/commit/867e3f87b072347a1ae9cf6983cc8bbf88447e5e +Bug: https://github.com/authlib/authlib/security/advisories/GHSA-pq5p-34cr-23v9 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-61920 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2025-61920 +--- + authlib/jose/rfc7515/jws.py | 5 +++++ + authlib/jose/util.py | 6 ++++++ + tests/jose/test_jws.py | 25 +++++++++++++++++++++++++ + 3 files changed, 36 insertions(+) + +diff --git a/authlib/jose/rfc7515/jws.py b/authlib/jose/rfc7515/jws.py +index faaa740..7e1cada 100644 +--- a/authlib/jose/rfc7515/jws.py ++++ b/authlib/jose/rfc7515/jws.py +@@ -27,6 +27,8 @@ class JsonWebSignature(object): + 'typ', 'cty', 'crit' + ]) + ++ MAX_CONTENT_LENGTH: int = 256000 ++ + #: Defined available JWS algorithms in the registry + ALGORITHMS_REGISTRY = {} + +@@ -82,6 +84,9 @@ class JsonWebSignature(object): + + .. _`Section 7.1`: https://tools.ietf.org/html/rfc7515#section-7.1 + """ ++ if len(s) > self.MAX_CONTENT_LENGTH: ++ raise ValueError("Serialization is too long.") ++ + try: + s = to_bytes(s) + signing_input, signature_segment = s.rsplit(b'.', 1) +diff --git a/authlib/jose/util.py b/authlib/jose/util.py +index adc8ad8..3c86433 100644 +--- a/authlib/jose/util.py ++++ b/authlib/jose/util.py +@@ -4,6 +4,9 @@ from authlib.jose.errors import DecodeError + + + def extract_header(header_segment, error_cls): ++ if len(header_segment) > 256000: ++ raise ValueError("Value of header is too long") ++ + header_data = extract_segment(header_segment, error_cls, 'header') + + try: +@@ -17,6 +20,9 @@ def extract_header(header_segment, error_cls): + + + def extract_segment(segment, error_cls, name='payload'): ++ if len(segment) > 256000: ++ raise ValueError(f"Value of {name} is too long") ++ + try: + return urlsafe_b64decode(segment) + except (TypeError, binascii.Error): +diff --git a/tests/jose/test_jws.py b/tests/jose/test_jws.py +index e531e5c..4df832f 100644 +--- a/tests/jose/test_jws.py ++++ b/tests/jose/test_jws.py +@@ -204,3 +204,28 @@ class JWSTest(unittest.TestCase): + header, payload = data['header'], data['payload'] + self.assertEqual(payload, b'hello') + self.assertEqual(header['alg'], 'ES256K') ++ ++ def test_deserialize_exceeds_length(self): ++ jws = JsonWebSignature() ++ value = "aa" * 256000 ++ ++ # header exceeds length ++ s = value + '.' + value + '.' + value ++ self.assertRaises( ++ ValueError, ++ jws.deserialize, s, '' ++ ) ++ ++ # payload exceeds length ++ s = 'eyJhbGciOiJIUzI1NiJ9.' + value + '.' + value ++ self.assertRaises( ++ ValueError, ++ jws.deserialize, s, '' ++ ) ++ ++ # signature exceeds length ++ s = 'eyJhbGciOiJIUzI1NiJ9.YQ.' + value ++ self.assertRaises( ++ ValueError, ++ jws.deserialize, s, '' ++ ) diff -Nru python-authlib-1.2.0/debian/patches/CVE-2025-62706.patch python-authlib-1.2.0/debian/patches/CVE-2025-62706.patch --- python-authlib-1.2.0/debian/patches/CVE-2025-62706.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-authlib-1.2.0/debian/patches/CVE-2025-62706.patch 2026-02-28 02:41:12.000000000 +0000 @@ -0,0 +1,53 @@ +From: Hsiaoming Yang +Date: Wed, 24 Sep 2025 21:38:45 +0900 +Subject: [PATCH] fix(jose): add max size for JWE zip=DEF decompression + +Reviewed-By: Daniel Leidert +Origin: https://github.com/authlib/authlib/commit/4b5b5703394608124cd39e547cc7829feda05a13 +Bug: https://github.com/authlib/authlib/security/advisories/GHSA-g7f3-828f-7h7m +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-62706 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2025-62706 +--- + authlib/jose/rfc7518/jwe_zips.py | 20 ++++++++++++++++---- + 1 file changed, 16 insertions(+), 4 deletions(-) + +diff --git a/authlib/jose/rfc7518/jwe_zips.py b/authlib/jose/rfc7518/jwe_zips.py +index 2396861..99dcf68 100644 +--- a/authlib/jose/rfc7518/jwe_zips.py ++++ b/authlib/jose/rfc7518/jwe_zips.py +@@ -2,19 +2,31 @@ import zlib + from ..rfc7516 import JWEZipAlgorithm, JsonWebEncryption + + ++GZIP_HEAD = bytes([120, 156]) ++MAX_SIZE = 250 * 1024 ++ ++ + class DeflateZipAlgorithm(JWEZipAlgorithm): + name = 'DEF' + description = 'DEFLATE' + +- def compress(self, s): ++ def compress(self, s: bytes) -> bytes: + """Compress bytes data with DEFLATE algorithm.""" + data = zlib.compress(s) +- # drop gzip headers and tail ++ # https://datatracker.ietf.org/doc/html/rfc1951 ++ # since DEF is always gzip, we can drop gzip headers and tail + return data[2:-4] + +- def decompress(self, s): ++ def decompress(self, s: bytes) -> bytes: + """Decompress DEFLATE bytes data.""" +- return zlib.decompress(s, -zlib.MAX_WBITS) ++ if s.startswith(GZIP_HEAD): ++ decompressor = zlib.decompressobj() ++ else: ++ decompressor = zlib.decompressobj(-zlib.MAX_WBITS) ++ value = decompressor.decompress(s, MAX_SIZE) ++ if decompressor.unconsumed_tail: ++ raise ValueError(f"Decompressed string exceeds {MAX_SIZE} bytes") ++ return value + + + def register_jwe_rfc7518(): diff -Nru python-authlib-1.2.0/debian/patches/CVE-2025-68158.patch python-authlib-1.2.0/debian/patches/CVE-2025-68158.patch --- python-authlib-1.2.0/debian/patches/CVE-2025-68158.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-authlib-1.2.0/debian/patches/CVE-2025-68158.patch 2026-02-28 02:41:12.000000000 +0000 @@ -0,0 +1,151 @@ +From: Hsiaoming Yang +Date: Fri, 12 Dec 2025 16:37:44 +0900 +Subject: [PATCH] Merge commit from fork + +Reviewed-By: Daniel Leidert +Origin: https://github.com/authlib/authlib/commit/2808378611dd6fb2532b189a9087877d8f0c0489 +Bug: https://github.com/authlib/authlib/security/advisories/GHSA-fg6f-75jq-6523 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-68158 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2025-68158 +--- + .../base_client/framework_integration.py | 25 ++++++------ + tests/clients/test_flask/test_oauth_client.py | 47 +++++++++++++++++++++- + 2 files changed, 58 insertions(+), 14 deletions(-) + +diff --git a/authlib/integrations/base_client/framework_integration.py b/authlib/integrations/base_client/framework_integration.py +index 91028b8..f20f98c 100644 +--- a/authlib/integrations/base_client/framework_integration.py ++++ b/authlib/integrations/base_client/framework_integration.py +@@ -20,11 +20,9 @@ class FrameworkIntegration(object): + + 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 @@ class FrameworkIntegration(object): + + 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 --git a/tests/clients/test_flask/test_oauth_client.py b/tests/clients/test_flask/test_oauth_client.py +index 0789822..aa517b6 100644 +--- a/tests/clients/test_flask/test_oauth_client.py ++++ b/tests/clients/test_flask/test_oauth_client.py +@@ -132,9 +132,13 @@ class FlaskOAuthTest(TestCase): + self.assertEqual(resp.status_code, 302) + url = resp.headers.get('Location') + self.assertIn('oauth_token=foo', url) ++ session_data = session["_state_dev_foo"] ++ self.assertIn('exp', session_data) ++ self.assertNotIn('data', 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() + self.assertEqual(token['oauth_token'], 'a') +@@ -186,7 +190,43 @@ class FlaskOAuthTest(TestCase): + session = oauth.dev._get_oauth_client() + self.assertIsNotNone(session.update_token) + +- def test_oauth2_authorize(self): ++ def test_oauth2_authorize_cache(self): ++ 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") ++ self.assertEqual(resp.status_code, 302) ++ url = resp.headers.get("Location") ++ self.assertIn('state=', url) ++ state = dict(url_decode(urlparse.urlparse(url).query))["state"] ++ self.assertIsNotNone(state) ++ session_data = session[f"_state_dev_{state}"] ++ self.assertIn('exp', session_data) ++ self.assertNotIn('data', 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() ++ self.assertEqual(token['access_token'], 'a') ++ ++ with app.test_request_context(): ++ self.assertEqual(client.token, None) ++ ++ def test_oauth2_authorize_session(self): + app = Flask(__name__) + app.secret_key = '!' + oauth = OAuth(app) +@@ -207,10 +247,13 @@ class FlaskOAuthTest(TestCase): + state = dict(url_decode(urlparse.urlparse(url).query))['state'] + self.assertIsNotNone(state) + data = session[f'_state_dev_{state}'] ++ session_data = session[f"_state_dev_{state}"] ++ self.assertIn('exp', session_data) ++ self.assertIn('data', 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 -Nru python-authlib-1.2.0/debian/patches/series python-authlib-1.2.0/debian/patches/series --- python-authlib-1.2.0/debian/patches/series 2022-12-09 22:08:37.000000000 +0000 +++ python-authlib-1.2.0/debian/patches/series 2026-02-28 02:41:12.000000000 +0000 @@ -1,2 +1,7 @@ sphinx-default-theme sphinx-3rdparty-assets +CVE-2025-62706.patch +CVE-2025-61920.patch +CVE-2025-59420.patch +CVE-2025-68158.patch +CVE-2024-37568.patch diff -Nru python-authlib-1.2.0/debian/tests/control python-authlib-1.2.0/debian/tests/control --- python-authlib-1.2.0/debian/tests/control 2022-12-09 22:08:37.000000000 +0000 +++ python-authlib-1.2.0/debian/tests/control 2026-02-28 02:41:12.000000000 +0000 @@ -2,12 +2,16 @@ Depends: python3-all, python3-authlib, + python3-cachelib, python3-cryptography, python3-django, python3-flask, python3-flask-sqlalchemy, + python3-httpx, python3-itsdangerous, python3-mock, python3-pytest, + python3-pytest-asyncio, python3-pytest-django, - python3-requests + python3-requests, + python3-starlette, diff -Nru python-authlib-1.2.0/debian/tests/unittests3 python-authlib-1.2.0/debian/tests/unittests3 --- python-authlib-1.2.0/debian/tests/unittests3 2022-12-09 22:08:37.000000000 +0000 +++ python-authlib-1.2.0/debian/tests/unittests3 2026-02-28 02:41:12.000000000 +0000 @@ -4,6 +4,7 @@ pys="$(py3versions -r 2> /dev/null)" cp -a setup.cfg tests "$AUTOPKGTEST_TMP" +echo "asyncio_mode = auto" >> "$AUTOPKGTEST_TMP/setup.cfg" cd "$AUTOPKGTEST_TMP" @@ -12,4 +13,6 @@ $py -m pytest tests/core 2>&1 DJANGO_SETTINGS_MODULE=tests.django.settings $py -m pytest tests/django 2>&1 $py -m pytest tests/flask 2>&1 + DJANGO_SETTINGS_MODULE=tests.clients.test_django.settings $py -m pytest tests/clients 2>&1 + $py -m pytest tests/jose 2>&1 done