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 2024-10-08 17:24:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-msal (Old) and /work/SRC/openSUSE:Factory/.python-msal.new.19354 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-msal" Tue Oct 8 17:24:47 2024 rev:24 rq:1206228 version:1.31.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-msal/python-msal.changes 2024-08-01 22:04:46.329314831 +0200 +++ /work/SRC/openSUSE:Factory/.python-msal.new.19354/python-msal.changes 2024-10-08 17:25:54.364778092 +0200 @@ -1,0 +2,11 @@ +Wed Oct 2 09:57:49 UTC 2024 - John Paul Adrian Glaubitz <adrian.glaubitz@suse.com> + +- Update to version 1.31.0 + * Integration with Broker-on-Mac in (#596) + * Change Managed Identity detection logic on Arc in (#731) + * Managed Identity supports CAE in (#730) + * Support Managed Identity on Azure Container + Instance (ACI) with Resource id in (#741) + * Other refactoring in (#740) + +------------------------------------------------------------------- Old: ---- msal-1.30.0.tar.gz New: ---- msal-1.31.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-msal.spec ++++++ --- /var/tmp/diff_new_pack.jfPE7h/_old 2024-10-08 17:25:54.788795788 +0200 +++ /var/tmp/diff_new_pack.jfPE7h/_new 2024-10-08 17:25:54.788795788 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-msal -Version: 1.30.0 +Version: 1.31.0 Release: 0 Summary: Microsoft Authentication Library (MSAL) for Python License: MIT ++++++ msal-1.30.0.tar.gz -> msal-1.31.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/PKG-INFO new/msal-1.31.0/PKG-INFO --- old/msal-1.30.0/PKG-INFO 2024-07-17 06:01:39.363946200 +0200 +++ new/msal-1.31.0/PKG-INFO 2024-09-07 00:20:40.805118800 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: msal -Version: 1.30.0 +Version: 1.31.0 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 @@ -27,9 +27,10 @@ License-File: LICENSE Requires-Dist: requests<3,>=2.0.0 Requires-Dist: PyJWT[crypto]<3,>=1.0.0 -Requires-Dist: cryptography<45,>=2.5 +Requires-Dist: cryptography<46,>=2.5 Provides-Extra: broker -Requires-Dist: pymsalruntime<0.17,>=0.13.2; (python_version >= "3.6" and platform_system == "Windows") and extra == "broker" +Requires-Dist: pymsalruntime<0.18,>=0.14; (python_version >= "3.6" and platform_system == "Windows") and extra == "broker" +Requires-Dist: pymsalruntime<0.18,>=0.17; (python_version >= "3.8" and platform_system == "Darwin") and extra == "broker" # Microsoft Authentication Library (MSAL) for Python diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/msal/__main__.py new/msal-1.31.0/msal/__main__.py --- old/msal-1.30.0/msal/__main__.py 2024-07-17 06:01:34.000000000 +0200 +++ new/msal-1.31.0/msal/__main__.py 2024-09-07 00:20:35.000000000 +0200 @@ -299,6 +299,7 @@ authority=authority, instance_discovery=instance_discovery, enable_broker_on_windows=enable_broker, + enable_broker_on_mac=enable_broker, enable_pii_log=enable_pii_log, token_cache=global_cache, ) if not is_cca else msal.ConfidentialClientApplication( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/msal/application.py new/msal-1.31.0/msal/application.py --- old/msal-1.30.0/msal/application.py 2024-07-17 06:01:34.000000000 +0200 +++ new/msal-1.31.0/msal/application.py 2024-09-07 00:20:35.000000000 +0200 @@ -21,11 +21,16 @@ # The __init__.py will import this. Not the other way around. -__version__ = "1.30.0" # When releasing, also check and bump our dependencies's versions if needed +__version__ = "1.31.0" # When releasing, also check and bump our dependencies's versions if needed logger = logging.getLogger(__name__) _AUTHORITY_TYPE_CLOUDSHELL = "CLOUDSHELL" +def _init_broker(enable_pii_log): # Make it a function to allow mocking + from . import broker # Trigger Broker's initialization, lazily + if enable_pii_log: + broker._enable_pii_log() + def extract_certs(public_cert_content): # Parses raw public certificate file contents and returns a list of strings # Usage: headers = {"x5c": extract_certs(open("my_cert.pem").read())} @@ -411,9 +416,11 @@ (STS) what this client is capable for, so STS can decide to turn on certain features. For example, if client is capable to handle *claims challenge*, - STS can then issue CAE access tokens to resources - knowing when the resource emits *claims challenge* - the client will be capable to handle. + STS may issue + `Continuous Access Evaluation (CAE) <https://learn.microsoft.com/entra/identity/conditional-access/concept-continuous-access-evaluation>`_ + access tokens to resources, + knowing that when the resource emits a *claims challenge* + the client will be able to handle those challenges. Implementation details: Client capability is implemented using "claims" parameter on the wire, @@ -638,20 +645,28 @@ if allow_broker: warnings.warn( "allow_broker is deprecated. " - "Please use PublicClientApplication(..., enable_broker_on_windows=True)", + "Please use PublicClientApplication(..., " + "enable_broker_on_windows=True, " + "enable_broker_on_mac=...)", DeprecationWarning) - self._enable_broker = self._enable_broker or ( + opted_in_for_broker = ( + self._enable_broker # True means Opted-in from PCA + or ( # When we started the broker project on Windows platform, # the allow_broker was meant to be cross-platform. Now we realize # that other platforms have different redirect_uri requirements, # so the old allow_broker is deprecated and will only for Windows. allow_broker and sys.platform == "win32") - if (self._enable_broker and not is_confidential_app - and not self.authority.is_adfs and not self.authority._is_b2c): + ) + self._enable_broker = ( # This same variable will also store the state + opted_in_for_broker + and not is_confidential_app + and not self.authority.is_adfs + and not self.authority._is_b2c + ) + if self._enable_broker: try: - from . import broker # Trigger Broker's initialization - if enable_pii_log: - broker._enable_pii_log() + _init_broker(enable_pii_log) except RuntimeError: self._enable_broker = False logger.exception( @@ -1879,7 +1894,7 @@ .. note:: - You may set enable_broker_on_windows to True. + You may set enable_broker_on_windows and/or enable_broker_on_mac to True. **What is a broker, and why use it?** @@ -1905,9 +1920,11 @@ * ``ms-appx-web://Microsoft.AAD.BrokerPlugin/your_client_id`` if your app is expected to run on Windows 10+ + * ``msauth.com.msauth.unsignedapp://auth`` + if your app is expected to run on Mac 2. installed broker dependency, - e.g. ``pip install msal[broker]>=1.25,<2``. + e.g. ``pip install msal[broker]>=1.31,<2``. 3. tested with ``acquire_token_interactive()`` and ``acquire_token_silent()``. @@ -1939,12 +1956,21 @@ This parameter defaults to None, which means MSAL will not utilize a broker. New in MSAL Python 1.25.0. + + :param boolean enable_broker_on_mac: + This setting is only effective if your app is running on Mac. + This parameter defaults to None, which means MSAL will not utilize a broker. + + New in MSAL Python 1.31.0. """ if client_credential is not None: raise ValueError("Public Client should not possess credentials") # Using kwargs notation for now. We will switch to keyword-only arguments. enable_broker_on_windows = kwargs.pop("enable_broker_on_windows", False) - self._enable_broker = enable_broker_on_windows and sys.platform == "win32" + enable_broker_on_mac = kwargs.pop("enable_broker_on_mac", False) + self._enable_broker = bool( + enable_broker_on_windows and sys.platform == "win32" + or enable_broker_on_mac and sys.platform == "darwin") super(PublicClientApplication, self).__init__( client_id, client_credential=None, **kwargs) @@ -2022,14 +2048,22 @@ New in version 1.15. :param int parent_window_handle: - Required if your app is running on Windows and opted in to use broker. + OPTIONAL. + + * If your app does not opt in to use broker, + you do not need to provide a ``parent_window_handle`` here. + + * If your app opts in to use broker, + ``parent_window_handle`` is required. - If your app is a GUI app, - you are recommended to also provide its window handle, - so that the sign in UI window will properly pop up on top of your window. + - If your app is a GUI app running on Windows or Mac system, + you are required to also provide its window handle, + so that the sign-in window will pop up on top of your window. + - If your app is a console app running on Windows or Mac system, + you can use a placeholder + ``PublicClientApplication.CONSOLE_WINDOW_HANDLE``. - If your app is a console app (most Python scripts are console apps), - you can use a placeholder value ``msal.PublicClientApplication.CONSOLE_WINDOW_HANDLE``. + Most Python scripts are console apps. New in version 1.20.0. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/msal/broker.py new/msal-1.31.0/msal/broker.py --- old/msal-1.30.0/msal/broker.py 2024-07-17 06:01:34.000000000 +0200 +++ new/msal-1.31.0/msal/broker.py 2024-09-07 00:20:35.000000000 +0200 @@ -1,9 +1,9 @@ """This module is an adaptor to the underlying broker. It relies on PyMsalRuntime which is the package providing broker's functionality. """ -from threading import Event import json import logging +import sys import time import uuid @@ -35,14 +35,12 @@ pass -class _CallbackData: - def __init__(self): - self.signal = Event() - self.result = None - - def complete(self, result): - self.signal.set() - self.result = result +_redirect_uri_on_mac = "msauth.com.msauth.unsignedapp://auth" # Note: + # On Mac, the native Python has a team_id which links to bundle id + # com.apple.python3 however it won't give Python scripts better security. + # Besides, the homebrew-installed Pythons have no team_id + # so they have to use a generic placeholder anyway. + # The v-team chose to combine two situations into using same placeholder. def _convert_error(error, client_id): @@ -52,8 +50,9 @@ or "AADSTS7000218" in context # This "request body must contain ... client_secret" is just a symptom of current app has no WAM redirect_uri ): raise RedirectUriError( # This would be seen by either the app developer or end user - "MsalRuntime won't work unless this one more redirect_uri is registered to current app: " - "ms-appx-web://Microsoft.AAD.BrokerPlugin/{}".format(client_id)) + "MsalRuntime needs the current app to register these redirect_uri " + "(1) ms-appx-web://Microsoft.AAD.BrokerPlugin/{} (2) {}".format( + client_id, _redirect_uri_on_mac)) # OTOH, AAD would emit other errors when other error handling branch was hit first, # so, the AADSTS50011/RedirectUriError is not guaranteed to happen. return { @@ -70,8 +69,8 @@ def _read_account_by_id(account_id, correlation_id): - """Return an instance of MSALRuntimeAccount, or log error and return None""" - callback_data = _CallbackData() + """Return an instance of MSALRuntimeError or MSALRuntimeAccount, or None""" + callback_data = pymsalruntime.CallbackData() pymsalruntime.read_account_by_id( account_id, correlation_id, @@ -142,7 +141,7 @@ params.set_pop_params( auth_scheme._http_method, auth_scheme._url.netloc, auth_scheme._url.path, auth_scheme._nonce) - callback_data = _CallbackData() + callback_data = pymsalruntime.CallbackData() for k, v in kwargs.items(): # This can be used to support domain_hint, max_age, etc. if v is not None: params.set_additional_parameter(k, str(v)) @@ -169,9 +168,12 @@ **kwargs): params = pymsalruntime.MSALRuntimeAuthParameters(client_id, authority) params.set_requested_scopes(scopes) - params.set_redirect_uri("https://login.microsoftonline.com/common/oauth2/nativeclient") - # This default redirect_uri value is not currently used by the broker + params.set_redirect_uri( + _redirect_uri_on_mac if sys.platform == "darwin" else + "https://login.microsoftonline.com/common/oauth2/nativeclient" + # This default redirect_uri value is not currently used by WAM # but it is required by the MSAL.cpp to be set to a non-empty valid URI. + ) if prompt: if prompt == "select_account": if login_hint: @@ -198,7 +200,7 @@ params.set_additional_parameter(k, str(v)) if claims: params.set_decoded_claims(claims) - callback_data = _CallbackData() + callback_data = pymsalruntime.CallbackData(is_interactive=True) pymsalruntime.signin_interactively( parent_window_handle or pymsalruntime.get_console_window() or pymsalruntime.get_desktop_window(), # Since pymsalruntime 0.2+ params, @@ -231,7 +233,7 @@ for k, v in kwargs.items(): # This can be used to support domain_hint, max_age, etc. if v is not None: params.set_additional_parameter(k, str(v)) - callback_data = _CallbackData() + callback_data = pymsalruntime.CallbackData() pymsalruntime.acquire_token_silently( params, correlation_id, @@ -247,7 +249,7 @@ account = _read_account_by_id(account_id, correlation_id) if account is None: return - callback_data = _CallbackData() + callback_data = pymsalruntime.CallbackData() pymsalruntime.signout_silently( # New in PyMsalRuntime 0.7 client_id, correlation_id, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/msal/managed_identity.py new/msal-1.31.0/msal/managed_identity.py --- old/msal-1.30.0/msal/managed_identity.py 2024-07-17 06:01:34.000000000 +0200 +++ new/msal-1.31.0/msal/managed_identity.py 2024-09-07 00:20:35.000000000 +0200 @@ -10,7 +10,7 @@ import time from urllib.parse import urlparse # Python 3+ from collections import UserDict # Python 3+ -from typing import Union # Needed in Python 3.7 & 3.8 +from typing import Optional, Union # Needed in Python 3.7 & 3.8 from .token_cache import TokenCache from .individual_cache import _IndividualCache as IndividualCache from .throttled_http_client import ThrottledHttpClientBase, RetryAfterParser @@ -40,14 +40,15 @@ _types_mapping = { # Maps type name in configuration to type name on wire CLIENT_ID: "client_id", - RESOURCE_ID: "mi_res_id", + RESOURCE_ID: "msi_res_id", # VM's IMDS prefers msi_res_id https://github.com/Azure/azure-rest-api-specs/blob/dba6ed1f03bda88ac6884c0a8... OBJECT_ID: "object_id", } @classmethod def is_managed_identity(cls, unknown): - return isinstance(unknown, ManagedIdentity) or ( - isinstance(unknown, dict) and cls.ID_TYPE in unknown) + return (isinstance(unknown, ManagedIdentity) + or cls.is_system_assigned(unknown) + or cls.is_user_assigned(unknown)) @classmethod def is_system_assigned(cls, unknown): @@ -144,7 +145,10 @@ (like what a ``PublicClientApplication`` does), not a token with application permissions for an app. """ - _instance, _tenant = socket.getfqdn(), "managed_identity" # Placeholders + __instance, _tenant = None, "managed_identity" # Placeholders + _TOKEN_SOURCE = "token_source" + _TOKEN_SOURCE_IDP = "identity_provider" + _TOKEN_SOURCE_CACHE = "cache" def __init__( self, @@ -214,6 +218,9 @@ ) token = client.acquire_token_for_client("resource") """ + if not ManagedIdentity.is_managed_identity(managed_identity): + raise ManagedIdentityError( + f"Incorrect managed_identity: {managed_identity}") self._managed_identity = managed_identity self._http_client = _ThrottledHttpClient( # This class only throttles excess token acquisition requests. @@ -232,12 +239,36 @@ ) self._token_cache = token_cache or TokenCache() - def acquire_token_for_client(self, *, resource): # We may support scope in the future + 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, + *, + resource: str, # If/when we support scope, resource will become optional + claims_challenge: Optional[str] = None, + ): """Acquire token for the managed identity. The result will be automatically cached. Subsequent calls will automatically search from cache first. + :param resource: The resource for which the token is acquired. + + :param claims_challenge: + Optional. + It is a string representation of a JSON object + (which contains lists of claims being requested). + + The tenant admin may choose to revoke all Managed Identity tokens, + and then a *claims challenge* will be returned by the target resource, + as a `claims_challenge` directive in the `www-authenticate` header, + even if the app developer did not opt in for the "CP1" client capability. + Upon receiving a `claims_challenge`, MSAL will skip a token cache read, + and will attempt to acquire a new token. + .. note:: Known issue: When an Azure VM has only one user-assigned managed identity, @@ -250,19 +281,18 @@ access_token_from_cache = None client_id_in_cache = self._managed_identity.get( ManagedIdentity.ID, "SYSTEM_ASSIGNED_MANAGED_IDENTITY") - if True: # Does not offer an "if not force_refresh" option, because - # there would be built-in token cache in the service side anyway + now = time.time() + if not claims_challenge: # Then attempt token cache search matches = self._token_cache.find( self._token_cache.CredentialType.ACCESS_TOKEN, target=[resource], query=dict( client_id=client_id_in_cache, - environment=self._instance, + environment=self._get_instance(), realm=self._tenant, home_account_id=None, ), ) - now = time.time() for entry in matches: expires_in = int(entry["expires_on"]) - now if expires_in < 5*60: # Then consider it expired @@ -272,6 +302,7 @@ "access_token": entry["secret"], "token_type": entry.get("token_type", "Bearer"), "expires_in": int(expires_in), # OAuth2 specs defines it as int + self._TOKEN_SOURCE: self._TOKEN_SOURCE_CACHE, } if "refresh_on" in entry: access_token_from_cache["refresh_on"] = int(entry["refresh_on"]) @@ -287,13 +318,15 @@ self._token_cache.add(dict( client_id=client_id_in_cache, scope=[resource], - token_endpoint="https://{}/{}".format(self._instance, self._tenant), + token_endpoint="https://{}/{}".format( + self._get_instance(), self._tenant), response=result, params={}, data={}, )) if "refresh_in" in result: result["refresh_on"] = int(now + result["refresh_in"]) + result[self._TOKEN_SOURCE] = self._TOKEN_SOURCE_IDP if (result and "error" not in result) or (not access_token_from_cache): return result except: # The exact HTTP exception is transportation-layer dependent @@ -310,6 +343,17 @@ return scope # There is no much else we can do here +def _get_arc_endpoint(): + if "IDENTITY_ENDPOINT" in os.environ and "IMDS_ENDPOINT" in os.environ: + return os.environ["IDENTITY_ENDPOINT"] + if ( # Defined in https://msazure.visualstudio.com/One/_wiki/wikis/One.wiki/233012/VM-Extensio... + sys.platform == "linux" and os.path.exists("/var/opt/azcmagent/bin/himds") + or sys.platform == "win32" and os.path.exists(os.path.expandvars( + r"%ProgramFiles%\AzureConnectedMachineAgent\himds.exe")) + ): + return "http://localhost:40342/metadata/identity/oauth2/token" + + APP_SERVICE = object() AZURE_ARC = object() CLOUD_SHELL = object() # In MSAL Python, token acquisition was done by @@ -332,7 +376,7 @@ return APP_SERVICE if "MSI_ENDPOINT" in os.environ and "MSI_SECRET" in os.environ: return MACHINE_LEARNING - if "IDENTITY_ENDPOINT" in os.environ and "IMDS_ENDPOINT" in os.environ: + if _get_arc_endpoint(): return AZURE_ARC if _is_running_in_cloud_shell(): return CLOUD_SHELL @@ -374,24 +418,21 @@ managed_identity, resource, ) - if "IDENTITY_ENDPOINT" in os.environ and "IMDS_ENDPOINT" in os.environ: + arc_endpoint = _get_arc_endpoint() + if arc_endpoint: if ManagedIdentity.is_user_assigned(managed_identity): raise ManagedIdentityError( # Note: Azure Identity for Python raised exception too "Invalid managed_identity parameter. " "Azure Arc supports only system-assigned managed identity, " "See also " "https://learn.microsoft.com/en-us/azure/service-fabric/configure-existing-cl...") - return _obtain_token_on_arc( - http_client, - os.environ["IDENTITY_ENDPOINT"], - resource, - ) + return _obtain_token_on_arc(http_client, arc_endpoint, resource) return _obtain_token_on_azure_vm(http_client, managed_identity, resource) -def _adjust_param(params, managed_identity): +def _adjust_param(params, managed_identity, types_mapping=None): # Modify the params dict in place - id_name = ManagedIdentity._types_mapping.get( + id_name = (types_mapping or ManagedIdentity._types_mapping).get( managed_identity.get(ManagedIdentity.ID_TYPE)) if id_name: params[id_name] = managed_identity[ManagedIdentity.ID] @@ -418,7 +459,7 @@ "resource": payload.get("resource"), "token_type": payload.get("token_type", "Bearer"), } - return payload # Typically an error, but it is undefined in the doc above + return payload # It would be {"error": ..., "error_description": ...} according to https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-re... except json.decoder.JSONDecodeError: logger.debug("IMDS emits unexpected payload: %s", resp.text) raise @@ -438,7 +479,12 @@ "api-version": "2019-08-01", "resource": resource, } - _adjust_param(params, managed_identity) + _adjust_param(params, managed_identity, types_mapping={ + ManagedIdentity.CLIENT_ID: "client_id", + ManagedIdentity.RESOURCE_ID: "mi_res_id", # App Service's resource id uses "mi_res_id" + ManagedIdentity.OBJECT_ID: "object_id", + }) + resp = http_client.get( endpoint, params=params, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/msal/token_cache.py new/msal-1.31.0/msal/token_cache.py --- old/msal-1.30.0/msal/token_cache.py 2024-07-17 06:01:34.000000000 +0200 +++ new/msal-1.31.0/msal/token_cache.py 2024-09-07 00:20:35.000000000 +0200 @@ -363,11 +363,14 @@ This class does NOT actually persist the cache on disk/db/etc.. Depending on your need, - the following simple recipe for file-based persistence may be sufficient:: + the following simple recipe for file-based, unencrypted persistence may be sufficient:: import os, atexit, msal cache_filename = os.path.join( # Persist cache into this file - os.getenv("XDG_RUNTIME_DIR", ""), # Automatically wipe out the cache from Linux when user's ssh session ends. See also https://github.com/AzureAD/microsoft-authentication-library-for-python/issue... + os.getenv( + # Automatically wipe out the cache from Linux when user's ssh session ends. + # See also https://github.com/AzureAD/microsoft-authentication-library-for-python/issue... + "XDG_RUNTIME_DIR", ""), "my_cache.bin") cache = msal.SerializableTokenCache() if os.path.exists(cache_filename): @@ -380,6 +383,10 @@ app = msal.ClientApplication(..., token_cache=cache) ... + Alternatively, you may use a more sophisticated cache persistence library, + `MSAL Extensions <https://github.com/AzureAD/microsoft-authentication-extensions-for-python>`_, + which provides token cache persistence with encryption, and more. + :var bool has_state_changed: Indicates whether the cache state in the memory has changed since last :func:`~serialize` or :func:`~deserialize` call. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/msal.egg-info/PKG-INFO new/msal-1.31.0/msal.egg-info/PKG-INFO --- old/msal-1.30.0/msal.egg-info/PKG-INFO 2024-07-17 06:01:39.000000000 +0200 +++ new/msal-1.31.0/msal.egg-info/PKG-INFO 2024-09-07 00:20:40.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: msal -Version: 1.30.0 +Version: 1.31.0 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 @@ -27,9 +27,10 @@ License-File: LICENSE Requires-Dist: requests<3,>=2.0.0 Requires-Dist: PyJWT[crypto]<3,>=1.0.0 -Requires-Dist: cryptography<45,>=2.5 +Requires-Dist: cryptography<46,>=2.5 Provides-Extra: broker -Requires-Dist: pymsalruntime<0.17,>=0.13.2; (python_version >= "3.6" and platform_system == "Windows") and extra == "broker" +Requires-Dist: pymsalruntime<0.18,>=0.14; (python_version >= "3.6" and platform_system == "Windows") and extra == "broker" +Requires-Dist: pymsalruntime<0.18,>=0.17; (python_version >= "3.8" and platform_system == "Darwin") and extra == "broker" # Microsoft Authentication Library (MSAL) for Python diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/msal.egg-info/requires.txt new/msal-1.31.0/msal.egg-info/requires.txt --- old/msal-1.30.0/msal.egg-info/requires.txt 2024-07-17 06:01:39.000000000 +0200 +++ new/msal-1.31.0/msal.egg-info/requires.txt 2024-09-07 00:20:40.000000000 +0200 @@ -1,8 +1,11 @@ requests<3,>=2.0.0 PyJWT[crypto]<3,>=1.0.0 -cryptography<45,>=2.5 +cryptography<46,>=2.5 [broker] [broker:python_version >= "3.6" and platform_system == "Windows"] -pymsalruntime<0.17,>=0.13.2 +pymsalruntime<0.18,>=0.14 + +[broker:python_version >= "3.8" and platform_system == "Darwin"] +pymsalruntime<0.18,>=0.17 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/setup.cfg new/msal-1.31.0/setup.cfg --- old/msal-1.30.0/setup.cfg 2024-07-17 06:01:39.363946200 +0200 +++ new/msal-1.31.0/setup.cfg 2024-09-07 00:20:40.805118800 +0200 @@ -39,11 +39,12 @@ PyJWT[crypto]>=1.0.0,<3 - cryptography>=2.5,<45 + cryptography>=2.5,<46 [options.extras_require] broker = - pymsalruntime>=0.13.2,<0.17; python_version>='3.6' and platform_system=='Windows' + pymsalruntime>=0.14,<0.18; python_version>='3.6' and platform_system=='Windows' + pymsalruntime>=0.17,<0.18; python_version>='3.8' and platform_system=='Darwin' [options.packages.find] exclude = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/tests/test_application.py new/msal-1.31.0/tests/test_application.py --- old/msal-1.30.0/tests/test_application.py 2024-07-17 06:01:34.000000000 +0200 +++ new/msal-1.31.0/tests/test_application.py 2024-09-07 00:20:35.000000000 +0200 @@ -1,11 +1,16 @@ # Note: Since Aug 2019 we move all e2e tests into test_e2e.py, # so this test_application file contains only unit tests without dependency. +import json +import logging import sys import time -from msal.application import * -from msal.application import _str2bytes +from unittest.mock import patch, Mock import msal -from msal.application import _merge_claims_challenge_and_capabilities +from msal.application import ( + extract_certs, + ClientApplication, PublicClientApplication, ConfidentialClientApplication, + _str2bytes, _merge_claims_challenge_and_capabilities, +) from tests import unittest from tests.test_token_cache import build_id_token, build_response from tests.http_client import MinimalHttpClient, MinimalResponse @@ -722,3 +727,63 @@ self._test_client_id_should_be_a_valid_scope("client_id", []) self._test_client_id_should_be_a_valid_scope("client_id", ["foo"]) + +@patch("sys.platform", new="darwin") # Pretend running on Mac. +@patch("msal.authority.tenant_discovery", new=Mock(return_value={ + "authorization_endpoint": "https://contoso.com/placeholder", + "token_endpoint": "https://contoso.com/placeholder", + })) +@patch("msal.application._init_broker", new=Mock()) # Allow testing without pymsalruntime +class TestBrokerFallback(unittest.TestCase): + + def test_broker_should_be_disabled_by_default(self): + app = msal.PublicClientApplication( + "client_id", + authority="https://login.microsoftonline.com/common", + ) + self.assertFalse(app._enable_broker) + + def test_broker_should_be_enabled_when_opted_in(self): + app = msal.PublicClientApplication( + "client_id", + authority="https://login.microsoftonline.com/common", + enable_broker_on_mac=True, + ) + self.assertTrue(app._enable_broker) + + def test_should_fallback_to_non_broker_when_using_adfs(self): + app = msal.PublicClientApplication( + "client_id", + authority="https://contoso.com/adfs", + #instance_discovery=False, # Automatically skipped when detected ADFS + enable_broker_on_mac=True, + ) + self.assertFalse(app._enable_broker) + + def test_should_fallback_to_non_broker_when_using_b2c(self): + app = msal.PublicClientApplication( + "client_id", + authority="https://contoso.b2clogin.com/contoso/policy", + #instance_discovery=False, # Automatically skipped when detected B2C + enable_broker_on_mac=True, + ) + self.assertFalse(app._enable_broker) + + def test_should_use_broker_when_disabling_instance_discovery(self): + app = msal.PublicClientApplication( + "client_id", + authority="https://contoso.com/path", + instance_discovery=False, # Need this for a generic authority url + enable_broker_on_mac=True, + ) + # TODO: Shall we bypass broker when opted out of instance discovery? + self.assertTrue(app._enable_broker) # Current implementation enables broker + + def test_should_fallback_to_non_broker_when_using_oidc_authority(self): + app = msal.PublicClientApplication( + "client_id", + oidc_authority="https://contoso.com/path", + enable_broker_on_mac=True, + ) + self.assertFalse(app._enable_broker) + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/tests/test_e2e.py new/msal-1.31.0/tests/test_e2e.py --- old/msal-1.30.0/tests/test_e2e.py 2024-07-17 06:01:34.000000000 +0200 +++ new/msal-1.31.0/tests/test_e2e.py 2024-09-07 00:20:35.000000000 +0200 @@ -16,6 +16,7 @@ import json import time import unittest +from urllib.parse import urlparse, parse_qs import sys try: from unittest.mock import patch, ANY @@ -171,6 +172,7 @@ client_id, client_credential=None, authority="https://login.microsoftonline.com/common", + oidc_authority=None, scopes=["https://graph.microsoft.com/.default"], # Microsoft Graph http_client=None, azure_region=None, @@ -180,6 +182,7 @@ client_id, client_credential=client_credential, authority=authority, + oidc_authority=oidc_authority, azure_region=azure_region, http_client=http_client or MinimalHttpClient(), ) @@ -193,20 +196,24 @@ return msal.PublicClientApplication( client_id, authority=authority, + oidc_authority=oidc_authority, http_client=http_client or MinimalHttpClient(), enable_broker_on_windows=broker_available, + enable_broker_on_mac=broker_available, ) def _test_username_password(self, authority=None, client_id=None, username=None, password=None, scope=None, + oidc_authority=None, client_secret=None, # Since MSAL 1.11, confidential client has ROPC too azure_region=None, http_client=None, auth_scheme=None, **ignored): - assert authority and client_id and username and password and scope + assert client_id and username and password and scope and ( + authority or oidc_authority) self.app = self._build_app( - client_id, authority=authority, + client_id, authority=authority, oidc_authority=oidc_authority, http_client=http_client, azure_region=azure_region, # Regional endpoint does not support ROPC. # Here we just use it to test a regional app won't break ROPC. @@ -227,9 +234,14 @@ os.getenv("TRAVIS"), # It is set when running on TravisCI or Github Actions "Although it is doable, we still choose to skip device flow to save time") def _test_device_flow( - self, client_id=None, authority=None, scope=None, **ignored): - assert client_id and authority and scope - self.app = self._build_app(client_id, authority=authority) + self, + *, + client_id=None, authority=None, oidc_authority=None, scope=None, + **ignored + ): + assert client_id and scope and (authority or oidc_authority) + self.app = self._build_app( + client_id, authority=authority, oidc_authority=oidc_authority) flow = self.app.initiate_device_flow(scopes=scope) assert "user_code" in flow, "DF does not seem to be provisioned: %s".format( json.dumps(flow, indent=4)) @@ -253,7 +265,8 @@ @unittest.skipIf(os.getenv("TRAVIS"), "Browser automation is not yet implemented") def _test_acquire_token_interactive( - self, client_id=None, authority=None, scope=None, port=None, + self, *, client_id=None, authority=None, scope=None, port=None, + oidc_authority=None, username=None, lab_name=None, username_uri="", # Unnecessary if you provided username and lab_name data=None, # Needed by ssh-cert feature @@ -261,8 +274,9 @@ enable_msa_passthrough=None, auth_scheme=None, **ignored): - assert client_id and authority and scope - self.app = self._build_app(client_id, authority=authority) + assert client_id and scope and (authority or oidc_authority) + self.app = self._build_app( + client_id, authority=authority, oidc_authority=oidc_authority) logger.info(_get_hint( # Useful when testing broker which shows no welcome_template username=username, lab_name=lab_name, username_uri=username_uri)) result = self.app.acquire_token_interactive( @@ -680,10 +694,13 @@ def _test_acquire_token_by_client_secret( self, client_id=None, client_secret=None, authority=None, scope=None, + oidc_authority=None, **ignored): - assert client_id and client_secret and authority and scope + assert client_id and client_secret and scope and ( + authority or oidc_authority) self.app = msal.ConfidentialClientApplication( client_id, client_credential=client_secret, authority=authority, + oidc_authority=oidc_authority, http_client=MinimalHttpClient()) result = self.app.acquire_token_for_client(scope) self.assertIsNotNone(result.get("access_token"), "Got %s instead" % result) @@ -1004,14 +1021,18 @@ @classmethod def setUpClass(cls): super(CiamTestCase, cls).setUpClass() - cls.user = cls.get_lab_user(federationProvider="ciam") + cls.user = cls.get_lab_user( + #federationProvider="ciam", # This line would return ciam2 tenant + federationProvider="ciamcud", signinAudience="AzureAdMyOrg", # ciam6 + ) # FYI: Only single- or multi-tenant CIAM app can have other-than-OIDC # delegated permissions on Microsoft Graph. cls.app_config = cls.get_lab_app_object(cls.user["client_id"]) def test_ciam_acquire_token_interactive(self): self._test_acquire_token_interactive( - authority=self.app_config["authority"], + authority=self.app_config.get("authority"), + oidc_authority=self.app_config.get("oidc_authority"), client_id=self.app_config["appId"], scope=self.app_config["scopes"], username=self.user["username"], @@ -1019,13 +1040,18 @@ ) def test_ciam_acquire_token_for_client(self): + raw_url = self.app_config["clientSecret"] + secret_url = urlparse(raw_url) + if secret_url.query: # Ciam2 era has a query param Secret=name + secret_name = parse_qs(secret_url.query)["Secret"][0] + else: # Ciam6 era has a URL path that ends with the secret name + secret_name = secret_url.path.split("/")[-1] + logger.info('Detected secret name "%s" from "%s"', secret_name, raw_url) self._test_acquire_token_by_client_secret( client_id=self.app_config["appId"], - client_secret=self.get_lab_user_secret( - self.app_config["clientSecret"].split("=")[-1]), - authority=self.app_config["authority"], - #scope=["{}/.default".format(self.app_config["appId"])], # AADSTS500207: The account type can't be used for the resource you're trying to access. - #scope=["api://{}/.default".format(self.app_config["appId"])], # AADSTS500011: The resource principal named api://ced781e7-bdb0-4c99-855c-d3bacddea88a was not found in the tenant named MSIDLABCIAM2. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant. + client_secret=self.get_lab_user_secret(secret_name), + authority=self.app_config.get("authority"), + oidc_authority=self.app_config.get("oidc_authority"), scope=self.app_config["scopes"], # It shall ends with "/.default" ) @@ -1038,21 +1064,35 @@ # and enabling "Allow public client flows". # Otherwise it would hit AADSTS7000218. self._test_username_password( - authority=self.app_config["authority"], + authority=self.app_config.get("authority"), + oidc_authority=self.app_config.get("oidc_authority"), client_id=self.app_config["appId"], username=self.user["username"], password=self.get_lab_user_secret(self.user["lab_name"]), scope=self.app_config["scopes"], ) + @unittest.skip("""As of Aug 2024, in both ciam2 and ciam6, sign-in fails with +AADSTS500208: The domain is not a valid login domain for the account type.""") def test_ciam_device_flow(self): self._test_device_flow( - authority=self.app_config["authority"], + authority=self.app_config.get("authority"), + oidc_authority=self.app_config.get("oidc_authority"), client_id=self.app_config["appId"], scope=self.app_config["scopes"], ) +class CiamCudTestCase(CiamTestCase): + @classmethod + def setUpClass(cls): + super(CiamCudTestCase, cls).setUpClass() + cls.app_config["authority"] = None + cls.app_config["oidc_authority"] = ( + # Derived from https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/... + "https://login.msidlabsciam.com/fe362aec-5d43-45d1-b730-9755e60dc3b9/v2.0") + + class WorldWideRegionalEndpointTestCase(LabBasedTestCase): region = "westus" timeout = 2 # Short timeout makes this test case responsive on non-VM diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msal-1.30.0/tests/test_mi.py new/msal-1.31.0/tests/test_mi.py --- old/msal-1.30.0/tests/test_mi.py 2024-07-17 06:01:34.000000000 +0200 +++ new/msal-1.31.0/tests/test_mi.py 2024-09-07 00:20:35.000000000 +0200 @@ -61,6 +61,14 @@ http_client=requests.Session(), ) + def test_error_out_on_invalid_input(self): + with self.assertRaises(ManagedIdentityError): + ManagedIdentityClient({"foo": "bar"}, http_client=requests.Session()) + with self.assertRaises(ManagedIdentityError): + ManagedIdentityClient( + {"ManagedIdentityIdType": "undefined", "Id": "foo"}, + http_client=requests.Session()) + def assertCacheStatus(self, app): cache = app._token_cache._cache self.assertEqual(1, len(cache.get("AccessToken", [])), "Should have 1 AT") @@ -82,20 +90,17 @@ self.assertTrue( is_subdict_of(expected_result, result), # We will test refresh_on later "Should obtain a token response") + self.assertTrue(result["token_source"], "identity_provider") self.assertEqual(expires_in, result["expires_in"], "Should have expected expires_in") if expires_in >= 7200: expected_refresh_on = int(time.time() + expires_in / 2) self.assertTrue( expected_refresh_on - 1 <= result["refresh_on"] <= expected_refresh_on + 1, "Should have a refresh_on time around the middle of the token's life") - self.assertEqual( - result["access_token"], - app.acquire_token_for_client(resource=resource).get("access_token"), - "Should hit the same token from cache") - - self.assertCacheStatus(app) result = app.acquire_token_for_client(resource=resource) + self.assertCacheStatus(app) + self.assertEqual("cache", result["token_source"], "Should hit cache") self.assertEqual( call_count, mocked_http.call_count, "No new call to the mocked http should be made for a cache hit") @@ -110,6 +115,9 @@ expected_refresh_on - 5 < result["refresh_on"] <= expected_refresh_on, "Should have a refresh_on time around the middle of the token's life") + result = app.acquire_token_for_client(resource=resource, claims_challenge="foo") + self.assertEqual("identity_provider", result["token_source"], "Should miss cache") + class VmTestCase(ClientTestCase): @@ -131,6 +139,22 @@ json.loads(raw_error), self.app.acquire_token_for_client(resource="R")) self.assertEqual({}, self.app._token_cache._cache) + def test_vm_resource_id_parameter_should_be_msi_res_id(self): + app = ManagedIdentityClient( + {"ManagedIdentityIdType": "ResourceId", "Id": "1234"}, + http_client=requests.Session(), + ) + with patch.object(app._http_client, "get", return_value=MinimalResponse( + status_code=200, + text='{"access_token": "AT", "expires_in": 3600, "resource": "R"}', + )) as mocked_method: + app.acquire_token_for_client(resource="R") + mocked_method.assert_called_with( + 'http://169.254.169.254/metadata/identity/oauth2/token', + params={'api-version': '2018-02-01', 'resource': 'R', 'msi_res_id': '1234'}, + headers={'Metadata': 'true'}, + ) + @patch.dict(os.environ, {"IDENTITY_ENDPOINT": "http://localhost", "IDENTITY_HEADER": "foo"}) class AppServiceTestCase(ClientTestCase): @@ -156,6 +180,22 @@ }, self.app.acquire_token_for_client(resource="R")) self.assertEqual({}, self.app._token_cache._cache) + def test_app_service_resource_id_parameter_should_be_mi_res_id(self): + app = ManagedIdentityClient( + {"ManagedIdentityIdType": "ResourceId", "Id": "1234"}, + http_client=requests.Session(), + ) + with patch.object(app._http_client, "get", return_value=MinimalResponse( + status_code=200, + text='{"access_token": "AT", "expires_on": 12345, "resource": "R"}', + )) as mocked_method: + app.acquire_token_for_client(resource="R") + mocked_method.assert_called_with( + 'http://localhost', + params={'api-version': '2019-08-01', 'resource': 'R', 'mi_res_id': '1234'}, + headers={'X-IDENTITY-HEADER': 'foo', 'Metadata': 'true'}, + ) + @patch.dict(os.environ, {"MSI_ENDPOINT": "http://localhost", "MSI_SECRET": "foo"}) class MachineLearningTestCase(ClientTestCase): @@ -241,6 +281,9 @@ "WWW-Authenticate": "Basic realm=/tmp/foo", }) + def test_error_out_on_invalid_input(self, mocked_stat): + return super(ArcTestCase, self).test_error_out_on_invalid_input() + def test_happy_path(self, mocked_stat): expires_in = 1234 with patch.object(self.app._http_client, "get", side_effect=[ @@ -249,7 +292,8 @@ status_code=200, text='{"access_token": "AT", "expires_in": "%s", "resource": "R"}' % expires_in, ), - ]) as mocked_method: + ] * 2, # Duplicate a pair of mocks for _test_happy_path()'s CAE check + ) as mocked_method: try: self._test_happy_path(self.app, mocked_method, expires_in) mocked_stat.assert_called_with(os.path.join(
participants (1)
-
Source-Sync