Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-msal for openSUSE:Factory 
checked in at 2026-02-07 15:33:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-msal (Old)
 and      /work/SRC/openSUSE:Factory/.python-msal.new.1670 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-msal"

Sat Feb  7 15:33:18 2026 rev:33 rq:1331689 version:1.35.0~b1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-msal/python-msal.changes  2025-10-23 
18:31:37.939542090 +0200
+++ /work/SRC/openSUSE:Factory/.python-msal.new.1670/python-msal.changes        
2026-02-07 15:33:39.785962896 +0100
@@ -1,0 +2,26 @@
+Fri Feb  6 09:38:14 UTC 2026 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- Update to version 1.35.0b1
+  * The managed identity code path no longer has a dependency on the
+    socket.getfqdn(). No API change is needed. Existing MSAL-powered
+    apps will automatically pick up this new behavior.
+  * This version of MSAL Python will pick up PyMsalRuntime 0.20.*.
+    No API change is needed. Existing MSAL-powered apps will
+    automatically pick up this new behavior.
+  * The thumbprint name-value pair in the client_credential parameter
+    becomes optional now. See API docs for usage.
+  * ROPC deprecation by @Ugonnaak1 in (#855)
+  * Test case for token response scope differing from token request
+    scope by @rayluo in (#856)
+  * Update pymsalruntime version range to handle the latest 0.20.0 release
+    by @DharshanBJ in (#858)
+  * Document how to enable sha256 for client credential by @rayluo in (#833)
+  * Remove the reliance on getfqdn() by @rayluo in (#859)
+  * Thumbprint for certificate made optional by @vi7us in (#835)
+  * Support Python 3.14 by @rayluo in (#861)
+  * Explicitly remove issuer from the OIDC discovery response
+    by @rayluo in (#863)
+  * Suppress CodeQL warning by @bgavrilMS in (#867)
+- Override upstream version with 1.35.0~b1
+
+-------------------------------------------------------------------

Old:
----
  msal-1.34.0.tar.gz

New:
----
  msal-1.35.0b1.tar.gz

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

Other differences:
------------------
++++++ python-msal.spec ++++++
--- /var/tmp/diff_new_pack.UxZk3P/_old  2026-02-07 15:33:40.657999125 +0100
+++ /var/tmp/diff_new_pack.UxZk3P/_new  2026-02-07 15:33:40.657999125 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-msal
 #
-# Copyright (c) 2025 SUSE LLC
+# 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
@@ -15,15 +15,18 @@
 # Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
+
+%define realversion 1.35.0b1
+
 %{?sle15_python_module_pythons}
 Name:           python-msal
-Version:        1.34.0
+Version:        1.35.0~b1
 Release:        0
 Summary:        Microsoft Authentication Library (MSAL) for Python
 License:        MIT
 Group:          Development/Languages/Python
 URL:            
https://github.com/AzureAD/microsoft-authentication-library-for-python
-Source:         
https://files.pythonhosted.org/packages/source/m/msal/msal-%{version}.tar.gz
+Source:         
https://files.pythonhosted.org/packages/source/m/msal/msal-%{realversion}.tar.gz
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module setuptools}
@@ -50,7 +53,7 @@
 standard OAuth2 and OpenID Connect.
 
 %prep
-%setup -q -n msal-%{version}
+%setup -q -n msal-%{realversion}
 
 %build
 %pyproject_wheel

++++++ msal-1.34.0.tar.gz -> msal-1.35.0b1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/PKG-INFO new/msal-1.35.0b1/PKG-INFO
--- old/msal-1.34.0/PKG-INFO    2025-09-23 01:05:43.740560000 +0200
+++ new/msal-1.35.0b1/PKG-INFO  2026-01-07 00:51:47.246401800 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: msal
-Version: 1.34.0
+Version: 1.35.0b1
 Summary: The Microsoft Authentication Library (MSAL) for Python library 
enables your app to access the Microsoft Cloud by supporting authentication of 
users with Microsoft Azure Active Directory accounts (AAD) and Microsoft 
Accounts (MSA) using industry standard OAuth2 and OpenID Connect.
 Home-page: 
https://github.com/AzureAD/microsoft-authentication-library-for-python
 Author: Microsoft Corporation
@@ -20,6 +20,7 @@
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
 Requires-Python: >=3.8
@@ -29,9 +30,9 @@
 Requires-Dist: PyJWT[crypto]<3,>=1.0.0
 Requires-Dist: cryptography<49,>=2.5
 Provides-Extra: broker
-Requires-Dist: pymsalruntime<0.19,>=0.14; (python_version >= "3.6" and 
platform_system == "Windows") and extra == "broker"
-Requires-Dist: pymsalruntime<0.19,>=0.17; (python_version >= "3.8" and 
platform_system == "Darwin") and extra == "broker"
-Requires-Dist: pymsalruntime<0.19,>=0.18; (python_version >= "3.8" and 
platform_system == "Linux") and extra == "broker"
+Requires-Dist: pymsalruntime<0.21,>=0.14; (python_version >= "3.8" and 
platform_system == "Windows") and extra == "broker"
+Requires-Dist: pymsalruntime<0.21,>=0.17; (python_version >= "3.8" and 
platform_system == "Darwin") and extra == "broker"
+Requires-Dist: pymsalruntime<0.21,>=0.18; (python_version >= "3.8" and 
platform_system == "Linux") and extra == "broker"
 Dynamic: license-file
 
 # Microsoft Authentication Library (MSAL) for Python
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/msal/application.py 
new/msal-1.35.0b1/msal/application.py
--- old/msal-1.34.0/msal/application.py 2025-09-23 01:05:37.000000000 +0200
+++ new/msal-1.35.0b1/msal/application.py       2026-01-07 00:51:42.000000000 
+0100
@@ -66,10 +66,24 @@
     except:
         return raw
 
+def _extract_cert_and_thumbprints(cert):
+    # Cert concepts https://security.stackexchange.com/a/226758/125264
+    from cryptography.hazmat.primitives import hashes, serialization
+    cert_pem = cert.public_bytes(  # Requires cryptography 1.0+
+        encoding=serialization.Encoding.PEM).decode()
+    x5c = [
+        '\n'.join(
+            cert_pem.splitlines()
+            [1:-1]  # Strip the "--- header ---" and "--- footer ---"
+        )
+    ]
+    # 
https://cryptography.io/en/latest/x509/reference/#x-509-certificate-object - 
Requires cryptography 0.7+
+    sha256_thumbprint = cert.fingerprint(hashes.SHA256()).hex() 
+    sha1_thumbprint = cert.fingerprint(hashes.SHA1()).hex()  # CodeQL 
[SM02167] for legacy support such as ADFS
+    return sha256_thumbprint, sha1_thumbprint, x5c
 
 def _parse_pfx(pfx_path, passphrase_bytes):
     # Cert concepts https://security.stackexchange.com/a/226758/125264
-    from cryptography.hazmat.primitives import hashes, serialization
     from cryptography.hazmat.primitives.serialization import pkcs12
     with open(pfx_path, 'rb') as f:
         private_key, cert, _ = pkcs12.load_key_and_certificates(  # 
cryptography 2.5+
@@ -77,13 +91,7 @@
             f.read(), passphrase_bytes)
     if not (private_key and cert):
         raise ValueError("Your PFX file shall contain both private key and 
cert")
-    cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM).decode() 
 # cryptography 1.0+
-    x5c = [
-        '\n'.join(cert_pem.splitlines()[1:-1])  # Strip the "--- header ---" 
and "--- footer ---"
-    ]
-    sha256_thumbprint = cert.fingerprint(hashes.SHA256()).hex()  # 
cryptography 0.7+
-    sha1_thumbprint = cert.fingerprint(hashes.SHA1()).hex()  # cryptography 
0.7+
-        # 
https://cryptography.io/en/latest/x509/reference/#x-509-certificate-object
+    sha256_thumbprint, sha1_thumbprint, x5c = 
_extract_cert_and_thumbprints(cert)
     return private_key, sha256_thumbprint, sha1_thumbprint, x5c
 
 
@@ -280,12 +288,20 @@
 
             .. admonition:: Support using a certificate in X.509 (.pem) format
 
+                Deprecated because it uses SHA-1 thumbprint,
+                unless you are still using ADFS which supports SHA-1 
thumbprint only.
+                Please use the .pfx option documented later in this page.
+
                 Feed in a dict in this form::
 
                     {
                         "private_key": "...-----BEGIN PRIVATE KEY-----... in 
PEM format",
-                        "thumbprint": "A1B2C3D4E5F6...",
-                        "passphrase": "Passphrase if the private_key is 
encrypted (Optional. Added in version 1.6.0)",
+                        "thumbprint": "An SHA-1 thumbprint such as 
A1B2C3D4E5F6..."
+                            "Changed in version 1.35.0, if thumbprint is 
absent"
+                            "and a public_certificate is present, MSAL will"
+                            "automatically calculate an SHA-256 thumbprint 
instead.",
+                        "passphrase": "Needed if the private_key is encrypted 
(Added in version 1.6.0)",
+                        "public_certificate": "...-----BEGIN 
CERTIFICATE-----...",  # Needed if you use Subject Name/Issuer auth. Added in 
version 0.5.0.
                     }
 
                 MSAL Python requires a "private_key" in PEM format.
@@ -296,25 +312,11 @@
                 The thumbprint is available in your app's registration in 
Azure Portal.
                 Alternatively, you can `calculate the thumbprint 
<https://github.com/Azure/azure-sdk-for-python/blob/07d10639d7e47f4852eaeb74aef5d569db499d6e/sdk/identity/azure-identity/azure/identity/_credentials/certificate.py#L94-L97>`_.
 
-            .. admonition:: Support Subject Name/Issuer Auth with a cert in 
.pem
-
-                `Subject Name/Issuer Auth
-                
<https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/60>`_
-                is an approach to allow easier certificate rotation.
-
-                *Added in version 0.5.0*::
-
-                    {
-                        "private_key": "...-----BEGIN PRIVATE KEY-----... in 
PEM format",
-                        "thumbprint": "A1B2C3D4E5F6...",
-                        "public_certificate": "...-----BEGIN 
CERTIFICATE-----...",
-                        "passphrase": "Passphrase if the private_key is 
encrypted (Optional. Added in version 1.6.0)",
-                    }
-
                 ``public_certificate`` (optional) is public key certificate
-                which will be sent through 'x5c' JWT header only for
-                subject name and issuer authentication to support cert auto 
rolls.
-
+                which will be sent through 'x5c' JWT header.
+                This is useful when you use `Subject Name/Issuer Authentication
+                
<https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/60>`_
+                which is an approach to allow easier certificate rotation.
                 Per `specs 
<https://tools.ietf.org/html/rfc7515#section-4.1.6>`_,
                 "the certificate containing
                 the public key corresponding to the key used to digitally sign 
the
@@ -338,11 +340,14 @@
 
             .. admonition:: Supporting reading client certificates from PFX 
files
 
+                This usage will automatically use SHA-256 thumbprint of the 
certificate.
+
                 *Added in version 1.29.0*:
                 Feed in a dictionary containing the path to a PFX file::
 
                     {
-                        "private_key_pfx_path": "/path/to/your.pfx",
+                        "private_key_pfx_path": "/path/to/your.pfx",  # Added 
in version 1.29.0
+                        "public_certificate": True,  # Only needed if you use 
Subject Name/Issuer auth. Added in version 1.30.0
                         "passphrase": "Passphrase if the private_key is 
encrypted (Optional)",
                     }
 
@@ -350,17 +355,11 @@
 
                     openssl pkcs12 -export -out certificate.pfx -inkey 
privateKey.key -in certificate.pem
 
-            .. admonition:: Support Subject Name/Issuer Auth with a cert in 
.pfx
-
-                *Added in version 1.30.0*:
+                `Subject Name/Issuer Auth
+                
<https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/60>`_
+                is an approach to allow easier certificate rotation.
                 If your .pfx file contains both the private key and public 
cert,
-                you can opt in for Subject Name/Issuer Auth like this::
-
-                    {
-                        "private_key_pfx_path": "/path/to/your.pfx",
-                        "public_certificate": True,
-                        "passphrase": "Passphrase if the private_key is 
encrypted (Optional)",
-                    }
+                you can opt in for Subject Name/Issuer Auth by setting 
"public_certificate" to ``True``.
 
         :type client_credential: Union[dict, str, None]
 
@@ -815,15 +814,30 @@
                         passphrase_bytes)
                     if client_credential.get("public_certificate") is True and 
x5c:
                         headers["x5c"] = x5c
-                elif (
-                        client_credential.get("private_key")  # PEM blob
-                        and client_credential.get("thumbprint")):
-                    sha1_thumbprint = client_credential["thumbprint"]
-                    if passphrase_bytes:
-                        private_key = _load_private_key_from_pem_str(
+                elif client_credential.get("private_key"):  # PEM blob
+                    private_key = (  # handles both encrypted and unencrypted
+                        _load_private_key_from_pem_str(
                             client_credential['private_key'], passphrase_bytes)
-                    else:  # PEM without passphrase
-                        private_key = client_credential['private_key']
+                        if passphrase_bytes
+                        else client_credential['private_key']
+                    )
+
+                    # Determine thumbprints based on what's provided
+                    if client_credential.get("thumbprint"):
+                        # User provided a thumbprint - use it as SHA-1 
(legacy/manual approach)
+                        sha1_thumbprint = client_credential["thumbprint"]
+                        sha256_thumbprint = None
+                    elif 
isinstance(client_credential.get('public_certificate'), str):
+                        # No thumbprint provided, but we have a certificate to 
calculate thumbprints
+                        from cryptography import x509
+                        cert = x509.load_pem_x509_certificate(
+                            
_str2bytes(client_credential['public_certificate']))
+                        sha256_thumbprint, sha1_thumbprint, headers["x5c"] = (
+                            _extract_cert_and_thumbprints(cert))
+                    else:
+                        raise ValueError(
+                            "You must provide either 'thumbprint' or 
'public_certificate' "
+                            "from which the thumbprint can be calculated.")
                 else:
                     raise ValueError(
                         "client_credential needs to follow this format "
@@ -1840,7 +1854,17 @@
 
             - A successful response would contain "access_token" key,
             - an error response would contain "error" and usually 
"error_description".
+
+        [Deprecated] This API is deprecated for public client flows and will be
+        removed in a future release. Use a more secure flow instead.
+        Migration guide: https://aka.ms/msal-ropc-migration
+
         """
+        is_confidential_app = self.client_credential or isinstance(
+            self, ConfidentialClientApplication)
+        if not is_confidential_app:
+            warnings.warn("""This API has been deprecated for public client 
flows, please use a more secure flow.
+        See https://aka.ms/msal-ropc-migration for migration guidance""", 
DeprecationWarning)
         claims = _merge_claims_challenge_and_capabilities(
                 self._client_capabilities, claims_challenge)
         if self._enable_broker and sys.platform in ("win32", "darwin"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/msal/authority.py 
new/msal-1.35.0b1/msal/authority.py
--- old/msal-1.34.0/msal/authority.py   2025-09-23 01:05:37.000000000 +0200
+++ new/msal-1.35.0b1/msal/authority.py 2026-01-07 00:51:42.000000000 +0100
@@ -93,6 +93,7 @@
                 .format(authority_url)
                 ) + " Also please double check your tenant name or GUID is 
correct."
             raise ValueError(error_message)
+        openid_config.pop("issuer", None)  # Not used in MSAL.py, so remove it 
therefore no need to validate it
         logger.debug(
             'openid_config("%s") = %s', tenant_discovery_endpoint, 
openid_config)
         self.authorization_endpoint = openid_config['authorization_endpoint']
@@ -178,7 +179,7 @@
 def canonicalize(authority_or_auth_endpoint):
     # Returns (url_parsed_result, hostname_in_lowercase, tenant)
     authority = urlparse(authority_or_auth_endpoint)
-    if authority.scheme == "https":
+    if authority.scheme == "https" and authority.hostname:
         parts = authority.path.split("/")
         first_part = parts[1] if len(parts) >= 2 and parts[1] else None
         if authority.hostname.endswith(_CIAM_DOMAIN_SUFFIX):  # CIAM
@@ -192,7 +193,7 @@
             return authority, authority.hostname, parts[1]
     raise ValueError(
         "Your given address (%s) should consist of "
-        "an https url with a minimum of one segment in a path: e.g. "
+        "an https url with hostname and a minimum of one segment in a path: 
e.g. "
         "https://login.microsoftonline.com/{tenant} "
         "or https://{tenant_name}.ciamlogin.com/{tenant} "
         "or 
https://{tenant_name}.b2clogin.com/{tenant_name}.onmicrosoft.com/policy";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/msal/broker.py 
new/msal-1.35.0b1/msal/broker.py
--- old/msal-1.34.0/msal/broker.py      2025-09-23 01:05:37.000000000 +0200
+++ new/msal-1.35.0b1/msal/broker.py    2026-01-07 00:51:42.000000000 +0100
@@ -145,12 +145,20 @@
     params.set_additional_parameter("msal_client_ver", __version__)
     return params
 
+def _set_redirect_uri_for_linux(params):
+    if sys.platform == "linux":
+        # This is required by Linux Java Broker to set a non-empty valid 
redirect_uri
+        params.set_redirect_uri(
+            "https://login.microsoftonline.com/common/oauth2/nativeclient";
+        )
+
 def _signin_silently(
         authority, client_id, scopes, correlation_id=None, claims=None,
         enable_msa_pt=False,
         auth_scheme=None,
         **kwargs):
     params = _build_msal_runtime_auth_params(client_id, authority)
+    _set_redirect_uri_for_linux(params)
     params.set_requested_scopes(scopes)
     if claims:
         params.set_decoded_claims(claims)
@@ -240,6 +248,7 @@
     if account is None:
         return
     params = _build_msal_runtime_auth_params(client_id, authority)
+    _set_redirect_uri_for_linux(params)
     params.set_requested_scopes(scopes)
     if claims:
         params.set_decoded_claims(claims)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/msal/managed_identity.py 
new/msal-1.35.0b1/msal/managed_identity.py
--- old/msal-1.34.0/msal/managed_identity.py    2025-09-23 01:05:37.000000000 
+0200
+++ new/msal-1.35.0b1/msal/managed_identity.py  2026-01-07 00:51:42.000000000 
+0100
@@ -6,7 +6,6 @@
 import json
 import logging
 import os
-import socket
 import sys
 import time
 from urllib.parse import urlparse  # Python 3+
@@ -146,7 +145,10 @@
         (like what a ``PublicClientApplication`` does),
         not a token with application permissions for an app.
     """
-    __instance, _tenant = None, "managed_identity"  # Placeholders
+    __instance = "localhost"  # We used to get this value from socket.getfqdn()
+        # but it is unreliable because getfqdn() either hangs or returns empty 
value
+        # on some misconfigured machines
+    _tenant = "managed_identity"
     _TOKEN_SOURCE = "token_source"
     _TOKEN_SOURCE_IDP = "identity_provider"
     _TOKEN_SOURCE_CACHE = "cache"
@@ -252,11 +254,6 @@
         self._token_cache = token_cache or TokenCache()
         self._client_capabilities = client_capabilities
 
-    def _get_instance(self):
-        if self.__instance is None:
-            self.__instance = socket.getfqdn()  # Moved from class definition 
to here
-        return self.__instance
-
     def acquire_token_for_client(
         self,
         *,
@@ -302,7 +299,7 @@
                 target=[resource],
                 query=dict(
                     client_id=client_id_in_cache,
-                    environment=self._get_instance(),
+                    environment=self.__instance,
                     realm=self._tenant,
                     home_account_id=None,
                 ),
@@ -344,7 +341,7 @@
                     client_id=client_id_in_cache,
                     scope=[resource],
                     token_endpoint="https://{}/{}".format(
-                        self._get_instance(), self._tenant),
+                        self.__instance, self._tenant),
                     response=result,
                     params={},
                     data={},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/msal/sku.py new/msal-1.35.0b1/msal/sku.py
--- old/msal-1.34.0/msal/sku.py 2025-09-23 01:05:37.000000000 +0200
+++ new/msal-1.35.0b1/msal/sku.py       2026-01-07 00:51:42.000000000 +0100
@@ -2,5 +2,5 @@
 """
 
 # The __init__.py will import this. Not the other way around.
-__version__ = "1.34.0"
+__version__ = "1.35.0b1"
 SKU = "MSAL.Python"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/msal.egg-info/PKG-INFO 
new/msal-1.35.0b1/msal.egg-info/PKG-INFO
--- old/msal-1.34.0/msal.egg-info/PKG-INFO      2025-09-23 01:05:43.000000000 
+0200
+++ new/msal-1.35.0b1/msal.egg-info/PKG-INFO    2026-01-07 00:51:47.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: msal
-Version: 1.34.0
+Version: 1.35.0b1
 Summary: The Microsoft Authentication Library (MSAL) for Python library 
enables your app to access the Microsoft Cloud by supporting authentication of 
users with Microsoft Azure Active Directory accounts (AAD) and Microsoft 
Accounts (MSA) using industry standard OAuth2 and OpenID Connect.
 Home-page: 
https://github.com/AzureAD/microsoft-authentication-library-for-python
 Author: Microsoft Corporation
@@ -20,6 +20,7 @@
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
 Requires-Python: >=3.8
@@ -29,9 +30,9 @@
 Requires-Dist: PyJWT[crypto]<3,>=1.0.0
 Requires-Dist: cryptography<49,>=2.5
 Provides-Extra: broker
-Requires-Dist: pymsalruntime<0.19,>=0.14; (python_version >= "3.6" and 
platform_system == "Windows") and extra == "broker"
-Requires-Dist: pymsalruntime<0.19,>=0.17; (python_version >= "3.8" and 
platform_system == "Darwin") and extra == "broker"
-Requires-Dist: pymsalruntime<0.19,>=0.18; (python_version >= "3.8" and 
platform_system == "Linux") and extra == "broker"
+Requires-Dist: pymsalruntime<0.21,>=0.14; (python_version >= "3.8" and 
platform_system == "Windows") and extra == "broker"
+Requires-Dist: pymsalruntime<0.21,>=0.17; (python_version >= "3.8" and 
platform_system == "Darwin") and extra == "broker"
+Requires-Dist: pymsalruntime<0.21,>=0.18; (python_version >= "3.8" and 
platform_system == "Linux") and extra == "broker"
 Dynamic: license-file
 
 # Microsoft Authentication Library (MSAL) for Python
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/msal.egg-info/SOURCES.txt 
new/msal-1.35.0b1/msal.egg-info/SOURCES.txt
--- old/msal-1.34.0/msal.egg-info/SOURCES.txt   2025-09-23 01:05:43.000000000 
+0200
+++ new/msal-1.35.0b1/msal.egg-info/SOURCES.txt 2026-01-07 00:51:47.000000000 
+0100
@@ -47,6 +47,7 @@
 tests/test_mex.py
 tests/test_mi.py
 tests/test_oidc.py
+tests/test_optional_thumbprint.py
 tests/test_throttled_http_client.py
 tests/test_token_cache.py
 tests/test_wstrust.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/msal.egg-info/requires.txt 
new/msal-1.35.0b1/msal.egg-info/requires.txt
--- old/msal-1.34.0/msal.egg-info/requires.txt  2025-09-23 01:05:43.000000000 
+0200
+++ new/msal-1.35.0b1/msal.egg-info/requires.txt        2026-01-07 
00:51:47.000000000 +0100
@@ -4,11 +4,11 @@
 
 [broker]
 
-[broker:python_version >= "3.6" and platform_system == "Windows"]
-pymsalruntime<0.19,>=0.14
-
 [broker:python_version >= "3.8" and platform_system == "Darwin"]
-pymsalruntime<0.19,>=0.17
+pymsalruntime<0.21,>=0.17
 
 [broker:python_version >= "3.8" and platform_system == "Linux"]
-pymsalruntime<0.19,>=0.18
+pymsalruntime<0.21,>=0.18
+
+[broker:python_version >= "3.8" and platform_system == "Windows"]
+pymsalruntime<0.21,>=0.14
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/setup.cfg new/msal-1.35.0b1/setup.cfg
--- old/msal-1.34.0/setup.cfg   2025-09-23 01:05:43.740560000 +0200
+++ new/msal-1.35.0b1/setup.cfg 2026-01-07 00:51:47.247401700 +0100
@@ -22,6 +22,7 @@
        Programming Language :: Python :: 3.11
        Programming Language :: Python :: 3.12
        Programming Language :: Python :: 3.13
+       Programming Language :: Python :: 3.14
        License :: OSI Approved :: MIT License
        Operating System :: OS Independent
 project_urls = 
@@ -43,9 +44,9 @@
 
 [options.extras_require]
 broker = 
-       pymsalruntime>=0.14,<0.19; python_version>='3.6' and 
platform_system=='Windows'
-       pymsalruntime>=0.17,<0.19; python_version>='3.8' and 
platform_system=='Darwin'
-       pymsalruntime>=0.18,<0.19; python_version>='3.8' and 
platform_system=='Linux'
+       pymsalruntime>=0.14,<0.21; python_version>='3.8' and 
platform_system=='Windows'
+       pymsalruntime>=0.17,<0.21; python_version>='3.8' and 
platform_system=='Darwin'
+       pymsalruntime>=0.18,<0.21; python_version>='3.8' and 
platform_system=='Linux'
 
 [options.packages.find]
 exclude = 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/tests/test_application.py 
new/msal-1.35.0b1/tests/test_application.py
--- old/msal-1.34.0/tests/test_application.py   2025-09-23 01:05:37.000000000 
+0200
+++ new/msal-1.35.0b1/tests/test_application.py 2026-01-07 00:51:42.000000000 
+0100
@@ -875,3 +875,50 @@
                 parent_window_handle=app.CONSOLE_WINDOW_HANDLE,
                 )
             self.assertEqual(result.get("error"), "broker_error")
+
+
+class MismatchingScopeTestCase(unittest.TestCase):
+    """Test cache behavior when HTTP response scope differs from requested 
scope"""
+
+    def test_token_should_be_cached_with_response_scope(self):
+        """Based on https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
+        authorization server may issue an access token with different scope.
+        For example, eSTS normalizes scopes by adding or removing trailing 
slash.
+        Calling app is supposed to use the normalized scope for subsequent 
calls.
+        """
+
+        # Create a fresh app instance
+        app = ConfidentialClientApplication(
+            "client_id", client_credential="secret",
+            authority="https://login.microsoftonline.com/common";)
+
+        # Mocked request: ask for "invalid_scope" scope but receive 
"valid_scope1 valid_scope2" scope in response
+        def mock_post(url, headers=None, *args, **kwargs):
+            return MinimalResponse(status_code=200, text=json.dumps({
+                "access_token": "AT_with_valid_scope1_valid_scope2_scopes",
+                "expires_in": 3600,
+                "scope": "valid_scope1 valid_scope2",  # Response scope 
differs from requested scope
+                "token_type": "Bearer"
+            }))
+
+        result1 = app.acquire_token_for_client(["invalid_scope"], 
post=mock_post)
+        self.assertEqual(result1[app._TOKEN_SOURCE], app._TOKEN_SOURCE_IDP)
+        self.assertEqual("AT_with_valid_scope1_valid_scope2_scopes", 
result1.get("access_token"))
+        self.assertEqual(["valid_scope1", "valid_scope2"], 
result1.get("scope").split())  # Scope from response
+
+        # Second request: ask for same "invalid_scope" scope again
+        # Since cached token has "valid_scope1 valid_scope2" scopes, it 
shouldn't match the "invalid_scope" request
+        # This should go to IDP again and receive the same response
+        result2 = app.acquire_token_for_client(["invalid_scope"], 
post=mock_post)
+        # Should get a new token from IDP, not from cache
+        self.assertEqual(result2[app._TOKEN_SOURCE], app._TOKEN_SOURCE_IDP)
+        self.assertEqual("AT_with_valid_scope1_valid_scope2_scopes", 
result2.get("access_token"))
+        self.assertEqual(["valid_scope1", "valid_scope2"], 
result2.get("scope").split())
+
+        # Third and fourth requests: ask for individual valid scopes
+        # Should hit cache for the token that has "valid_scope1 valid_scope2" 
scopes
+        for scope in ["valid_scope1", "valid_scope2"]:
+            result = app.acquire_token_for_client([scope])
+            self.assertEqual(result[app._TOKEN_SOURCE], 
app._TOKEN_SOURCE_CACHE)
+            self.assertEqual("AT_with_valid_scope1_valid_scope2_scopes", 
result.get("access_token"))
+            self.assertIsNone(result.get("scope"), "scope field is not 
returned when token comes from cache")
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/tests/test_authority.py 
new/msal-1.35.0b1/tests/test_authority.py
--- old/msal-1.34.0/tests/test_authority.py     2025-09-23 01:05:37.000000000 
+0200
+++ new/msal-1.35.0b1/tests/test_authority.py   2026-01-07 00:51:42.000000000 
+0100
@@ -190,6 +190,10 @@
         with self.assertRaises(ValueError):
             canonicalize("https://no.tenant.example.com/";)
 
+    def test_canonicalize_rejects_empty_host(self):
+        with self.assertRaises(ValueError):
+            canonicalize("https:///tenant";)
+
 
 @unittest.skipIf(os.getenv("TRAVIS_TAG"), "Skip network io during tagged 
release")
 class TestAuthorityInternalHelperUserRealmDiscovery(unittest.TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/tests/test_e2e.py 
new/msal-1.35.0b1/tests/test_e2e.py
--- old/msal-1.34.0/tests/test_e2e.py   2025-09-23 01:05:37.000000000 +0200
+++ new/msal-1.35.0b1/tests/test_e2e.py 2026-01-07 00:51:42.000000000 +0100
@@ -841,14 +841,14 @@
 
 
 class WorldWideTestCase(LabBasedTestCase):
-    _ADFS_LABS_DECOMMISSIONED = "ADFS labs were decommissioned since July 2025 
until further notice"
+    _ADFS_LABS_UNAVAILABLE = "ADFS labs were temporarily down since July 2025 
until further notice"
 
     def test_aad_managed_user(self):  # Pure cloud
         config = self.get_lab_user(usertype="cloud")
         config["password"] = self.get_lab_user_secret(config["lab_name"])
         self._test_username_password(**config)
 
-    @unittest.skip(_ADFS_LABS_DECOMMISSIONED)
+    @unittest.skip(_ADFS_LABS_UNAVAILABLE)
     def test_adfs4_fed_user(self):
         config = self.get_lab_user(usertype="federated", 
federationProvider="ADFSv4")
         config["password"] = self.get_lab_user_secret(config["lab_name"])
@@ -866,7 +866,7 @@
         config["password"] = self.get_lab_user_secret(config["lab_name"])
         self._test_username_password(**config)
 
-    @unittest.skip(_ADFS_LABS_DECOMMISSIONED)
+    @unittest.skip(_ADFS_LABS_UNAVAILABLE)
     def test_adfs2019_fed_user(self):
         try:
             config = self.get_lab_user(usertype="federated", 
federationProvider="ADFSv2019")
@@ -895,7 +895,7 @@
             prompt="select_account",  # In MSAL Python, this resets login_hint
             ))
 
-    @unittest.skip(_ADFS_LABS_DECOMMISSIONED)
+    @unittest.skip(_ADFS_LABS_UNAVAILABLE)
     def test_ropc_adfs2019_onprem(self):
         # Configuration is derived from 
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4.7.0/tests/Microsoft.Identity.Test.Common/TestConstants.cs#L250-L259
         config = self.get_lab_user(usertype="onprem", 
federationProvider="ADFSv2019")
@@ -904,7 +904,7 @@
         config["password"] = self.get_lab_user_secret(config["lab_name"])
         self._test_username_password(**config)
 
-    @unittest.skip(_ADFS_LABS_DECOMMISSIONED)
+    @unittest.skip(_ADFS_LABS_UNAVAILABLE)
     def test_adfs2019_onprem_acquire_token_by_auth_code(self):
         """When prompted, you can manually login using this account:
 
@@ -918,7 +918,7 @@
         config["port"] = 8080
         self._test_acquire_token_by_auth_code(**config)
 
-    @unittest.skip(_ADFS_LABS_DECOMMISSIONED)
+    @unittest.skip(_ADFS_LABS_UNAVAILABLE)
     def test_adfs2019_onprem_acquire_token_by_auth_code_flow(self):
         config = self.get_lab_user(usertype="onprem", 
federationProvider="ADFSv2019")
         self._test_acquire_token_by_auth_code_flow(**dict(
@@ -928,7 +928,7 @@
             port=8080,
             ))
 
-    @unittest.skip(_ADFS_LABS_DECOMMISSIONED)
+    @unittest.skip(_ADFS_LABS_UNAVAILABLE)
     def test_adfs2019_onprem_acquire_token_interactive(self):
         config = self.get_lab_user(usertype="onprem", 
federationProvider="ADFSv2019")
         self._test_acquire_token_interactive(**dict(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/tests/test_mi.py 
new/msal-1.35.0b1/tests/test_mi.py
--- old/msal-1.34.0/tests/test_mi.py    2025-09-23 01:05:37.000000000 +0200
+++ new/msal-1.35.0b1/tests/test_mi.py  2026-01-07 00:51:42.000000000 +0100
@@ -190,8 +190,12 @@
             headers={'Metadata': 'true'},
             )
 
-    @patch("msal.managed_identity.socket.getfqdn", new=lambda: 
"MixedCaseHostName")
-    def test_happy_path_of_windows_vm(self):
+    @patch.object(ManagedIdentityClient, "_ManagedIdentityClient__instance", 
"MixedCaseHostName")
+    def test_happy_path_of_theoretical_mixed_case_hostname(self):
+        """Historically, we used to get the host name from socket.getfqdn(),
+        which could return a mixed-case host name on Windows.
+        Although we no longer use getfqdn(), we still keep this test case to 
ensure we tolerate it.
+        """
         self.test_happy_path_of_vm()
 
     @patch.dict(os.environ, {"AZURE_POD_IDENTITY_AUTHORITY_HOST": 
"http://localhost:1234//"})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.34.0/tests/test_optional_thumbprint.py 
new/msal-1.35.0b1/tests/test_optional_thumbprint.py
--- old/msal-1.34.0/tests/test_optional_thumbprint.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/msal-1.35.0b1/tests/test_optional_thumbprint.py 2026-01-07 
00:51:42.000000000 +0100
@@ -0,0 +1,215 @@
+import unittest
+from unittest.mock import Mock, patch
+from msal.application import ConfidentialClientApplication
+
+
+@patch('msal.application.Authority')
+@patch('msal.application.JwtAssertionCreator', new_callable=lambda: Mock(
+    
return_value=Mock(create_regenerative_assertion=Mock(return_value="mock_jwt_assertion"))))
+class TestClientCredentialWithOptionalThumbprint(unittest.TestCase):
+    """Test that thumbprint is optional when public_certificate is provided"""
+
+    # Sample test certificate and private key (PEM format)
+    # These are minimal valid PEM structures for testing
+    test_private_key = """-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKj
+MzEfYyjiWA4R4/M2bS1+fWIcPm15j7uo6xKvRr4PNx5bKMDFqMdW6/xfqFWX0nZK
+-----END PRIVATE KEY-----"""
+
+    test_certificate = """-----BEGIN CERTIFICATE-----
+MIIC5jCCAc6gAwIBAgIJALdYQVsVsNZHMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
+BAMMC0V4YW1wbGUgQ0EwHhcNMjQwMTAxMDAwMDAwWhcNMjUwMTAxMDAwMDAwWjAW
+-----END CERTIFICATE-----"""
+
+    def _setup_mocks(self, mock_authority_class, 
authority="https://login.microsoftonline.com/common";):
+        """Helper to setup Authority mock"""
+        # Setup Authority mock
+        mock_authority = Mock()
+        mock_authority.is_adfs = "adfs" in authority.lower()
+
+        # Extract instance from authority URL
+        if mock_authority.is_adfs:
+            # For ADFS: https://adfs.contoso.com/adfs -> adfs.contoso.com
+            mock_authority.instance = authority.split("//")[1].split("/")[0]
+            mock_authority.token_endpoint = 
f"https://{mock_authority.instance}/adfs/oauth2/token";
+            mock_authority.authorization_endpoint = 
f"https://{mock_authority.instance}/adfs/oauth2/authorize";
+        else:
+            # For AAD: https://login.microsoftonline.com/common -> 
login.microsoftonline.com
+            mock_authority.instance = authority.split("//")[1].split("/")[0]
+            mock_authority.token_endpoint = 
f"https://{mock_authority.instance}/common/oauth2/v2.0/token";
+            mock_authority.authorization_endpoint = 
f"https://{mock_authority.instance}/common/oauth2/v2.0/authorize";
+
+        mock_authority.device_authorization_endpoint = None
+        mock_authority_class.return_value = mock_authority
+
+        return mock_authority
+
+    def _setup_certificate_mocks(self, mock_extract, mock_load_cert):
+        """Helper to setup certificate parsing mocks"""
+        # Mock certificate loading
+        mock_cert = Mock()
+        mock_load_cert.return_value = mock_cert
+
+        # Mock _extract_cert_and_thumbprints to return thumbprints
+        mock_extract.return_value = (
+            "mock_sha256_thumbprint",  # sha256_thumbprint
+            "mock_sha1_thumbprint",     # sha1_thumbprint
+            ["mock_x5c_value"]          # x5c
+        )
+
+    def _verify_assertion_params(self, mock_jwt_creator_class, 
expected_algorithm,
+                                  expected_thumbprint_type, 
expected_thumbprint_value=None,
+                                  has_x5c=False):
+        """Helper to verify JwtAssertionCreator was called with correct 
params"""
+        mock_jwt_creator_class.assert_called_once()
+        call_args = mock_jwt_creator_class.call_args
+
+        # Verify algorithm
+        self.assertEqual(call_args[1]['algorithm'], expected_algorithm)
+
+        # Verify thumbprint type
+        if expected_thumbprint_type == 'sha256':
+            self.assertIn('sha256_thumbprint', call_args[1])
+            self.assertNotIn('sha1_thumbprint', call_args[1])
+        elif expected_thumbprint_type == 'sha1':
+            self.assertIn('sha1_thumbprint', call_args[1])
+            self.assertNotIn('sha256_thumbprint', call_args[1])
+            if expected_thumbprint_value:
+                self.assertEqual(call_args[1]['sha1_thumbprint'], 
expected_thumbprint_value)
+
+        # Verify x5c header if expected
+        if has_x5c:
+            self.assertIn('headers', call_args[1])
+            self.assertIn('x5c', call_args[1]['headers'])
+
+        return call_args
+
+    @patch('cryptography.x509.load_pem_x509_certificate')
+    @patch('msal.application._extract_cert_and_thumbprints')
+    def test_pem_with_certificate_only_uses_sha256(
+            self, mock_extract, mock_load_cert, mock_jwt_creator_class, 
mock_authority_class):
+        """Test that providing only public_certificate (no thumbprint) uses 
SHA-256"""
+        authority = "https://login.microsoftonline.com/common";
+        self._setup_mocks(mock_authority_class, authority)
+        self._setup_certificate_mocks(mock_extract, mock_load_cert)
+
+        # Create app with certificate credential WITHOUT thumbprint
+        app = ConfidentialClientApplication(
+            client_id="my_client_id",
+            client_credential={
+                "private_key": self.test_private_key,
+                "public_certificate": self.test_certificate,
+                # Note: NO thumbprint provided
+            },
+            authority=authority
+        )
+
+        # Verify SHA-256 with PS256 algorithm is used
+        self._verify_assertion_params(
+            mock_jwt_creator_class,
+            expected_algorithm='PS256',
+            expected_thumbprint_type='sha256',
+            has_x5c=True
+        )
+
+    def test_pem_with_manual_thumbprint_uses_sha1(
+            self, mock_jwt_creator_class, mock_authority_class):
+        """Test that providing manual thumbprint (no certificate) uses SHA-1"""
+        authority = "https://login.microsoftonline.com/common";
+        self._setup_mocks(mock_authority_class, authority)
+
+        # Create app with manual thumbprint (legacy approach)
+        manual_thumbprint = "A1B2C3D4E5F6"
+        app = ConfidentialClientApplication(
+            client_id="my_client_id",
+            client_credential={
+                "private_key": self.test_private_key,
+                "thumbprint": manual_thumbprint,
+                # Note: NO public_certificate provided
+            },
+            authority=authority
+        )
+
+        # Verify SHA-1 with RS256 algorithm is used
+        self._verify_assertion_params(
+            mock_jwt_creator_class,
+            expected_algorithm='RS256',
+            expected_thumbprint_type='sha1',
+            expected_thumbprint_value=manual_thumbprint
+        )
+
+    def test_pem_with_both_uses_manual_thumbprint_as_sha1(
+            self, mock_jwt_creator_class, mock_authority_class):
+        """Test that providing both thumbprint and certificate prefers manual 
thumbprint (SHA-1)"""
+        authority = "https://login.microsoftonline.com/common";
+        self._setup_mocks(mock_authority_class, authority)
+
+        # Create app with BOTH thumbprint and certificate
+        manual_thumbprint = "A1B2C3D4E5F6"
+        app = ConfidentialClientApplication(
+            client_id="my_client_id",
+            client_credential={
+                "private_key": self.test_private_key,
+                "thumbprint": manual_thumbprint,
+                "public_certificate": self.test_certificate,
+            },
+            authority=authority
+        )
+
+        # Verify manual thumbprint takes precedence (backward compatibility)
+        self._verify_assertion_params(
+            mock_jwt_creator_class,
+            expected_algorithm='RS256',
+            expected_thumbprint_type='sha1',
+            expected_thumbprint_value=manual_thumbprint,
+            has_x5c=True  # x5c should still be present
+        )
+
+    @patch('cryptography.x509.load_pem_x509_certificate')
+    @patch('msal.application._extract_cert_and_thumbprints')
+    def test_pem_with_adfs_uses_sha1(
+            self, mock_extract, mock_load_cert, mock_jwt_creator_class, 
mock_authority_class):
+        """Test that ADFS authority uses SHA-1 even with SHA-256 thumbprint"""
+        authority = "https://adfs.contoso.com/adfs";
+        self._setup_mocks(mock_authority_class, authority)
+        self._setup_certificate_mocks(mock_extract, mock_load_cert)
+
+        # Create app with certificate on ADFS
+        app = ConfidentialClientApplication(
+            client_id="my_client_id",
+            client_credential={
+                "private_key": self.test_private_key,
+                "public_certificate": self.test_certificate,
+            },
+            authority=authority
+        )
+
+        # ADFS should force SHA-1 with RS256 even though SHA-256 would be 
calculated
+        self._verify_assertion_params(
+            mock_jwt_creator_class,
+            expected_algorithm='RS256',
+            expected_thumbprint_type='sha1'
+        )
+
+    def test_pem_with_neither_raises_error(self, mock_jwt_creator_class, 
mock_authority_class):
+        """Test that providing neither thumbprint nor certificate raises 
ValueError"""
+        authority = "https://login.microsoftonline.com/common";
+        self._setup_mocks(mock_authority_class, authority)
+
+        # Should raise ValueError when neither thumbprint nor certificate 
provided
+        with self.assertRaises(ValueError) as context:
+            app = ConfidentialClientApplication(
+                client_id="my_client_id",
+                client_credential={
+                    "private_key": self.test_private_key,
+                    # Note: NO thumbprint and NO public_certificate
+                },
+                authority=authority
+            )
+
+        self.assertIn("thumbprint", str(context.exception).lower())
+        self.assertIn("public_certificate", str(context.exception).lower())
+
+
+if __name__ == "__main__":
+    unittest.main()

Reply via email to