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-05-28 17:28:26
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Authlib (Old)
 and      /work/SRC/openSUSE:Factory/.python-Authlib.new.1937 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-Authlib"

Thu May 28 17:28:26 2026 rev:32 rq:1355533 version:1.7.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Authlib/python-Authlib.changes    
2026-04-25 21:37:38.146419499 +0200
+++ /work/SRC/openSUSE:Factory/.python-Authlib.new.1937/python-Authlib.changes  
2026-05-28 17:29:20.437683245 +0200
@@ -1,0 +2,10 @@
+Thu May 28 06:41:00 UTC 2026 - Markéta Machová <[email protected]>
+
+- Update to 1.7.2 (bsc#1266665, CVE-2026-44681)
+  * Allow non-recommended algorithms in ClientSecretJWT and PrivateKey
+  * Validate BCP47 language tags with a regex
+  * Fix RFC7523 signing with non RSA keys
+  * redirecting to unvalidated redirect_uri on InvalidScopeError in OIDC grants
+  * authlib.jose deprecation warning poping from _joserfc_helpers
+
+-------------------------------------------------------------------

Old:
----
  authlib-1.7.0.tar.gz

New:
----
  authlib-1.7.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-Authlib.spec ++++++
--- /var/tmp/diff_new_pack.w2rEIC/_old  2026-05-28 17:29:21.233716195 +0200
+++ /var/tmp/diff_new_pack.w2rEIC/_new  2026-05-28 17:29:21.233716195 +0200
@@ -19,7 +19,7 @@
 %define modname authlib
 %{?sle15_python_module_pythons}
 Name:           python-Authlib
-Version:        1.7.0
+Version:        1.7.2
 Release:        0
 Summary:        Python library for building OAuth and OpenID Connect servers
 License:        BSD-3-Clause

++++++ authlib-1.7.0.tar.gz -> authlib-1.7.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/.github/workflows/pypi.yml 
new/authlib-1.7.2/.github/workflows/pypi.yml
--- old/authlib-1.7.0/.github/workflows/pypi.yml        2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/.github/workflows/pypi.yml        2026-05-06 
10:08:51.000000000 +0200
@@ -54,3 +54,15 @@
 
     - name: Push build artifacts to PyPI
       uses: pypa/gh-action-pypi-publish@release/v1
+
+  github-release:
+    name: create github release
+    needs: publish
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+
+    steps:
+    - uses: softprops/action-gh-release@v2
+      with:
+        generate_release_notes: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/.pre-commit-config.yaml 
new/authlib-1.7.2/.pre-commit-config.yaml
--- old/authlib-1.7.0/.pre-commit-config.yaml   2026-04-18 12:59:19.000000000 
+0200
+++ new/authlib-1.7.2/.pre-commit-config.yaml   2026-05-06 10:08:51.000000000 
+0200
@@ -4,7 +4,7 @@
   - commit-msg
 repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.15.8
+    rev: v0.15.12
     hooks:
       - id: ruff
         args: [--fix, --exit-non-zero-on-fix]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/README.md new/authlib-1.7.2/README.md
--- old/authlib-1.7.0/README.md 2026-04-18 12:59:19.000000000 +0200
+++ new/authlib-1.7.2/README.md 2026-05-06 10:08:51.000000000 +0200
@@ -45,33 +45,33 @@
 
 Generic, spec-compliant implementation to build clients and providers:
 
-- [The OAuth 1.0 
Protocol](https://docs.authlib.org/en/stable/basic/oauth1.html)
-  - [RFC5849: The OAuth 1.0 
Protocol](https://docs.authlib.org/en/stable/specs/rfc5849.html)
-- [The OAuth 2.0 Authorization 
Framework](https://docs.authlib.org/en/stable/basic/oauth2.html)
-  - [RFC6749: The OAuth 2.0 Authorization 
Framework](https://docs.authlib.org/en/stable/specs/rfc6749.html)
-  - [RFC6750: The OAuth 2.0 Authorization Framework: Bearer Token 
Usage](https://docs.authlib.org/en/stable/specs/rfc6750.html)
-  - [RFC7009: OAuth 2.0 Token 
Revocation](https://docs.authlib.org/en/stable/specs/rfc7009.html)
-  - [RFC7523: JWT Profile for OAuth 2.0 Client Authentication and 
Authorization Grants](https://docs.authlib.org/en/stable/specs/rfc7523.html)
-  - [RFC7591: OAuth 2.0 Dynamic Client Registration 
Protocol](https://docs.authlib.org/en/stable/specs/rfc7591.html)
-  - [RFC7592: OAuth 2.0 Dynamic Client Registration Management 
Protocol](https://docs.authlib.org/en/stable/specs/rfc7592.html)
-  - [RFC7636: Proof Key for Code Exchange by OAuth Public 
Clients](https://docs.authlib.org/en/stable/specs/rfc7636.html)
-  - [RFC7662: OAuth 2.0 Token 
Introspection](https://docs.authlib.org/en/stable/specs/rfc7662.html)
-  - [RFC8414: OAuth 2.0 Authorization Server 
Metadata](https://docs.authlib.org/en/stable/specs/rfc8414.html)
-  - [RFC8628: OAuth 2.0 Device Authorization 
Grant](https://docs.authlib.org/en/stable/specs/rfc8628.html)
-  - [RFC9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access 
Tokens](https://docs.authlib.org/en/stable/specs/rfc9068.html)
-  - [RFC9101: The OAuth 2.0 Authorization Framework: JWT-Secured Authorization 
Request (JAR)](https://docs.authlib.org/en/stable/specs/rfc9101.html)
-  - [RFC9207: OAuth 2.0 Authorization Server Issuer 
Identification](https://docs.authlib.org/en/stable/specs/rfc9207.html)
+- [The OAuth 1.0 
Protocol](https://docs.authlib.org/en/stable/oauth1/index.html)
+  - [RFC5849: The OAuth 1.0 
Protocol](https://docs.authlib.org/en/stable/oauth1/specs/rfc5849.html)
+- [The OAuth 2.0 Authorization 
Framework](https://docs.authlib.org/en/stable/oauth2/index.html)
+  - [RFC6749: The OAuth 2.0 Authorization 
Framework](https://docs.authlib.org/en/stable/oauth2/specs/rfc6749.html)
+  - [RFC6750: The OAuth 2.0 Authorization Framework: Bearer Token 
Usage](https://docs.authlib.org/en/stable/oauth2/specs/rfc6750.html)
+  - [RFC7009: OAuth 2.0 Token 
Revocation](https://docs.authlib.org/en/stable/oauth2/specs/rfc7009.html)
+  - [RFC7523: JWT Profile for OAuth 2.0 Client Authentication and 
Authorization 
Grants](https://docs.authlib.org/en/stable/oauth2/specs/rfc7523.html)
+  - [RFC7591: OAuth 2.0 Dynamic Client Registration 
Protocol](https://docs.authlib.org/en/stable/oauth2/specs/rfc7591.html)
+  - [RFC7592: OAuth 2.0 Dynamic Client Registration Management 
Protocol](https://docs.authlib.org/en/stable/oauth2/specs/rfc7592.html)
+  - [RFC7636: Proof Key for Code Exchange by OAuth Public 
Clients](https://docs.authlib.org/en/stable/oauth2/specs/rfc7636.html)
+  - [RFC7662: OAuth 2.0 Token 
Introspection](https://docs.authlib.org/en/stable/oauth2/specs/rfc7662.html)
+  - [RFC8414: OAuth 2.0 Authorization Server 
Metadata](https://docs.authlib.org/en/stable/oauth2/specs/rfc8414.html)
+  - [RFC8628: OAuth 2.0 Device Authorization 
Grant](https://docs.authlib.org/en/stable/oauth2/specs/rfc8628.html)
+  - [RFC9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access 
Tokens](https://docs.authlib.org/en/stable/oauth2/specs/rfc9068.html)
+  - [RFC9101: The OAuth 2.0 Authorization Framework: JWT-Secured Authorization 
Request (JAR)](https://docs.authlib.org/en/stable/oauth2/specs/rfc9101.html)
+  - [RFC9207: OAuth 2.0 Authorization Server Issuer 
Identification](https://docs.authlib.org/en/stable/oauth2/specs/rfc9207.html)
 - [Javascript Object Signing and 
Encryption](https://docs.authlib.org/en/stable/jose/index.html)
   - [RFC7515: JSON Web 
Signature](https://docs.authlib.org/en/stable/jose/jws.html)
   - [RFC7516: JSON Web 
Encryption](https://docs.authlib.org/en/stable/jose/jwe.html)
   - [RFC7517: JSON Web Key](https://docs.authlib.org/en/stable/jose/jwk.html)
-  - [RFC7518: JSON Web 
Algorithms](https://docs.authlib.org/en/stable/specs/rfc7518.html)
+  - [RFC7518: JSON Web 
Algorithms](https://docs.authlib.org/en/stable/jose/specs/rfc7518.html)
   - [RFC7519: JSON Web Token](https://docs.authlib.org/en/stable/jose/jwt.html)
-  - [RFC7638: JSON Web Key (JWK) 
Thumbprint](https://docs.authlib.org/en/stable/specs/rfc7638.html)
+  - [RFC7638: JSON Web Key (JWK) 
Thumbprint](https://docs.authlib.org/en/stable/jose/specs/rfc7638.html)
   - [ ] RFC7797: JSON Web Signature (JWS) Unencoded Payload Option
-  - [RFC8037: ECDH in JWS and 
JWE](https://docs.authlib.org/en/stable/specs/rfc8037.html)
+  - [RFC8037: ECDH in JWS and 
JWE](https://docs.authlib.org/en/stable/jose/specs/rfc8037.html)
   - [ ] draft-madden-jose-ecdh-1pu-04: Public Key Authenticated Encryption for 
JOSE: ECDH-1PU
-- [OpenID Connect 1.0](https://docs.authlib.org/en/stable/specs/oidc.html)
+- [OpenID Connect 
1.0](https://docs.authlib.org/en/stable/oauth2/specs/oidc.html)
   - [x] OpenID Connect Core 1.0
   - [x] OpenID Connect Discovery 1.0
   - [x] OpenID Connect Dynamic Client Registration 1.0
@@ -80,30 +80,30 @@
 Connect third party OAuth providers with Authlib built-in client integrations:
 
 - Requests
-  - 
[OAuth1Session](https://docs.authlib.org/en/stable/client/requests.html#requests-oauth-1-0)
-  - 
[OAuth2Session](https://docs.authlib.org/en/stable/client/requests.html#requests-oauth-2-0)
-  - [OpenID 
Connect](https://docs.authlib.org/en/stable/client/requests.html#requests-openid-connect)
-  - 
[AssertionSession](https://docs.authlib.org/en/stable/client/requests.html#requests-service-account)
+  - 
[OAuth1Session](https://docs.authlib.org/en/stable/oauth1/client/http/requests.html)
+  - 
[OAuth2Session](https://docs.authlib.org/en/stable/oauth2/client/http/requests.html#requests-oauth-2-0)
+  - [OpenID 
Connect](https://docs.authlib.org/en/stable/oauth2/client/http/requests.html#requests-openid-connect)
+  - 
[AssertionSession](https://docs.authlib.org/en/stable/oauth2/client/http/requests.html#requests-service-account)
 - HTTPX
-  - 
[AsyncOAuth1Client](https://docs.authlib.org/en/stable/client/httpx.html#httpx-oauth-1-0)
-  - 
[AsyncOAuth2Client](https://docs.authlib.org/en/stable/client/httpx.html#httpx-oauth-2-0)
-  - [OpenID 
Connect](https://docs.authlib.org/en/stable/client/httpx.html#httpx-oauth-2-0)
-  - 
[AsyncAssertionClient](https://docs.authlib.org/en/stable/client/httpx.html#async-service-account)
-- [Flask OAuth Client](https://docs.authlib.org/en/stable/client/flask.html)
-- [Django OAuth Client](https://docs.authlib.org/en/stable/client/django.html)
-- [Starlette OAuth 
Client](https://docs.authlib.org/en/stable/client/starlette.html)
-- [FastAPI OAuth 
Client](https://docs.authlib.org/en/stable/client/fastapi.html)
+  - 
[AsyncOAuth1Client](https://docs.authlib.org/en/stable/oauth1/client/http/httpx.html)
+  - 
[AsyncOAuth2Client](https://docs.authlib.org/en/stable/oauth2/client/http/httpx.html#httpx-oauth-2-0)
+  - [OpenID 
Connect](https://docs.authlib.org/en/stable/oauth2/client/http/httpx.html#httpx-oauth-2-0)
+  - 
[AsyncAssertionClient](https://docs.authlib.org/en/stable/oauth2/client/http/httpx.html#async-service-account)
+- [Flask OAuth 
Client](https://docs.authlib.org/en/stable/oauth2/client/web/flask.html)
+- [Django OAuth 
Client](https://docs.authlib.org/en/stable/oauth2/client/web/django.html)
+- [Starlette OAuth 
Client](https://docs.authlib.org/en/stable/oauth2/client/web/starlette.html)
+- [FastAPI OAuth 
Client](https://docs.authlib.org/en/stable/oauth2/client/web/fastapi.html)
 
 Build your own OAuth 1.0, OAuth 2.0, and OpenID Connect providers:
 
 - Flask
-  - [Flask OAuth 1.0 Provider](https://docs.authlib.org/en/stable/flask/1/)
-  - [Flask OAuth 2.0 Provider](https://docs.authlib.org/en/stable/flask/2/)
-  - [Flask OpenID Connect 1.0 
Provider](https://docs.authlib.org/en/stable/flask/2/openid-connect.html)
+  - [Flask OAuth 1.0 
Provider](https://docs.authlib.org/en/stable/oauth1/provider/flask/index.html)
+  - [Flask OAuth 2.0 
Provider](https://docs.authlib.org/en/stable/oauth2/authorization-server/flask/index.html)
+  - [Flask OpenID Connect 1.0 
Provider](https://docs.authlib.org/en/stable/oauth2/authorization-server/flask/openid-connect.html)
 - Django
-  - [Django OAuth 1.0 Provider](https://docs.authlib.org/en/stable/django/1/)
-  - [Django OAuth 2.0 Provider](https://docs.authlib.org/en/stable/django/2/)
-  - [Django OpenID Connect 1.0 
Provider](https://docs.authlib.org/en/stable/django/2/openid-connect.html)
+  - [Django OAuth 1.0 
Provider](https://docs.authlib.org/en/stable/oauth1/provider/django/index.html)
+  - [Django OAuth 2.0 
Provider](https://docs.authlib.org/en/stable/oauth2/authorization-server/django/index.html)
+  - [Django OpenID Connect 1.0 
Provider](https://docs.authlib.org/en/stable/oauth2/authorization-server/django/openid-connect.html)
 
 ## Useful Links
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/_joserfc_helpers.py 
new/authlib-1.7.2/authlib/_joserfc_helpers.py
--- old/authlib-1.7.0/authlib/_joserfc_helpers.py       2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/_joserfc_helpers.py       2026-05-06 
10:08:51.000000000 +0200
@@ -1,3 +1,4 @@
+import sys
 from typing import Any
 
 from joserfc.jwk import KeySet
@@ -5,16 +6,18 @@
 
 from authlib.common.encoding import json_loads
 from authlib.deprecate import deprecate
-from authlib.jose import ECKey
-from authlib.jose import OctKey
-from authlib.jose import OKPKey
-from authlib.jose import RSAKey
 
 
 def import_any_key(data: Any):
-    if isinstance(data, (OctKey, RSAKey, ECKey, OKPKey)):
-        deprecate("Please use joserfc to import keys.", version="2.0.0")
-        return import_key(data.as_dict(is_private=not data.public_only))
+    if "authlib.jose" in sys.modules:
+        from authlib.jose.rfc7518 import ECKey
+        from authlib.jose.rfc7518 import OctKey
+        from authlib.jose.rfc7518 import RSAKey
+        from authlib.jose.rfc8037 import OKPKey
+
+        if isinstance(data, (OctKey, RSAKey, ECKey, OKPKey)):
+            deprecate("Please use joserfc to import keys.", version="2.0.0")
+            return import_key(data.as_dict(is_private=not data.public_only))
 
     if (
         isinstance(data, str)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/common/language.py 
new/authlib-1.7.2/authlib/common/language.py
--- old/authlib-1.7.0/authlib/common/language.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/authlib-1.7.2/authlib/common/language.py        2026-05-06 
10:08:51.000000000 +0200
@@ -0,0 +1,13 @@
+import re
+
+# Structurally validates BCP 47 language tags (RFC 5646).
+# Accepts private-use tags (x-...) and standard tags (2-8 alpha + optional 
subtags).
+# Does not validate subtags against the IANA registry.
+_LANGUAGE_TAG_RE = re.compile(
+    r"^(x(-[a-zA-Z0-9]{1,8})+|[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*)$"
+)
+
+
+def is_valid_language_tag(tag):
+    """Return True if tag is a structurally valid BCP 47 language tag."""
+    return isinstance(tag, str) and bool(_LANGUAGE_TAG_RE.match(tag))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/consts.py 
new/authlib-1.7.2/authlib/consts.py
--- old/authlib-1.7.0/authlib/consts.py 2026-04-18 12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/consts.py 2026-05-06 10:08:51.000000000 +0200
@@ -1,5 +1,5 @@
 name = "Authlib"
-version = "1.7.0"
+version = "1.7.2"
 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.7.0/authlib/oauth2/rfc7523/assertion.py 
new/authlib-1.7.2/authlib/oauth2/rfc7523/assertion.py
--- old/authlib-1.7.0/authlib/oauth2/rfc7523/assertion.py       2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/oauth2/rfc7523/assertion.py       2026-05-06 
10:08:51.000000000 +0200
@@ -44,7 +44,7 @@
     if claims:
         payload.update(claims)
 
-    return jwt.encode(header, payload, import_any_key(key))
+    return jwt.encode(header, payload, import_any_key(key), 
algorithms=[header["alg"]])
 
 
 def client_secret_jwt_sign(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/oauth2/rfc7523/auth.py 
new/authlib-1.7.2/authlib/oauth2/rfc7523/auth.py
--- old/authlib-1.7.0/authlib/oauth2/rfc7523/auth.py    2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/oauth2/rfc7523/auth.py    2026-05-06 
10:08:51.000000000 +0200
@@ -1,5 +1,7 @@
 from joserfc.jwk import OctKey
 from joserfc.jwk import RSAKey
+from joserfc.jwk import ECKey
+from joserfc.jwk import OKPKey
 
 from authlib.common.urls import add_params_to_qs
 
@@ -100,7 +102,7 @@
     alg = "RS256"
 
     def sign(self, auth, token_endpoint):
-        if isinstance(auth.client_secret, RSAKey):
+        if isinstance(auth.client_secret, (RSAKey, ECKey, OKPKey)):
             key = auth.client_secret
         else:
             key = RSAKey.import_key(auth.client_secret)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/oauth2/rfc8414/models.py 
new/authlib-1.7.2/authlib/oauth2/rfc8414/models.py
--- old/authlib-1.7.0/authlib/oauth2/rfc8414/models.py  2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/oauth2/rfc8414/models.py  2026-05-06 
10:08:51.000000000 +0200
@@ -1,3 +1,4 @@
+from authlib.common.language import is_valid_language_tag
 from authlib.common.security import is_secure_transport
 from authlib.common.urls import is_valid_url
 from authlib.common.urls import urlparse
@@ -208,7 +209,7 @@
         [RFC5646].  If omitted, the set of supported languages and scripts
         is unspecified.
         """
-        validate_array_value(self, "ui_locales_supported")
+        validate_language_tags_array(self, "ui_locales_supported")
 
     def validate_op_policy_uri(self):
         """OPTIONAL.  URL that the authorization server provides to the
@@ -411,6 +412,15 @@
         raise ValueError(f'"{key}" MUST be JSON array')
 
 
+def validate_language_tags_array(metadata, key):
+    validate_array_value(metadata, key)
+    values = metadata.get(key)
+    if values is not None:
+        for tag in values:
+            if not is_valid_language_tag(tag):
+                raise ValueError(f'"{key}" MUST contain BCP 47 language tags')
+
+
 def validate_boolean_value(metadata, key):
     if key not in metadata:
         return
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/oidc/core/claims.py 
new/authlib-1.7.2/authlib/oidc/core/claims.py
--- old/authlib-1.7.0/authlib/oidc/core/claims.py       2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/oidc/core/claims.py       2026-05-06 
10:08:51.000000000 +0200
@@ -6,6 +6,7 @@
 from joserfc.errors import MissingClaimError
 
 from authlib.common.encoding import to_bytes
+from authlib.common.language import is_valid_language_tag
 from authlib.oauth2.claims import JWTClaims
 from authlib.oauth2.rfc6749.util import scope_to_list
 
@@ -244,6 +245,12 @@
         "phone": ["phone_number", "phone_number_verified"],
     }
 
+    def validate_locale(self):
+        """Validate the locale claim is a BCP 47 language tag."""
+        locale = self.get("locale")
+        if locale is not None and not is_valid_language_tag(locale):
+            raise ValueError('"locale" MUST be a BCP 47 language tag')
+
     def filter(self, scope: str):
         """Return a new UserInfo object containing only the claims matching 
the scope passed in parameter."""
         scope = scope_to_list(scope)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/oidc/core/grants/hybrid.py 
new/authlib-1.7.2/authlib/oidc/core/grants/hybrid.py
--- old/authlib-1.7.0/authlib/oidc/core/grants/hybrid.py        2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/oidc/core/grants/hybrid.py        2026-05-06 
10:08:51.000000000 +0200
@@ -49,19 +49,20 @@
         raise NotImplementedError()
 
     def validate_authorization_request(self):
-        if not is_openid_scope(self.request.payload.scope):
-            raise InvalidScopeError(
-                "Missing 'openid' scope",
-                redirect_uri=self.request.payload.redirect_uri,
-                redirect_fragment=True,
-            )
         self.register_hook(
             "after_validate_authorization_request_payload",
             lambda grant, redirect_uri: validate_nonce(
                 grant.request, grant.exists_nonce, required=True
             ),
         )
-        return validate_code_authorization_request(self)
+        redirect_uri = validate_code_authorization_request(self)
+        if not is_openid_scope(self.request.payload.scope):
+            raise InvalidScopeError(
+                "Missing 'openid' scope",
+                redirect_uri=redirect_uri,
+                redirect_fragment=True,
+            )
+        return redirect_uri
 
     def create_granted_params(self, grant_user):
         self.request.user = grant_user
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/oidc/core/grants/implicit.py 
new/authlib-1.7.2/authlib/oidc/core/grants/implicit.py
--- old/authlib-1.7.0/authlib/oidc/core/grants/implicit.py      2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/oidc/core/grants/implicit.py      2026-05-06 
10:08:51.000000000 +0200
@@ -68,13 +68,13 @@
         return [client.get_client_id()]
 
     def validate_authorization_request(self):
+        redirect_uri = super().validate_authorization_request()
         if not is_openid_scope(self.request.payload.scope):
             raise InvalidScopeError(
                 "Missing 'openid' scope",
-                redirect_uri=self.request.payload.redirect_uri,
+                redirect_uri=redirect_uri,
                 redirect_fragment=True,
             )
-        redirect_uri = super().validate_authorization_request()
         try:
             validate_nonce(self.request, self.exists_nonce, required=True)
         except OAuth2Error as error:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/authlib/oidc/discovery/models.py 
new/authlib-1.7.2/authlib/oidc/discovery/models.py
--- old/authlib-1.7.0/authlib/oidc/discovery/models.py  2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/authlib/oidc/discovery/models.py  2026-05-06 
10:08:51.000000000 +0200
@@ -1,6 +1,7 @@
 from authlib.oauth2.rfc8414 import AuthorizationServerMetadata
 from authlib.oauth2.rfc8414.models import validate_array_value
 from authlib.oauth2.rfc8414.models import validate_boolean_value
+from authlib.oauth2.rfc8414.models import validate_language_tags_array
 
 
 class OpenIDProviderMetadata(AuthorizationServerMetadata):
@@ -236,7 +237,7 @@
         language tag values. Not all languages and scripts are necessarily
         supported for all Claim values.
         """
-        validate_array_value(self, "claims_locales_supported")
+        validate_language_tags_array(self, "claims_locales_supported")
 
     def validate_claims_parameter_supported(self):
         """OPTIONAL. Boolean value specifying whether the OP supports use of
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/docs/upgrades/changelog.rst 
new/authlib-1.7.2/docs/upgrades/changelog.rst
--- old/authlib-1.7.0/docs/upgrades/changelog.rst       2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/docs/upgrades/changelog.rst       2026-05-06 
10:08:51.000000000 +0200
@@ -6,6 +6,30 @@
 
 Here you can see the full list of changes between each Authlib release.
 
+Version 1.7.2
+-------------
+
+**Released on May 6, 2026**
+
+- Fix ``PrivateKeyJWT`` rejecting ``ECKey`` private keys (e.g. for ``ES256``).
+  The client private key is no longer forcibly imported as an ``RSAKey``;
+  ``joserfc``'s key auto-detection is used instead. :pr:`852`
+- Allow ``ClientSecretJWT`` and ``PrivateKeyJWT`` to sign client assertions
+  with non-recommended algorithms (e.g. ``RS384``) when explicitly set via the
+  ``alg`` parameter. :issue:`883`
+- Validate BCP 47 language tags in ``ui_locales_supported``, 
``claims_locales_supported``
+  and ``UserInfo.locale``. :pr:`873`
+
+Version 1.7.1
+-------------
+
+**Released on May 4, 2026**
+
+- Fix ``AuthlibDeprecationWarning`` being emitted on import when using 
integrations
+  that do not use ``authlib.jose`` directly. :issue:`880`
+- Fix redirecting to unvalidated ``redirect_uri`` on ``InvalidScopeError``
+  in ``OpenIDImplicitGrant`` and ``OpenIDHybridGrant``.
+
 Version 1.7.0
 -------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/authlib-1.7.0/tests/core/test_oauth2/test_rfc7523_private_key.py 
new/authlib-1.7.2/tests/core/test_oauth2/test_rfc7523_private_key.py
--- old/authlib-1.7.0/tests/core/test_oauth2/test_rfc7523_private_key.py        
2026-04-18 12:59:19.000000000 +0200
+++ new/authlib-1.7.2/tests/core/test_oauth2/test_rfc7523_private_key.py        
2026-05-06 10:08:51.000000000 +0200
@@ -1,7 +1,9 @@
 import time
+from types import SimpleNamespace
 from unittest import mock
 
 from joserfc import jwt
+from joserfc.jwk import ECKey
 from joserfc.jwk import RSAKey
 
 from authlib.oauth2.rfc7523 import PrivateKeyJWT
@@ -229,3 +231,32 @@
         "role": "bar",
     }
     assert {"alg": "RS256", "typ": "JWT"} == decoded.header
+
+
+def test_sign_with_ec_key():
+    """``PrivateKeyJWT`` should accept an ``ECKey`` private key and sign with 
ES256."""
+    ec_key = ECKey.generate_key("P-256", private=True)
+    jwt_signer = PrivateKeyJWT(alg="ES256")
+    auth = SimpleNamespace(client_id="client_id_1", client_secret=ec_key)
+
+    data = jwt_signer.sign(auth, "https://provider.test/oauth/access_token";)
+    decoded = jwt.decode(data, ec_key)
+
+    assert decoded.claims["iss"] == "client_id_1"
+    assert decoded.claims["sub"] == "client_id_1"
+    assert decoded.claims["aud"] == "https://provider.test/oauth/access_token";
+    assert decoded.header["alg"] == "ES256"
+
+
+def test_sign_with_non_recommended_alg():
+    """Signing must work with algorithms outside joserfc's recommended set."""
+    jwt_signer = PrivateKeyJWT(alg="RS384")
+
+    auth = mock.MagicMock()
+    auth.client_id = "client_id_1"
+    auth.client_secret = private_key
+
+    data = jwt_signer.sign(auth, "https://provider.test/oauth/access_token";)
+    decoded = jwt.decode(data, RSAKey.import_key(public_key), 
algorithms=["RS384"])
+
+    assert decoded.header["alg"] == "RS384"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/tests/core/test_oauth2/test_rfc8414.py 
new/authlib-1.7.2/tests/core/test_oauth2/test_rfc8414.py
--- old/authlib-1.7.0/tests/core/test_oauth2/test_rfc8414.py    2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/tests/core/test_oauth2/test_rfc8414.py    2026-05-06 
10:08:51.000000000 +0200
@@ -260,14 +260,38 @@
     metadata = AuthorizationServerMetadata()
     metadata.validate_ui_locales_supported()
 
+    # valid
+    metadata = AuthorizationServerMetadata({"ui_locales_supported": ["en"]})
+    metadata.validate_ui_locales_supported()
+
+    metadata = AuthorizationServerMetadata(
+        {"ui_locales_supported": ["en-US", "fr-FR", "zh-Hans-CN"]}
+    )
+    metadata.validate_ui_locales_supported()
+
+    # private use
+    metadata = AuthorizationServerMetadata({"ui_locales_supported": 
["x-custom"]})
+    metadata.validate_ui_locales_supported()
+
     # not array
     metadata = AuthorizationServerMetadata({"ui_locales_supported": "en"})
     with pytest.raises(ValueError, match="JSON array"):
         metadata.validate_ui_locales_supported()
 
-    # valid
-    metadata = AuthorizationServerMetadata({"ui_locales_supported": ["en"]})
-    metadata.validate_ui_locales_supported()
+    # underscore instead of hyphen
+    metadata = AuthorizationServerMetadata({"ui_locales_supported": ["fr_FR"]})
+    with pytest.raises(ValueError, match="BCP 47"):
+        metadata.validate_ui_locales_supported()
+
+    # single char (not private-use)
+    metadata = AuthorizationServerMetadata({"ui_locales_supported": ["a-US"]})
+    with pytest.raises(ValueError, match="BCP 47"):
+        metadata.validate_ui_locales_supported()
+
+    # subtag too long
+    metadata = AuthorizationServerMetadata({"ui_locales_supported": 
["toolongsubtag"]})
+    with pytest.raises(ValueError, match="BCP 47"):
+        metadata.validate_ui_locales_supported()
 
 
 def test_validate_op_policy_uri():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/tests/core/test_oidc/test_core.py 
new/authlib-1.7.2/tests/core/test_oidc/test_core.py
--- old/authlib-1.7.0/tests/core/test_oidc/test_core.py 2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/tests/core/test_oidc/test_core.py 2026-05-06 
10:08:51.000000000 +0200
@@ -172,3 +172,17 @@
     assert user.email is None
     with pytest.raises(AttributeError):
         user.invalid  # noqa: B018
+
+
+def test_userinfo_validate_locale():
+    UserInfo({"sub": "1"}).validate_locale()
+    UserInfo({"sub": "1", "locale": "en"}).validate_locale()
+    UserInfo({"sub": "1", "locale": "en-US"}).validate_locale()
+    UserInfo({"sub": "1", "locale": "zh-Hans-CN"}).validate_locale()
+    UserInfo({"sub": "1", "locale": "x-custom"}).validate_locale()
+
+    with pytest.raises(ValueError, match="BCP 47"):
+        UserInfo({"sub": "1", "locale": "fr_FR"}).validate_locale()
+
+    with pytest.raises(ValueError, match="BCP 47"):
+        UserInfo({"sub": "1", "locale": "toolongsubtag"}).validate_locale()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/authlib-1.7.0/tests/core/test_oidc/test_discovery.py 
new/authlib-1.7.2/tests/core/test_oidc/test_discovery.py
--- old/authlib-1.7.0/tests/core/test_oidc/test_discovery.py    2026-04-18 
12:59:19.000000000 +0200
+++ new/authlib-1.7.2/tests/core/test_oidc/test_discovery.py    2026-05-06 
10:08:51.000000000 +0200
@@ -119,6 +119,19 @@
 def test_validate_claims_locales_supported():
     _call_validate_array("claims_locales_supported", ["en-US"])
 
+    metadata = OpenIDProviderMetadata(
+        {"claims_locales_supported": ["en-US", "fr-FR", "zh-Hans-CN"]}
+    )
+    metadata.validate_claims_locales_supported()
+
+    metadata = OpenIDProviderMetadata({"claims_locales_supported": ["fr_FR"]})
+    with pytest.raises(ValueError, match="BCP 47"):
+        metadata.validate_claims_locales_supported()
+
+    metadata = OpenIDProviderMetadata({"claims_locales_supported": 
["toolongsubtag"]})
+    with pytest.raises(ValueError, match="BCP 47"):
+        metadata.validate_claims_locales_supported()
+
 
 def test_validate_claims_parameter_supported():
     _call_validate_boolean("claims_parameter_supported")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/authlib-1.7.0/tests/flask/test_oauth2/test_openid_hybrid_grant.py 
new/authlib-1.7.2/tests/flask/test_oauth2/test_openid_hybrid_grant.py
--- old/authlib-1.7.0/tests/flask/test_oauth2/test_openid_hybrid_grant.py       
2026-04-18 12:59:19.000000000 +0200
+++ new/authlib-1.7.2/tests/flask/test_oauth2/test_openid_hybrid_grant.py       
2026-05-06 10:08:51.000000000 +0200
@@ -160,6 +160,23 @@
     assert "error=invalid_scope" in rv.location
 
 
+def 
test_missing_openid_in_scope_does_not_redirect_to_unregistered_uri(test_client):
+    """An unregistered redirect_uri must not be followed even when openid 
scope is missing."""
+    rv = test_client.post(
+        "/oauth/authorize",
+        data={
+            "client_id": "client-id",
+            "response_type": "code id_token",
+            "state": "s",
+            "nonce": "n",
+            "scope": "profile",
+            "redirect_uri": "https://evil.example.com/phish";,
+        },
+    )
+    assert rv.status_code == 400
+    assert rv.headers.get("Location") is None
+
+
 def test_access_denied(test_client):
     rv = test_client.post(
         "/oauth/authorize",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/authlib-1.7.0/tests/flask/test_oauth2/test_openid_implict_grant.py 
new/authlib-1.7.2/tests/flask/test_oauth2/test_openid_implict_grant.py
--- old/authlib-1.7.0/tests/flask/test_oauth2/test_openid_implict_grant.py      
2026-04-18 12:59:19.000000000 +0200
+++ new/authlib-1.7.2/tests/flask/test_oauth2/test_openid_implict_grant.py      
2026-05-06 10:08:51.000000000 +0200
@@ -107,6 +107,23 @@
     assert "error=invalid_scope" in rv.location
 
 
+def 
test_missing_openid_in_scope_does_not_redirect_to_unregistered_uri(test_client):
+    """An unregistered redirect_uri must not be followed even when openid 
scope is missing."""
+    rv = test_client.post(
+        "/oauth/authorize",
+        data={
+            "response_type": "id_token token",
+            "client_id": "client-id",
+            "scope": "profile",
+            "state": "s",
+            "nonce": "n",
+            "redirect_uri": "https://evil.example.com/phish";,
+        },
+    )
+    assert rv.status_code == 400
+    assert rv.headers.get("Location") is None
+
+
 def test_denied(test_client):
     rv = test_client.post(
         "/oauth/authorize",

Reply via email to