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 2023-10-13 23:14:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-msal (Old)
 and      /work/SRC/openSUSE:Factory/.python-msal.new.20540 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-msal"

Fri Oct 13 23:14:01 2023 rev:16 rq:1116468 version:1.24.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-msal/python-msal.changes  2023-08-14 
22:36:28.504689044 +0200
+++ /work/SRC/openSUSE:Factory/.python-msal.new.20540/python-msal.changes       
2023-10-13 23:14:18.362220889 +0200
@@ -1,0 +2,28 @@
+Mon Oct  9 09:52:17 UTC 2023 - John Paul Adrian Glaubitz 
<adrian.glaub...@suse.com>
+
+- Update to version 1.24.1
+  + Includes minor adjustments on handling acquire_token_interactive().
+    The scope of the issue being addressed was limited to a short-lived
+    sign-in attempt. The potential misuse vector complexity was high,
+    therefore it is unlikely to be reproduced in standard usage scenarios;
+    however, out of abundance of caution, this fix is shipped to align
+    ourselves with Microsoft's policy of secure-by-default.
+- from version 1.24.0
+  + Enhancement: There may be a new msal_telemetry key available in MSAL's
+    acquire token response, currently observed when broker is enabled. Its
+    content and format are opaque to caller. This telemetry blob allows
+    participating apps to collect them via telemetry, and it may help
+    future troubleshooting. (#575)
+  + Enhancement: A new enable_pii_log parameter is added into ClientApplication
+    constructor. When enabled, the broker component may include PII (Personal
+    Identifiable Information) in logs. This may help troubleshooting. (#568, 
#590)
+- Remove temporary version override
+
+-------------------------------------------------------------------
+Wed Oct  4 10:11:52 UTC 2023 - John Paul Adrian Glaubitz 
<adrian.glaub...@suse.com>
+
+- Update to version 1.24.0b2
+  + Experimental: Building on top of 1.24.0b1 and includes
+    some adjustment on handling acquire_token_interactive().
+
+-------------------------------------------------------------------

Old:
----
  msal-1.24.0b1.tar.gz

New:
----
  msal-1.24.1.tar.gz

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

Other differences:
------------------
++++++ python-msal.spec ++++++
--- /var/tmp/diff_new_pack.pc7Evg/_old  2023-10-13 23:14:19.202251357 +0200
+++ /var/tmp/diff_new_pack.pc7Evg/_new  2023-10-13 23:14:19.202251357 +0200
@@ -16,20 +16,18 @@
 #
 
 
-%define realversion 1.24.0b1
-
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %if 0%{?suse_version} >= 1500
 %define skip_python2 1
 %endif
 Name:           python-msal
-Version:        1.24.0~b1
+Version:        1.24.1
 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-%{realversion}.tar.gz
+Source:         
https://files.pythonhosted.org/packages/source/m/msal/msal-%{version}.tar.gz
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
@@ -51,7 +49,7 @@
 standard OAuth2 and OpenID Connect.
 
 %prep
-%setup -q -n msal-%{realversion}
+%setup -q -n msal-%{version}
 
 %build
 %python_build

++++++ msal-1.24.0b1.tar.gz -> msal-1.24.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/PKG-INFO new/msal-1.24.1/PKG-INFO
--- old/msal-1.24.0b1/PKG-INFO  2023-07-24 11:38:52.279546500 +0200
+++ new/msal-1.24.1/PKG-INFO    2023-09-29 09:54:15.598715300 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: msal
-Version: 1.24.0b1
+Version: 1.24.1
 Summary: The Microsoft Authentication Library (MSAL) for Python library
 Home-page: 
https://github.com/AzureAD/microsoft-authentication-library-for-python
 Author: Microsoft Corporation
@@ -26,14 +26,19 @@
 Classifier: Operating System :: OS Independent
 Requires-Python: >=2.7
 Description-Content-Type: text/markdown
-Provides-Extra: broker
 License-File: LICENSE
+Requires-Dist: requests<3,>=2.0.0
+Requires-Dist: PyJWT[crypto]<3,>=1.0.0
+Requires-Dist: cryptography<44,>=0.6
+Requires-Dist: mock; python_version < "3.3"
+Provides-Extra: broker
+Requires-Dist: pymsalruntime<0.14,>=0.13.2; (python_version >= "3.6" and 
platform_system == "Windows") and extra == "broker"
 
 # Microsoft Authentication Library (MSAL) for Python
 
-| `dev` branch | Reference Docs | # of Downloads per different platforms | # 
of Downloads per recent MSAL versions |
-|---------------|---------------|----------------------------------------|-----------------------------------------|
- [![Build 
status](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions/workflows/python-package.yml/badge.svg?branch=dev)](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | 
[![Downloads](https://pepy.tech/badge/msal)](https://pypistats.org/packages/msal)
 | [![Download 
monthly](https://pepy.tech/badge/msal/month)](https://pepy.tech/project/msal)
+| `dev` branch | Reference Docs | # of Downloads per different platforms | # 
of Downloads per recent MSAL versions | Benchmark Diagram |
+|:------------:|:--------------:|:--------------------------------------:|:---------------------------------------:|:-----------------:|
+ [![Build 
status](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions/workflows/python-package.yml/badge.svg?branch=dev)](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | 
[![Downloads](https://static.pepy.tech/badge/msal)](https://pypistats.org/packages/msal)
 | [![Download 
monthly](https://static.pepy.tech/badge/msal/month)](https://pepy.tech/project/msal)
 | 
[📉](https://azuread.github.io/microsoft-authentication-library-for-python/dev/bench/)
 
 The Microsoft Authentication Library for Python enables applications to 
integrate with the [Microsoft identity platform](https://aka.ms/aaddevv2). It 
allows you to sign in users or apps with Microsoft identities ([Azure 
AD](https://azure.microsoft.com/services/active-directory/), [Microsoft 
Accounts](https://account.microsoft.com) and [Azure AD 
B2C](https://azure.microsoft.com/services/active-directory-b2c/) accounts) and 
obtain tokens to call Microsoft APIs such as [Microsoft 
Graph](https://graph.microsoft.io/) or your own APIs registered with the 
Microsoft identity platform. It is built using industry standard OAuth2 and 
OpenID Connect protocols
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/README.md new/msal-1.24.1/README.md
--- old/msal-1.24.0b1/README.md 2023-07-24 11:38:42.000000000 +0200
+++ new/msal-1.24.1/README.md   2023-09-29 09:54:06.000000000 +0200
@@ -1,8 +1,8 @@
 # Microsoft Authentication Library (MSAL) for Python
 
-| `dev` branch | Reference Docs | # of Downloads per different platforms | # 
of Downloads per recent MSAL versions |
-|---------------|---------------|----------------------------------------|-----------------------------------------|
- [![Build 
status](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions/workflows/python-package.yml/badge.svg?branch=dev)](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | 
[![Downloads](https://pepy.tech/badge/msal)](https://pypistats.org/packages/msal)
 | [![Download 
monthly](https://pepy.tech/badge/msal/month)](https://pepy.tech/project/msal)
+| `dev` branch | Reference Docs | # of Downloads per different platforms | # 
of Downloads per recent MSAL versions | Benchmark Diagram |
+|:------------:|:--------------:|:--------------------------------------:|:---------------------------------------:|:-----------------:|
+ [![Build 
status](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions/workflows/python-package.yml/badge.svg?branch=dev)](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | 
[![Downloads](https://static.pepy.tech/badge/msal)](https://pypistats.org/packages/msal)
 | [![Download 
monthly](https://static.pepy.tech/badge/msal/month)](https://pepy.tech/project/msal)
 | 
[📉](https://azuread.github.io/microsoft-authentication-library-for-python/dev/bench/)
 
 The Microsoft Authentication Library for Python enables applications to 
integrate with the [Microsoft identity platform](https://aka.ms/aaddevv2). It 
allows you to sign in users or apps with Microsoft identities ([Azure 
AD](https://azure.microsoft.com/services/active-directory/), [Microsoft 
Accounts](https://account.microsoft.com) and [Azure AD 
B2C](https://azure.microsoft.com/services/active-directory-b2c/) accounts) and 
obtain tokens to call Microsoft APIs such as [Microsoft 
Graph](https://graph.microsoft.io/) or your own APIs registered with the 
Microsoft identity platform. It is built using industry standard OAuth2 and 
OpenID Connect protocols
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/msal/__main__.py 
new/msal-1.24.1/msal/__main__.py
--- old/msal-1.24.0b1/msal/__main__.py  1970-01-01 01:00:00.000000000 +0100
+++ new/msal-1.24.1/msal/__main__.py    2023-09-29 09:54:06.000000000 +0200
@@ -0,0 +1,232 @@
+# It is currently shipped inside msal library.
+# Pros: It is always available wherever msal is installed.
+# Cons: Its 3rd-party dependencies (if any) may become msal's dependency.
+"""MSAL Python Tester
+
+Usage 1: Run it on the fly.
+    python -m msal
+
+Usage 2: Build an all-in-one executable file for bug bash.
+    shiv -e msal.__main__._main -o msaltest-on-os-name.pyz .
+    Note: We choose to not define a console script to avoid name conflict.
+"""
+import base64, getpass, json, logging, sys, msal
+
+_AZURE_CLI = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
+_VISUAL_STUDIO = "04f0c124-f2bc-4f59-8241-bf6df9866bbd"
+
+def print_json(blob):
+    print(json.dumps(blob, indent=2, sort_keys=True))
+
+def _input_boolean(message):
+    return input(
+        "{} (N/n/F/f or empty means False, otherwise it is True): 
".format(message)
+        ) not in ('N', 'n', 'F', 'f', '')
+
+def _input(message, default=None):
+    return input(message.format(default=default)).strip() or default
+
+def _select_options(
+        options, header="Your options:", footer="    Your choice? ", 
option_renderer=str,
+        accept_nonempty_string=False,
+        ):
+    assert options, "options must not be empty"
+    if header:
+        print(header)
+    for i, o in enumerate(options, start=1):
+        print("    {}: {}".format(i, option_renderer(o)))
+    if accept_nonempty_string:
+        print("    Or you can just type in your input.")
+    while True:
+        raw_data = input(footer)
+        try:
+            choice = int(raw_data)
+            if 1 <= choice <= len(options):
+                return options[choice - 1]
+        except ValueError:
+            if raw_data and accept_nonempty_string:
+                return raw_data
+
+def _input_scopes():
+    scopes = _select_options([
+        "https://graph.microsoft.com/.default";,
+        "https://management.azure.com/.default";,
+        "User.Read",
+        "User.ReadBasic.All",
+        ],
+        header="Select a scope (multiple scopes can only be input by manually 
typing them, delimited by space):",
+        accept_nonempty_string=True,
+        ).split()  # It also converts the input string(s) into a list
+    if "https://pas.windows.net/CheckMyAccess/Linux/.default"; in scopes:
+        raise ValueError("SSH Cert scope shall be tested by its dedicated 
functions")
+    return scopes
+
+def _select_account(app):
+    accounts = app.get_accounts()
+    if accounts:
+        return _select_options(
+            accounts,
+            option_renderer=lambda a: a["username"],
+            header="Account(s) already signed in inside MSAL Python:",
+            )
+    else:
+        print("No account available inside MSAL Python. Use other methods to 
acquire token first.")
+
+def _acquire_token_silent(app):
+    """acquire_token_silent() - with an account already signed into MSAL 
Python."""
+    account = _select_account(app)
+    if account:
+        print_json(app.acquire_token_silent(
+            _input_scopes(),
+            account=account,
+            force_refresh=_input_boolean("Bypass MSAL Python's token cache?"),
+            ))
+
+def _acquire_token_interactive(app, scopes=None, data=None):
+    """acquire_token_interactive() - User will be prompted if app opts to do 
select_account."""
+    scopes = scopes or _input_scopes()  # Let user input scope param before 
less important prompt and login_hint
+    prompt = _select_options([
+        {"value": None, "description": "Unspecified. Proceed silently with a 
default account (if any), fallback to prompt."},
+        {"value": "none", "description": "none. Proceed silently with a 
default account (if any), or error out."},
+        {"value": "select_account", "description": "select_account. Prompt 
with an account picker."},
+        ],
+        option_renderer=lambda o: o["description"],
+        header="Prompt behavior?")["value"]
+    if prompt == "select_account":
+        login_hint = None  # login_hint is unnecessary when 
prompt=select_account
+    else:
+        raw_login_hint = _select_options(
+            [None] + [a["username"] for a in app.get_accounts()],
+            header="login_hint? (If you have multiple signed-in sessions in 
browser/broker, and you specify a login_hint to match one of them, you will 
bypass the account picker.)",
+            accept_nonempty_string=True,
+            )
+        login_hint = raw_login_hint["username"] if isinstance(raw_login_hint, 
dict) else raw_login_hint
+    result = app.acquire_token_interactive(
+        scopes,
+        parent_window_handle=app.CONSOLE_WINDOW_HANDLE,  # This test app is a 
console app
+        enable_msa_passthrough=app.client_id in [  # Apps are expected to set 
this right
+            _AZURE_CLI, _VISUAL_STUDIO,
+            ],  # Here this test app mimics the setting for some known MSA-PT 
apps
+        prompt=prompt, login_hint=login_hint, data=data or {},
+        )
+    if login_hint and "id_token_claims" in result:
+        signed_in_user = result.get("id_token_claims", 
{}).get("preferred_username")
+        if signed_in_user != login_hint:
+            logging.warning('Signed-in user "%s" does not match login_hint', 
signed_in_user)
+    print_json(result)
+    return result
+
+def _acquire_token_by_username_password(app):
+    """acquire_token_by_username_password() - See constraints here: 
https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#constraints-for-ropc""";
+    print_json(app.acquire_token_by_username_password(
+        _input("username: "), getpass.getpass("password: "), 
scopes=_input_scopes()))
+
+_JWK1 = """{"kty":"RSA", 
"n":"2tNr73xwcj6lH7bqRZrFzgSLj7OeLfbn8216uOMDHuaZ6TEUBDN8Uz0ve8jAlKsP9CQFCSVoSNovdE-fs7c15MxEGHjDcNKLWonznximj8pDGZQjVdfK-7mG6P6z-lgVcLuYu5JcWU_PeEqIKg5llOaz-qeQ4LEDS4T1D2qWRGpAra4rJX1-kmrWmX_XIamq30C9EIO0gGuT4rc2hJBWQ-4-FnE1NXmy125wfT3NdotAJGq5lMIfhjfglDbJCwhc8Oe17ORjO3FsB5CLuBRpYmP7Nzn66lRY3Fe11Xz8AEBl3anKFSJcTvlMnFtu3EpD-eiaHfTgRBU7CztGQqVbiQ",
 "e":"AQAB"}"""
+_SSH_CERT_DATA = {"token_type": "ssh-cert", "key_id": "key1", "req_cnf": _JWK1}
+_SSH_CERT_SCOPE = ["https://pas.windows.net/CheckMyAccess/Linux/.default";]
+
+def _acquire_ssh_cert_silently(app):
+    """Acquire an SSH Cert silently- This typically only works with Azure 
CLI"""
+    account = _select_account(app)
+    if account:
+        result = app.acquire_token_silent(
+            _SSH_CERT_SCOPE,
+            account,
+            data=_SSH_CERT_DATA,
+            force_refresh=_input_boolean("Bypass MSAL Python's token cache?"),
+            )
+        print_json(result)
+        if result and result.get("token_type") != "ssh-cert":
+            logging.error("Unable to acquire an ssh-cert.")
+
+def _acquire_ssh_cert_interactive(app):
+    """Acquire an SSH Cert interactively - This typically only works with 
Azure CLI"""
+    result = _acquire_token_interactive(app, scopes=_SSH_CERT_SCOPE, 
data=_SSH_CERT_DATA)
+    if result.get("token_type") != "ssh-cert":
+        logging.error("Unable to acquire an ssh-cert")
+
+_POP_KEY_ID = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-AAAAAAAA'  # Fake key with a 
certain format and length
+_RAW_REQ_CNF = json.dumps({"kid": _POP_KEY_ID, "xms_ksl": "sw"})
+_POP_DATA = {  # Sampled from Azure CLI's plugin connectedk8s
+    'token_type': 'pop',
+    'key_id': _POP_KEY_ID,
+    "req_cnf": 
base64.urlsafe_b64encode(_RAW_REQ_CNF.encode('utf-8')).decode('utf-8').rstrip('='),
+        # Note: Sending _RAW_REQ_CNF without base64 encoding would result in 
an http 500 error
+}  # See also 
https://github.com/Azure/azure-cli-extensions/blob/main/src/connectedk8s/azext_connectedk8s/_clientproxyutils.py#L86-L92
+
+def _acquire_pop_token_interactive(app):
+    """Acquire a POP token interactively - This typically only works with 
Azure CLI"""
+    POP_SCOPE = ['6256c85f-0aad-4d50-b960-e6e9b21efe35/.default']  # KAP 1P 
Server App Scope, obtained from 
https://github.com/Azure/azure-cli-extensions/pull/4468/files#diff-a47efa3186c7eb4f1176e07d0b858ead0bf4a58bfd51e448ee3607a5b4ef47f6R116
+    result = _acquire_token_interactive(app, scopes=POP_SCOPE, data=_POP_DATA)
+    print_json(result)
+    if result.get("token_type") != "pop":
+        logging.error("Unable to acquire a pop token")
+
+def _remove_account(app):
+    """remove_account() - Invalidate account and/or token(s) from cache, so 
that acquire_token_silent() would be reset"""
+    account = _select_account(app)
+    if account:
+        app.remove_account(account)
+        print('Account "{}" and/or its token(s) are signed out from MSAL 
Python'.format(account["username"]))
+
+def _exit(app):
+    """Exit"""
+    bug_link = (
+        
"https://identitydivision.visualstudio.com/Engineering/_queries/query/79b3a352-a775-406f-87cd-a487c382a8ed/";
+        if app._enable_broker else
+        
"https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/new/choose";
+        )
+    print("Bye. If you found a bug, please report it here: 
{}".format(bug_link))
+    sys.exit()
+
+def _main():
+    print("Welcome to the Msal Python {} Tester 
(Experimental)\n".format(msal.__version__))
+    chosen_app = _select_options([
+        {"client_id": _AZURE_CLI, "name": "Azure CLI (Correctly configured for 
MSA-PT)"},
+        {"client_id": _VISUAL_STUDIO, "name": "Visual Studio (Correctly 
configured for MSA-PT)"},
+        {"client_id": "95de633a-083e-42f5-b444-a4295d8e9314", "name": 
"Whiteboard Services (Non MSA-PT app. Accepts AAD & MSA accounts.)"},
+        ],
+        option_renderer=lambda a: a["name"],
+        header="Impersonate this app (or you can type in the client_id of your 
own app)",
+        accept_nonempty_string=True)
+    allow_broker = _input_boolean("Allow broker?")
+    enable_debug_log = _input_boolean("Enable MSAL Python's DEBUG log?")
+    enable_pii_log = _input_boolean("Enable PII in broker's log?") if 
allow_broker and enable_debug_log else False
+    app = msal.PublicClientApplication(
+        chosen_app["client_id"] if isinstance(chosen_app, dict) else 
chosen_app,
+        authority=_select_options([
+            "https://login.microsoftonline.com/common";,
+            "https://login.microsoftonline.com/organizations";,
+            "https://login.microsoftonline.com/microsoft.onmicrosoft.com";,
+            "https://login.microsoftonline.com/msidlab4.onmicrosoft.com";,
+            "https://login.microsoftonline.com/consumers";,
+            ],
+            header="Input authority (Note that MSA-PT apps would NOT use the 
/common authority)",
+            accept_nonempty_string=True,
+            ),
+        allow_broker=allow_broker,
+        enable_pii_log=enable_pii_log,
+        )
+    if enable_debug_log:
+        logging.basicConfig(level=logging.DEBUG)
+    while True:
+        func = _select_options([
+            _acquire_token_silent,
+            _acquire_token_interactive,
+            _acquire_token_by_username_password,
+            _acquire_ssh_cert_silently,
+            _acquire_ssh_cert_interactive,
+            _acquire_pop_token_interactive,
+            _remove_account,
+            _exit,
+            ], option_renderer=lambda f: f.__doc__, header="MSAL Python APIs:")
+        try:
+            func(app)
+        except ValueError as e:
+            logging.error("Invalid input: %s", e)
+        except KeyboardInterrupt:  # Useful for bailing out a stuck 
interactive flow
+            print("Aborted")
+
+if __name__ == "__main__":
+    _main()
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/msal/application.py 
new/msal-1.24.1/msal/application.py
--- old/msal-1.24.0b1/msal/application.py       2023-07-24 11:38:42.000000000 
+0200
+++ new/msal-1.24.1/msal/application.py 2023-09-29 09:54:06.000000000 +0200
@@ -25,7 +25,7 @@
 
 
 # The __init__.py will import this. Not the other way around.
-__version__ = "1.24.0b1"  # When releasing, also check and bump our 
dependencies's versions if needed
+__version__ = "1.24.1"  # When releasing, also check and bump our 
dependencies's versions if needed
 
 logger = logging.getLogger(__name__)
 _AUTHORITY_TYPE_CLOUDSHELL = "CLOUDSHELL"
@@ -193,6 +193,7 @@
             http_cache=None,
             instance_discovery=None,
             allow_broker=None,
+            enable_pii_log=None,
             ):
         """Create an instance of application.
 
@@ -205,12 +206,19 @@
             or an X509 certificate container in this form::
 
                 {
-                    "private_key": "...-----BEGIN PRIVATE KEY-----...",
+                    "private_key": "...-----BEGIN PRIVATE KEY-----... in PEM 
format",
                     "thumbprint": "A1B2C3D4E5F6...",
                     "public_certificate": "...-----BEGIN CERTIFICATE-----... 
(Optional. See below.)",
                     "passphrase": "Passphrase if the private_key is encrypted 
(Optional. Added in version 1.6.0)",
                 }
 
+            MSAL Python requires a "private_key" in PEM format.
+            If your cert is in a PKCS12 (.pfx) format, you can also
+            `convert it to PEM and get the thumbprint 
<https://github.com/Azure/azure-sdk-for-python/blob/07d10639d7e47f4852eaeb74aef5d569db499d6e/sdk/identity/azure-identity/azure/identity/_credentials/certificate.py#L101-L123>`_.
+
+            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>`_.
+
             *Added in version 0.5.0*:
             public_certificate (optional) is public key certificate
             which will be sent through 'x5c' JWT header only for
@@ -500,6 +508,13 @@
               * AAD and MSA accounts (i.e. Non-ADFS, non-B2C)
 
             New in version 1.20.0.
+
+        :param boolean enable_pii_log:
+            When enabled, logs may include PII (Personal Identifiable 
Information).
+            This can be useful in troubleshooting broker behaviors.
+            The default behavior is False.
+
+            New in version 1.24.0.
         """
         self.client_id = client_id
         self.client_credential = client_credential
@@ -576,6 +591,8 @@
             try:
                 from . import broker  # Trigger Broker's initialization
                 self._enable_broker = True
+                if enable_pii_log:
+                    broker._enable_pii_log()
             except RuntimeError:
                 logger.exception(
                     "Broker is unavailable on this platform. "
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/msal/authority.py 
new/msal-1.24.1/msal/authority.py
--- old/msal-1.24.0b1/msal/authority.py 2023-07-24 11:38:42.000000000 +0200
+++ new/msal-1.24.1/msal/authority.py   2023-09-29 09:54:06.000000000 +0200
@@ -163,9 +163,9 @@
     raise ValueError(
         "Your given address (%s) should consist of "
         "an https url with 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"
+        "https://login.microsoftonline.com/{tenant} "
+        "or https://{tenant_name}.ciamlogin.com/{tenant} "
+        "or 
https://{tenant_name}.b2clogin.com/{tenant_name}.onmicrosoft.com/policy";
         % authority_or_auth_endpoint)
 
 def _instance_discovery(url, http_client, instance_discovery_endpoint, 
**kwargs):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/msal/broker.py 
new/msal-1.24.1/msal/broker.py
--- old/msal-1.24.0b1/msal/broker.py    2023-07-24 11:38:42.000000000 +0200
+++ new/msal-1.24.1/msal/broker.py      2023-09-29 09:54:06.000000000 +0200
@@ -236,3 +236,6 @@
     if error:
         return _convert_error(error, client_id)
 
+def _enable_pii_log():
+    pymsalruntime.set_is_pii_enabled(1)  # New in PyMsalRuntime 0.13.0
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/msal/oauth2cli/authcode.py 
new/msal-1.24.1/msal/oauth2cli/authcode.py
--- old/msal-1.24.0b1/msal/oauth2cli/authcode.py        2023-07-24 
11:38:42.000000000 +0200
+++ new/msal-1.24.1/msal/oauth2cli/authcode.py  2023-09-29 09:54:06.000000000 
+0200
@@ -15,10 +15,12 @@
 try:  # Python 3
     from http.server import HTTPServer, BaseHTTPRequestHandler
     from urllib.parse import urlparse, parse_qs, urlencode
+    from html import escape
 except ImportError:  # Fall back to Python 2
     from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
     from urlparse import urlparse, parse_qs
     from urllib import urlencode
+    from cgi import escape
 
 
 logger = logging.getLogger(__name__)
@@ -77,25 +79,42 @@
         for k, v in qs.items()}
 
 
+def _is_html(text):
+    return text.startswith("<")  # Good enough for our purpose
+
+
+def _escape(key_value_pairs):
+    return {k: escape(v) for k, v in key_value_pairs.items()}
+
+
 class _AuthCodeHandler(BaseHTTPRequestHandler):
     def do_GET(self):
         # For flexibility, we choose to not check self.path matching 
redirect_uri
         #assert self.path.startswith('/THE_PATH_REGISTERED_BY_THE_APP')
         qs = parse_qs(urlparse(self.path).query)
         if qs.get('code') or qs.get("error"):  # So, it is an auth response
-            self.server.auth_response = _qs2kv(qs)
-            logger.debug("Got auth response: %s", self.server.auth_response)
-            template = (self.server.success_template
-                if "code" in qs else self.server.error_template)
-            self._send_full_response(
-                template.safe_substitute(**self.server.auth_response))
-            # NOTE: Don't do self.server.shutdown() here. It'll halt the 
server.
+            auth_response = _qs2kv(qs)
+            logger.debug("Got auth response: %s", auth_response)
+            if self.server.auth_state and self.server.auth_state != 
auth_response.get("state"):
+                # OAuth2 successful and error responses contain state when it 
was used
+                # https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2.1
+                self._send_full_response("State mismatch")  # Possibly an 
attack
+            else:
+                template = (self.server.success_template
+                    if "code" in qs else self.server.error_template)
+                if _is_html(template.template):
+                    safe_data = _escape(auth_response)  # Foiling an XSS attack
+                else:
+                    safe_data = auth_response
+                self._send_full_response(template.safe_substitute(**safe_data))
+                self.server.auth_response = auth_response  # Set it now, after 
the response is likely sent
         else:
             self._send_full_response(self.server.welcome_page)
+        # NOTE: Don't do self.server.shutdown() here. It'll halt the server.
 
     def _send_full_response(self, body, is_ok=True):
         self.send_response(200 if is_ok else 400)
-        content_type = 'text/html' if body.startswith('<') else 'text/plain'
+        content_type = 'text/html' if _is_html(body) else 'text/plain'
         self.send_header('Content-type', content_type)
         self.end_headers()
         self.wfile.write(body.encode("utf-8"))
@@ -281,16 +300,14 @@
 
         self._server.timeout = timeout  # Otherwise its handle_timeout() won't 
work
         self._server.auth_response = {}  # Shared with _AuthCodeHandler
+        self._server.auth_state = state  # So handler will check it before 
sending response
         while not self._closing:  # Otherwise, the handle_request() attempt
                                   # would yield noisy ValueError trace
             # Derived from
             # 
https://docs.python.org/2/library/basehttpserver.html#more-examples
             self._server.handle_request()
             if self._server.auth_response:
-                if state and state != self._server.auth_response.get("state"):
-                    logger.debug("State mismatch. Ignoring this noise.")
-                else:
-                    break
+                break
         result.update(self._server.auth_response)  # Return via writable 
result param
 
     def close(self):
@@ -318,6 +335,7 @@
         
default="https://login.microsoftonline.com/common/oauth2/v2.0/authorize";)
     p.add_argument('client_id', help="The client_id of your application")
     p.add_argument('--port', type=int, default=0, help="The port in 
redirect_uri")
+    p.add_argument('--timeout', type=int, default=60, help="Timeout value, in 
second")
     p.add_argument('--host', default="127.0.0.1", help="The host of 
redirect_uri")
     p.add_argument('--scope', default=None, help="The scope list")
     args = parser.parse_args()
@@ -331,8 +349,8 @@
             auth_uri=flow["auth_uri"],
             welcome_template=
                 "<a href='$auth_uri'>Sign In</a>, or <a 
href='$abort_uri'>Abort</a",
-            error_template="Oh no. $error",
+            error_template="<html>Oh no. $error</html>",
             success_template="Oh yeah. Got $code",
-            timeout=60,
+            timeout=args.timeout,
             state=flow["state"],  # Optional
             ), indent=4))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/msal/oauth2cli/oauth2.py 
new/msal-1.24.1/msal/oauth2cli/oauth2.py
--- old/msal-1.24.0b1/msal/oauth2cli/oauth2.py  2023-07-24 11:38:42.000000000 
+0200
+++ new/msal-1.24.1/msal/oauth2cli/oauth2.py    2023-09-29 09:54:06.000000000 
+0200
@@ -666,7 +666,7 @@
             **(auth_params or {}))
         auth_response = auth_code_receiver.get_auth_response(
             auth_uri=flow["auth_uri"],
-            state=flow["state"],  # Optional but we choose to do it upfront
+            state=flow["state"],  # So receiver can check it early
             timeout=timeout,
             welcome_template=welcome_template,
             success_template=success_template,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/msal.egg-info/PKG-INFO 
new/msal-1.24.1/msal.egg-info/PKG-INFO
--- old/msal-1.24.0b1/msal.egg-info/PKG-INFO    2023-07-24 11:38:52.000000000 
+0200
+++ new/msal-1.24.1/msal.egg-info/PKG-INFO      2023-09-29 09:54:15.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: msal
-Version: 1.24.0b1
+Version: 1.24.1
 Summary: The Microsoft Authentication Library (MSAL) for Python library
 Home-page: 
https://github.com/AzureAD/microsoft-authentication-library-for-python
 Author: Microsoft Corporation
@@ -26,14 +26,19 @@
 Classifier: Operating System :: OS Independent
 Requires-Python: >=2.7
 Description-Content-Type: text/markdown
-Provides-Extra: broker
 License-File: LICENSE
+Requires-Dist: requests<3,>=2.0.0
+Requires-Dist: PyJWT[crypto]<3,>=1.0.0
+Requires-Dist: cryptography<44,>=0.6
+Requires-Dist: mock; python_version < "3.3"
+Provides-Extra: broker
+Requires-Dist: pymsalruntime<0.14,>=0.13.2; (python_version >= "3.6" and 
platform_system == "Windows") and extra == "broker"
 
 # Microsoft Authentication Library (MSAL) for Python
 
-| `dev` branch | Reference Docs | # of Downloads per different platforms | # 
of Downloads per recent MSAL versions |
-|---------------|---------------|----------------------------------------|-----------------------------------------|
- [![Build 
status](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions/workflows/python-package.yml/badge.svg?branch=dev)](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | 
[![Downloads](https://pepy.tech/badge/msal)](https://pypistats.org/packages/msal)
 | [![Download 
monthly](https://pepy.tech/badge/msal/month)](https://pepy.tech/project/msal)
+| `dev` branch | Reference Docs | # of Downloads per different platforms | # 
of Downloads per recent MSAL versions | Benchmark Diagram |
+|:------------:|:--------------:|:--------------------------------------:|:---------------------------------------:|:-----------------:|
+ [![Build 
status](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions/workflows/python-package.yml/badge.svg?branch=dev)](https://github.com/AzureAD/microsoft-authentication-library-for-python/actions)
 | [![Documentation 
Status](https://readthedocs.org/projects/msal-python/badge/?version=latest)](https://msal-python.readthedocs.io/en/latest/?badge=latest)
 | 
[![Downloads](https://static.pepy.tech/badge/msal)](https://pypistats.org/packages/msal)
 | [![Download 
monthly](https://static.pepy.tech/badge/msal/month)](https://pepy.tech/project/msal)
 | 
[📉](https://azuread.github.io/microsoft-authentication-library-for-python/dev/bench/)
 
 The Microsoft Authentication Library for Python enables applications to 
integrate with the [Microsoft identity platform](https://aka.ms/aaddevv2). It 
allows you to sign in users or apps with Microsoft identities ([Azure 
AD](https://azure.microsoft.com/services/active-directory/), [Microsoft 
Accounts](https://account.microsoft.com) and [Azure AD 
B2C](https://azure.microsoft.com/services/active-directory-b2c/) accounts) and 
obtain tokens to call Microsoft APIs such as [Microsoft 
Graph](https://graph.microsoft.io/) or your own APIs registered with the 
Microsoft identity platform. It is built using industry standard OAuth2 and 
OpenID Connect protocols
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/msal.egg-info/SOURCES.txt 
new/msal-1.24.1/msal.egg-info/SOURCES.txt
--- old/msal-1.24.0b1/msal.egg-info/SOURCES.txt 2023-07-24 11:38:52.000000000 
+0200
+++ new/msal-1.24.1/msal.egg-info/SOURCES.txt   2023-09-29 09:54:15.000000000 
+0200
@@ -3,6 +3,7 @@
 setup.cfg
 setup.py
 msal/__init__.py
+msal/__main__.py
 msal/application.py
 msal/authority.py
 msal/broker.py
@@ -31,9 +32,11 @@
 tests/test_assertion.py
 tests/test_authcode.py
 tests/test_authority.py
+tests/test_benchmark.py
 tests/test_broker.py
 tests/test_ccs.py
 tests/test_client.py
+tests/test_cryptography.py
 tests/test_e2e.py
 tests/test_individual_cache.py
 tests/test_mex.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/tests/test_application.py 
new/msal-1.24.1/tests/test_application.py
--- old/msal-1.24.0b1/tests/test_application.py 2023-07-24 11:38:42.000000000 
+0200
+++ new/msal-1.24.1/tests/test_application.py   2023-09-29 09:54:06.000000000 
+0200
@@ -627,7 +627,7 @@
     sys.version_info[0] >= 3 and sys.version_info[1] >= 2,
     "assertWarns() is only available in Python 3.2+")
 class TestClientCredentialGrant(unittest.TestCase):
-    def _test_certain_authority_should_emit_warnning(self, authority):
+    def _test_certain_authority_should_emit_warning(self, authority):
         app = ConfidentialClientApplication(
             "client_id", client_credential="secret", authority=authority)
         def mock_post(url, headers=None, *args, **kwargs):
@@ -636,12 +636,12 @@
         with self.assertWarns(DeprecationWarning):
             app.acquire_token_for_client(["scope"], post=mock_post)
 
-    def test_common_authority_should_emit_warnning(self):
-        self._test_certain_authority_should_emit_warnning(
+    def test_common_authority_should_emit_warning(self):
+        self._test_certain_authority_should_emit_warning(
             authority="https://login.microsoftonline.com/common";)
 
-    def test_organizations_authority_should_emit_warnning(self):
-        self._test_certain_authority_should_emit_warnning(
+    def test_organizations_authority_should_emit_warning(self):
+        self._test_certain_authority_should_emit_warning(
             authority="https://login.microsoftonline.com/organizations";)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/tests/test_authcode.py 
new/msal-1.24.1/tests/test_authcode.py
--- old/msal-1.24.0b1/tests/test_authcode.py    2023-07-24 11:38:42.000000000 
+0200
+++ new/msal-1.24.1/tests/test_authcode.py      2023-09-29 09:54:06.000000000 
+0200
@@ -2,6 +2,8 @@
 import socket
 import sys
 
+import requests
+
 from msal.oauth2cli.authcode import AuthCodeReceiver
 
 
@@ -17,10 +19,24 @@
             self.assertNotEqual(port, receiver.get_port())
 
     def test_no_two_concurrent_receivers_can_listen_on_same_port(self):
-        port = 12345  # Assuming this port is available
-        with AuthCodeReceiver(port=port) as receiver:
+        with AuthCodeReceiver() as receiver:
             expected_error = OSError if sys.version_info[0] > 2 else 
socket.error
             with self.assertRaises(expected_error):
-                with AuthCodeReceiver(port=port) as receiver2:
+                with AuthCodeReceiver(port=receiver.get_port()):
                     pass
 
+    def test_template_should_escape_input(self):
+        with AuthCodeReceiver() as receiver:
+            receiver._scheduled_actions = [(  # Injection happens here when 
the port is known
+                1,  # Delay it until the receiver is activated by 
get_auth_response()
+                lambda: self.assertEqual(
+                    "<html>&lt;tag&gt;foo&lt;/tag&gt;</html>",
+                    
requests.get("http://localhost:{}?error=<tag>foo</tag>".format(
+                        receiver.get_port())).text,
+                    "Unsafe data in HTML should be escaped",
+            ))]
+            receiver.get_auth_response(  # Starts server and hang until timeout
+                timeout=3,
+                error_template="<html>$error</html>",
+            )
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/tests/test_benchmark.py 
new/msal-1.24.1/tests/test_benchmark.py
--- old/msal-1.24.0b1/tests/test_benchmark.py   1970-01-01 01:00:00.000000000 
+0100
+++ new/msal-1.24.1/tests/test_benchmark.py     2023-09-29 09:54:06.000000000 
+0200
@@ -0,0 +1,28 @@
+from tests.simulator import ClientCredentialGrantSimulator as CcaTester
+from perf_baseline import Baseline
+
+
+baseline = Baseline(".perf.baseline", threshold=1.5)  # Up to 1.5x slower than 
baseline
+
+# Here come benchmark test cases, powered by pytest-benchmark
+# Func names will become diag names.
+def test_cca_1_tenant_with_10_tokens_per_tenant_and_cache_hit(benchmark):
+    tester = CcaTester(tokens_per_tenant=10, cache_hit=True)
+    baseline.set_or_compare(tester.run)
+    benchmark(tester.run)
+
+def test_cca_many_tenants_with_10_tokens_per_tenant_and_cache_hit(benchmark):
+    tester = CcaTester(number_of_tenants=1000, tokens_per_tenant=10, 
cache_hit=True)
+    baseline.set_or_compare(tester.run)
+    benchmark(tester.run)
+
+def test_cca_1_tenant_with_10_tokens_per_tenant_and_cache_miss(benchmark):
+    tester = CcaTester(tokens_per_tenant=10, cache_hit=False)
+    baseline.set_or_compare(tester.run)
+    benchmark(tester.run)
+
+def test_cca_many_tenants_with_10_tokens_per_tenant_and_cache_miss(benchmark):
+    tester = CcaTester(number_of_tenants=1000, tokens_per_tenant=10, 
cache_hit=False)
+    baseline.set_or_compare(tester.run)
+    benchmark(tester.run)
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/tests/test_cryptography.py 
new/msal-1.24.1/tests/test_cryptography.py
--- old/msal-1.24.0b1/tests/test_cryptography.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/msal-1.24.1/tests/test_cryptography.py  2023-09-29 09:54:06.000000000 
+0200
@@ -0,0 +1,63 @@
+import configparser
+import os
+import re
+from unittest import TestCase
+import warnings
+import xml.etree.ElementTree as ET
+
+import requests
+
+from msal.application import _str2bytes
+
+
+latest_cryptography_version = ET.fromstring(
+    requests.get("https://pypi.org/rss/project/cryptography/releases.xml";).text
+    ).findall("./channel/item/title")[0].text
+
+
+def get_current_ceiling():
+    parser = configparser.ConfigParser()
+    parser.read("setup.cfg")
+    for line in parser["options"]["install_requires"].splitlines():
+        if line.startswith("cryptography"):
+            match = re.search(r"<(\d+)", line)
+            if match:
+                return int(match.group(1))
+    raise RuntimeError("Unable to find cryptography info from setup.cfg")
+
+
+class CryptographyTestCase(TestCase):
+
+    def test_should_be_run_with_latest_version_of_cryptography(self):
+        import cryptography
+        self.assertEqual(
+            cryptography.__version__, latest_cryptography_version,
+            "We are using cryptography {} but we should test with latest {} 
instead. "
+            "Run 'pip install -U cryptography'.".format(
+            cryptography.__version__, latest_cryptography_version))
+
+    def 
test_latest_cryptography_should_support_our_usage_without_warnings(self):
+        with open(os.path.join(
+                os.path.dirname(__file__), "certificate-with-password.pem")) 
as f:
+            cert = f.read()
+        with warnings.catch_warnings(record=True) as encountered_warnings:
+            # The usage was copied from application.py
+            from cryptography.hazmat.primitives import serialization
+            from cryptography.hazmat.backends import default_backend
+            unencrypted_private_key = serialization.load_pem_private_key(
+                 _str2bytes(cert),
+                _str2bytes("password"),
+                backend=default_backend(),  # It was a required param until 
2020
+                )
+            self.assertEqual(0, len(encountered_warnings),
+                "Did cryptography deprecate the functions that we used?")
+
+    def test_ceiling_should_be_latest_cryptography_version_plus_three(self):
+        expected_ceiling = int(latest_cryptography_version.split(".")[0]) + 3
+        self.assertEqual(
+            expected_ceiling, get_current_ceiling(),
+            "Test passed with latest cryptography, so we shall bump ceiling to 
N+3={}, "
+            "based on their latest deprecation policy "
+            
"https://cryptography.io/en/latest/api-stability/#deprecation".format(
+            expected_ceiling))
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-1.24.0b1/tests/test_e2e.py 
new/msal-1.24.1/tests/test_e2e.py
--- old/msal-1.24.0b1/tests/test_e2e.py 2023-07-24 11:38:42.000000000 +0200
+++ new/msal-1.24.1/tests/test_e2e.py   2023-09-29 09:54:06.000000000 +0200
@@ -10,7 +10,7 @@
     load_dotenv()  # take environment variables from .env.
 except:
     pass
-
+import base64
 import logging
 import os
 import json
@@ -277,48 +277,6 @@
         return result  # For further testing
 
 
-class SshCertTestCase(E2eTestCase):
-    _JWK1 = """{"kty":"RSA", 
"n":"2tNr73xwcj6lH7bqRZrFzgSLj7OeLfbn8216uOMDHuaZ6TEUBDN8Uz0ve8jAlKsP9CQFCSVoSNovdE-fs7c15MxEGHjDcNKLWonznximj8pDGZQjVdfK-7mG6P6z-lgVcLuYu5JcWU_PeEqIKg5llOaz-qeQ4LEDS4T1D2qWRGpAra4rJX1-kmrWmX_XIamq30C9EIO0gGuT4rc2hJBWQ-4-FnE1NXmy125wfT3NdotAJGq5lMIfhjfglDbJCwhc8Oe17ORjO3FsB5CLuBRpYmP7Nzn66lRY3Fe11Xz8AEBl3anKFSJcTvlMnFtu3EpD-eiaHfTgRBU7CztGQqVbiQ",
 "e":"AQAB"}"""
-    _JWK2 = """{"kty":"RSA", 
"n":"72u07mew8rw-ssw3tUs9clKstGO2lvD7ZNxJU7OPNKz5PGYx3gjkhUmtNah4I4FP0DuF1ogb_qSS5eD86w10Wb1ftjWcoY8zjNO9V3ph-Q2tMQWdDW5kLdeU3-EDzc0HQeou9E0udqmfQoPbuXFQcOkdcbh3eeYejs8sWn3TQprXRwGh_TRYi-CAurXXLxQ8rp-pltUVRIr1B63fXmXhMeCAGwCPEFX9FRRs-YHUszUJl9F9-E0nmdOitiAkKfCC9LhwB9_xKtjmHUM9VaEC9jWOcdvXZutwEoW2XPMOg0Ky-s197F9rfpgHle2gBrXsbvVMvS0D-wXg6vsq6BAHzQ",
 "e":"AQAB"}"""
-    DATA1 = {"token_type": "ssh-cert", "key_id": "key1", "req_cnf": _JWK1}
-    DATA2 = {"token_type": "ssh-cert", "key_id": "key2", "req_cnf": _JWK2}
-    _SCOPE_USER = 
["https://pas.windows.net/CheckMyAccess/Linux/user_impersonation";]
-    _SCOPE_SP = ["https://pas.windows.net/CheckMyAccess/Linux/.default";]
-    SCOPE = _SCOPE_SP  # Historically there was a separation, at 2021 it is 
unified
-
-    def test_ssh_cert_for_service_principal(self):
-        # Any SP can obtain an ssh-cert. Here we use the lab app.
-        result = get_lab_app().acquire_token_for_client(self.SCOPE, 
data=self.DATA1)
-        self.assertIsNotNone(result.get("access_token"), "Encountered {}: 
{}".format(
-            result.get("error"), result.get("error_description")))
-        self.assertEqual("ssh-cert", result["token_type"])
-
-    def test_ssh_cert_for_user_should_work_with_any_account(self):
-        result = self._test_acquire_token_interactive(
-            client_id="04b07795-8ddb-461a-bbee-02f9e1bf7b46",  # Azure CLI is 
one
-                # of the only 2 clients that are PreAuthz to use ssh cert 
feature
-            authority="https://login.microsoftonline.com/common";,
-            scope=self.SCOPE,
-            data=self.DATA1,
-            username_uri="https://msidlab.com/api/user?usertype=cloud";,
-            prompt="none" if msal.application._is_running_in_cloud_shell() 
else None,
-            )   # It already tests reading AT from cache, and using RT to 
refresh
-                # acquire_token_silent() would work because we pass in the 
same key
-        self.assertIsNotNone(result.get("access_token"), "Encountered {}: 
{}".format(
-            result.get("error"), result.get("error_description")))
-        self.assertEqual("ssh-cert", result["token_type"])
-        logger.debug("%s.cache = %s",
-            self.id(), json.dumps(self.app.token_cache._cache, indent=4))
-
-        # refresh_token grant can fetch an ssh-cert bound to a different key
-        account = self.app.get_accounts()[0]
-        refreshed_ssh_cert = self.app.acquire_token_silent(
-            self.SCOPE, account=account, data=self.DATA2)
-        self.assertIsNotNone(refreshed_ssh_cert)
-        self.assertEqual(refreshed_ssh_cert["token_type"], "ssh-cert")
-        self.assertNotEqual(result["access_token"], 
refreshed_ssh_cert['access_token'])
-
-
 @unittest.skipUnless(
     msal.application._is_running_in_cloud_shell(),
     "Manually run this test case from inside Cloud Shell")
@@ -465,7 +423,7 @@
 
 def get_lab_app(
         env_client_id="LAB_APP_CLIENT_ID",
-        env_client_secret="LAB_APP_CLIENT_SECRET",
+        env_name2="LAB_APP_CLIENT_SECRET",  # A var name that hopefully avoids 
false alarm
         authority="https://login.microsoftonline.com/";
             "72f988bf-86f1-41af-91ab-2d7cd011db47",  # Microsoft tenant ID
         timeout=None,
@@ -477,18 +435,17 @@
     logger.info(
         "Reading ENV variables %s and %s for lab app defined at "
         "https://docs.msidlab.com/accounts/confidentialclient.html";,
-        env_client_id, env_client_secret)
-    if os.getenv(env_client_id) and os.getenv(env_client_secret):
+        env_client_id, env_name2)
+    if os.getenv(env_client_id) and os.getenv(env_name2):
         # A shortcut mainly for running tests on developer's local development 
machine
         # or it could be setup on Travis CI
         #   
https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings
         # Data came from here
         # https://docs.msidlab.com/accounts/confidentialclient.html
         client_id = os.getenv(env_client_id)
-        client_secret = os.getenv(env_client_secret)
+        client_secret = os.getenv(env_name2)
     else:
-        logger.info("ENV variables %s and/or %s are not defined. Fall back to 
MSI.",
-                env_client_id, env_client_secret)
+        logger.info("ENV variables are not defined. Fall back to MSI.")
         # See also 
https://microsoft.sharepoint-df.com/teams/MSIDLABSExtended/SitePages/Programmatically-accessing-LAB-API's.aspx
         raise unittest.SkipTest("MSI-based mechanism has not been implemented 
yet")
     return msal.ConfidentialClientApplication(
@@ -698,6 +655,85 @@
         self.assertCacheWorksForApp(result, scope)
 
 
+class PopWithExternalKeyTestCase(LabBasedTestCase):
+    def _test_service_principal(self):
+        # Any SP can obtain an ssh-cert. Here we use the lab app.
+        result = get_lab_app().acquire_token_for_client(self.SCOPE, 
data=self.DATA1)
+        self.assertIsNotNone(result.get("access_token"), "Encountered {}: 
{}".format(
+            result.get("error"), result.get("error_description")))
+        self.assertEqual(self.EXPECTED_TOKEN_TYPE, result["token_type"])
+
+    def _test_user_account(self):
+        lab_user = self.get_lab_user(usertype="cloud")
+        result = self._test_acquire_token_interactive(
+            client_id="04b07795-8ddb-461a-bbee-02f9e1bf7b46",  # Azure CLI is 
one
+                # of the only 2 clients that are PreAuthz to use ssh cert 
feature
+            authority="https://login.microsoftonline.com/common";,
+            scope=self.SCOPE,
+            data=self.DATA1,
+            username=lab_user["username"],
+            lab_name=lab_user["lab_name"],
+            prompt="none" if msal.application._is_running_in_cloud_shell() 
else None,
+            )   # It already tests reading AT from cache, and using RT to 
refresh
+                # acquire_token_silent() would work because we pass in the 
same key
+        self.assertIsNotNone(result.get("access_token"), "Encountered {}: 
{}".format(
+            result.get("error"), result.get("error_description")))
+        self.assertEqual(self.EXPECTED_TOKEN_TYPE, result["token_type"])
+        logger.debug("%s.cache = %s",
+            self.id(), json.dumps(self.app.token_cache._cache, indent=4))
+
+        # refresh_token grant can fetch an ssh-cert bound to a different key
+        account = self.app.get_accounts()[0]
+        refreshed_ssh_cert = self.app.acquire_token_silent(
+            self.SCOPE, account=account, data=self.DATA2)
+        self.assertIsNotNone(refreshed_ssh_cert)
+        self.assertEqual(self.EXPECTED_TOKEN_TYPE, 
refreshed_ssh_cert["token_type"])
+        self.assertNotEqual(result["access_token"], 
refreshed_ssh_cert['access_token'])
+
+
+class SshCertTestCase(PopWithExternalKeyTestCase):
+    EXPECTED_TOKEN_TYPE = "ssh-cert"
+    _JWK1 = """{"kty":"RSA", 
"n":"2tNr73xwcj6lH7bqRZrFzgSLj7OeLfbn8216uOMDHuaZ6TEUBDN8Uz0ve8jAlKsP9CQFCSVoSNovdE-fs7c15MxEGHjDcNKLWonznximj8pDGZQjVdfK-7mG6P6z-lgVcLuYu5JcWU_PeEqIKg5llOaz-qeQ4LEDS4T1D2qWRGpAra4rJX1-kmrWmX_XIamq30C9EIO0gGuT4rc2hJBWQ-4-FnE1NXmy125wfT3NdotAJGq5lMIfhjfglDbJCwhc8Oe17ORjO3FsB5CLuBRpYmP7Nzn66lRY3Fe11Xz8AEBl3anKFSJcTvlMnFtu3EpD-eiaHfTgRBU7CztGQqVbiQ",
 "e":"AQAB"}"""
+    _JWK2 = """{"kty":"RSA", 
"n":"72u07mew8rw-ssw3tUs9clKstGO2lvD7ZNxJU7OPNKz5PGYx3gjkhUmtNah4I4FP0DuF1ogb_qSS5eD86w10Wb1ftjWcoY8zjNO9V3ph-Q2tMQWdDW5kLdeU3-EDzc0HQeou9E0udqmfQoPbuXFQcOkdcbh3eeYejs8sWn3TQprXRwGh_TRYi-CAurXXLxQ8rp-pltUVRIr1B63fXmXhMeCAGwCPEFX9FRRs-YHUszUJl9F9-E0nmdOitiAkKfCC9LhwB9_xKtjmHUM9VaEC9jWOcdvXZutwEoW2XPMOg0Ky-s197F9rfpgHle2gBrXsbvVMvS0D-wXg6vsq6BAHzQ",
 "e":"AQAB"}"""
+    DATA1 = {"token_type": "ssh-cert", "key_id": "key1", "req_cnf": _JWK1}
+    DATA2 = {"token_type": "ssh-cert", "key_id": "key2", "req_cnf": _JWK2}
+    _SCOPE_USER = 
["https://pas.windows.net/CheckMyAccess/Linux/user_impersonation";]
+    _SCOPE_SP = ["https://pas.windows.net/CheckMyAccess/Linux/.default";]
+    SCOPE = _SCOPE_SP  # Historically there was a separation, at 2021 it is 
unified
+
+    def test_service_principal(self):
+        self._test_service_principal()
+
+    def test_user_account(self):
+        self._test_user_account()
+
+
+def _data_for_pop(key):
+    raw_req_cnf = json.dumps({"kid": key, "xms_ksl": "sw"})
+    return {  # Sampled from Azure CLI's plugin connectedk8s
+        'token_type': 'pop',
+        'key_id': key,
+        "req_cnf": 
base64.urlsafe_b64encode(raw_req_cnf.encode('utf-8')).decode('utf-8').rstrip('='),
+            # Note: Sending raw_req_cnf without base64 encoding would result 
in an http 500 error
+    }  # See also 
https://github.com/Azure/azure-cli-extensions/blob/main/src/connectedk8s/azext_connectedk8s/_clientproxyutils.py#L86-L92
+
+
+class AtPopWithExternalKeyTestCase(PopWithExternalKeyTestCase):
+    EXPECTED_TOKEN_TYPE = "pop"
+    DATA1 = _data_for_pop('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA-AAAAAAAA')  # 
Fake key with a certain format and length
+    DATA2 = _data_for_pop('BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB-BBBBBBBB')  # 
Fake key with a certain format and length
+    SCOPE = [
+        '6256c85f-0aad-4d50-b960-e6e9b21efe35/.default',  # Azure CLI's 
connectedk8s plugin uses this
+            # 
https://github.com/Azure/azure-cli-extensions/pull/4468/files#diff-a47efa3186c7eb4f1176e07d0b858ead0bf4a58bfd51e448ee3607a5b4ef47f6R116
+    ]
+
+    def test_service_principal(self):
+        self._test_service_principal()
+
+    def test_user_account(self):
+        self._test_user_account()
+
+
 class WorldWideTestCase(LabBasedTestCase):
 
     def test_aad_managed_user(self):  # Pure cloud

Reply via email to