Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-azure-keyvault-secrets for
openSUSE:Factory checked in at 2026-08-19 17:57:47
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-azure-keyvault-secrets (Old)
and /work/SRC/openSUSE:Factory/.python-azure-keyvault-secrets.new.1258
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-azure-keyvault-secrets"
Wed Aug 19 17:57:47 2026 rev:16 rq:1371812 version:4.11.1
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-azure-keyvault-secrets/python-azure-keyvault-secrets.changes
2026-04-22 16:55:59.815062449 +0200
+++
/work/SRC/openSUSE:Factory/.python-azure-keyvault-secrets.new.1258/python-azure-keyvault-secrets.changes
2026-08-19 17:58:04.547471066 +0200
@@ -1,0 +2,8 @@
+Tue Aug 18 08:56:30 UTC 2026 - John Paul Adrian Glaubitz
<[email protected]>
+
+- New upstream release
+ + Version 4.11.1
+ + For detailed information about changes see the
+ CHANGELOG.md file provided with this package
+
+-------------------------------------------------------------------
Old:
----
azure_keyvault_secrets-4.11.0.tar.gz
New:
----
azure_keyvault_secrets-4.11.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-azure-keyvault-secrets.spec ++++++
--- /var/tmp/diff_new_pack.yxEp8A/_old 2026-08-19 17:58:05.233495811 +0200
+++ /var/tmp/diff_new_pack.yxEp8A/_new 2026-08-19 17:58:05.235495883 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-azure-keyvault-secrets
-Version: 4.11.0
+Version: 4.11.1
Release: 0
Summary: Microsoft Azure Key Vault Secrets Client Library for Python
License: MIT
++++++ azure_keyvault_secrets-4.11.0.tar.gz ->
azure_keyvault_secrets-4.11.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_keyvault_secrets-4.11.0/CHANGELOG.md
new/azure_keyvault_secrets-4.11.1/CHANGELOG.md
--- old/azure_keyvault_secrets-4.11.0/CHANGELOG.md 2026-04-17
01:49:40.000000000 +0200
+++ new/azure_keyvault_secrets-4.11.1/CHANGELOG.md 2026-08-12
03:17:58.000000000 +0200
@@ -1,5 +1,16 @@
# Release History
+## 4.11.1 (2026-08-12)
+
+### Bugs Fixed
+
+- Fixed a replay bug in the challenge authentication policy where a request
copy stashed on the shared policy
+ instance was never cleared, allowing one request's method, URL, and body to
leak into a later, unrelated request
+ made by the same client (for example, after an Entra ID Continuous Access
Evaluation challenge). The original
+ request is now stored per-request instead of on the policy instance. This
mirrors the fix already applied to
+ `azure-keyvault-keys` and `azure-keyvault-administration` in
+ [#47742](https://github.com/Azure/azure-sdk-for-python/pull/47742).
+
## 4.11.0 (2026-03-26)
### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_keyvault_secrets-4.11.0/PKG-INFO
new/azure_keyvault_secrets-4.11.1/PKG-INFO
--- old/azure_keyvault_secrets-4.11.0/PKG-INFO 2026-04-17 01:50:27.042300700
+0200
+++ new/azure_keyvault_secrets-4.11.1/PKG-INFO 2026-08-12 03:19:09.812467000
+0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: azure-keyvault-secrets
-Version: 4.11.0
+Version: 4.11.1
Summary: Microsoft Corporation Key Vault Secrets Client Library for Python
Author-email: Microsoft Corporation <[email protected]>
License: MIT License
@@ -406,6 +406,17 @@
# Release History
+## 4.11.1 (2026-08-12)
+
+### Bugs Fixed
+
+- Fixed a replay bug in the challenge authentication policy where a request
copy stashed on the shared policy
+ instance was never cleared, allowing one request's method, URL, and body to
leak into a later, unrelated request
+ made by the same client (for example, after an Entra ID Continuous Access
Evaluation challenge). The original
+ request is now stored per-request instead of on the policy instance. This
mirrors the fix already applied to
+ `azure-keyvault-keys` and `azure-keyvault-administration` in
+ [#47742](https://github.com/Azure/azure-sdk-for-python/pull/47742).
+
## 4.11.0 (2026-03-26)
### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure_keyvault_secrets-4.11.0/api.md
new/azure_keyvault_secrets-4.11.1/api.md
--- old/azure_keyvault_secrets-4.11.0/api.md 1970-01-01 01:00:00.000000000
+0100
+++ new/azure_keyvault_secrets-4.11.1/api.md 2026-08-12 03:17:58.000000000
+0200
@@ -0,0 +1,329 @@
+```py
+namespace azure.keyvault.secrets
+
+ class azure.keyvault.secrets.ApiVersion(str, Enum,
metaclass=CaseInsensitiveEnumMeta):
+ V2016_10_01 = "2016-10-01"
+ V2025_07_01 = "2025-07-01"
+ V7_0 = "7.0"
+ V7_1 = "7.1"
+ V7_2 = "7.2"
+ V7_3 = "7.3"
+ V7_4 = "7.4"
+ V7_5 = "7.5"
+ V7_6 = "7.6"
+
+
+ class azure.keyvault.secrets.ContentType(str, Enum,
metaclass=CaseInsensitiveEnumMeta):
+ PEM = "application/x-pem-file"
+ PFX = "application/x-pkcs12"
+
+
+ class azure.keyvault.secrets.DeletedSecret:
+ property deleted_date: Optional[datetime] # Read-only
+ property id: Optional[str] # Read-only
+ property name: Optional[str] # Read-only
+ property properties: SecretProperties # Read-only
+ property recovery_id: Optional[str] # Read-only
+ property scheduled_purge_date: Optional[datetime] # Read-only
+
+ def __init__(
+ self,
+ properties: SecretProperties,
+ deleted_date: Optional[datetime] = None,
+ recovery_id: Optional[str] = None,
+ scheduled_purge_date: Optional[datetime] = None
+ ) -> None: ...
+
+ def __repr__(self) -> str: ...
+
+
+ class azure.keyvault.secrets.KeyVaultSecret:
+ property id: Optional[str] # Read-only
+ property name: Optional[str] # Read-only
+ property properties: SecretProperties # Read-only
+ property value: Optional[str] # Read-only
+
+ def __init__(
+ self,
+ properties: SecretProperties,
+ value: Optional[str]
+ ) -> None: ...
+
+ def __repr__(self) -> str: ...
+
+
+ class azure.keyvault.secrets.KeyVaultSecretIdentifier:
+ property name: str # Read-only
+ property source_id: str # Read-only
+ property vault_url: str # Read-only
+ property version: Optional[str] # Read-only
+
+ def __init__(self, source_id: str) -> None: ...
+
+
+ class azure.keyvault.secrets.SecretClient(KeyVaultClientBase): implements
ContextManager
+ property vault_url: str # Read-only
+
+ def __init__(
+ self,
+ vault_url: str,
+ credential: TokenCredential,
+ *,
+ api_version: Union[ApiVersion, str] = ...,
+ verify_challenge_resource: Optional[bool] = ...,
+ **kwargs: Any
+ ) -> None: ...
+
+ @distributed_trace
+ def backup_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> bytes: ...
+
+ @distributed_trace
+ def begin_delete_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> LROPoller[DeletedSecret]: ...
+
+ @distributed_trace
+ def begin_recover_deleted_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> LROPoller[SecretProperties]: ...
+
+ def close(self) -> None: ...
+
+ @distributed_trace
+ def get_deleted_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> DeletedSecret: ...
+
+ @distributed_trace
+ def get_secret(
+ self,
+ name: str,
+ version: Optional[str] = None,
+ *,
+ out_content_type: Optional[Union[str, ContentType]] = ...,
+ **kwargs: Any
+ ) -> KeyVaultSecret: ...
+
+ @distributed_trace
+ def list_deleted_secrets(self, **kwargs: Any) ->
ItemPaged[DeletedSecret]: ...
+
+ @distributed_trace
+ def list_properties_of_secret_versions(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> ItemPaged[SecretProperties]: ...
+
+ @distributed_trace
+ def list_properties_of_secrets(self, **kwargs: Any) ->
ItemPaged[SecretProperties]: ...
+
+ @distributed_trace
+ def purge_deleted_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> None: ...
+
+ @distributed_trace
+ def restore_secret_backup(
+ self,
+ backup: bytes,
+ **kwargs: Any
+ ) -> SecretProperties: ...
+
+ @distributed_trace
+ def send_request(
+ self,
+ request: HttpRequest,
+ *,
+ stream: bool = False,
+ **kwargs: Any
+ ) -> HttpResponse: ...
+
+ @distributed_trace
+ def set_secret(
+ self,
+ name: str,
+ value: str,
+ *,
+ content_type: Optional[str] = ...,
+ enabled: Optional[bool] = ...,
+ expires_on: Optional[datetime] = ...,
+ not_before: Optional[datetime] = ...,
+ tags: Optional[Dict[str, str]] = ...,
+ **kwargs: Any
+ ) -> KeyVaultSecret: ...
+
+ @distributed_trace
+ def update_secret_properties(
+ self,
+ name: str,
+ version: Optional[str] = None,
+ *,
+ content_type: Optional[str] = ...,
+ enabled: Optional[bool] = ...,
+ expires_on: Optional[datetime] = ...,
+ not_before: Optional[datetime] = ...,
+ tags: Optional[Dict[str, str]] = ...,
+ **kwargs: Any
+ ) -> SecretProperties: ...
+
+
+ class azure.keyvault.secrets.SecretProperties:
+ property content_type: Optional[str] # Read-only
+ property created_on: Optional[datetime] # Read-only
+ property enabled: Optional[bool] # Read-only
+ property expires_on: Optional[datetime] # Read-only
+ property id: Optional[str] # Read-only
+ property key_id: Optional[str] # Read-only
+ property managed: Optional[bool] # Read-only
+ property name: Optional[str] # Read-only
+ property not_before: Optional[datetime] # Read-only
+ property previous_version: Optional[str] # Read-only
+ property recoverable_days: Optional[int] # Read-only
+ property recovery_level: Optional[str] # Read-only
+ property tags: Optional[Dict[str, str]] # Read-only
+ property updated_on: Optional[datetime] # Read-only
+ property vault_url: Optional[str] # Read-only
+ property version: Optional[str] # Read-only
+
+ def __init__(
+ self,
+ *args: Any,
+ **kwargs: Any
+ ) -> None: ...
+
+ def __repr__(self) -> str: ...
+
+
+namespace azure.keyvault.secrets.aio
+
+ class azure.keyvault.secrets.aio.SecretClient(AsyncKeyVaultClientBase):
implements AsyncContextManager
+ property vault_url: str # Read-only
+
+ def __init__(
+ self,
+ vault_url: str,
+ credential: AsyncTokenCredential,
+ *,
+ api_version: Union[ApiVersion, str] = ...,
+ verify_challenge_resource: Optional[bool] = ...,
+ **kwargs: Any
+ ) -> None: ...
+
+ @distributed_trace_async
+ async def backup_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> bytes: ...
+
+ async def close(self) -> None: ...
+
+ @distributed_trace_async
+ async def delete_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> DeletedSecret: ...
+
+ @distributed_trace_async
+ async def get_deleted_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> DeletedSecret: ...
+
+ @distributed_trace_async
+ async def get_secret(
+ self,
+ name: str,
+ version: Optional[str] = None,
+ *,
+ out_content_type: Optional[Union[str, ContentType]] = ...,
+ **kwargs: Any
+ ) -> KeyVaultSecret: ...
+
+ @distributed_trace
+ def list_deleted_secrets(self, **kwargs: Any) ->
AsyncItemPaged[DeletedSecret]: ...
+
+ @distributed_trace
+ def list_properties_of_secret_versions(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> AsyncItemPaged[SecretProperties]: ...
+
+ @distributed_trace
+ def list_properties_of_secrets(self, **kwargs: Any) ->
AsyncItemPaged[SecretProperties]: ...
+
+ @distributed_trace_async
+ async def purge_deleted_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> None: ...
+
+ @distributed_trace_async
+ async def recover_deleted_secret(
+ self,
+ name: str,
+ **kwargs: Any
+ ) -> SecretProperties: ...
+
+ @distributed_trace_async
+ async def restore_secret_backup(
+ self,
+ backup: bytes,
+ **kwargs: Any
+ ) -> SecretProperties: ...
+
+ @distributed_trace_async
+ def send_request(
+ self,
+ request: HttpRequest,
+ *,
+ stream: bool = False,
+ **kwargs: Any
+ ) -> Awaitable[AsyncHttpResponse]: ...
+
+ @distributed_trace_async
+ async def set_secret(
+ self,
+ name: str,
+ value: str,
+ *,
+ content_type: Optional[str] = ...,
+ enabled: Optional[bool] = ...,
+ expires_on: Optional[datetime] = ...,
+ not_before: Optional[datetime] = ...,
+ tags: Optional[Dict[str, str]] = ...,
+ **kwargs: Any
+ ) -> KeyVaultSecret: ...
+
+ @distributed_trace_async
+ async def update_secret_properties(
+ self,
+ name: str,
+ version: Optional[str] = None,
+ *,
+ content_type: Optional[str] = ...,
+ enabled: Optional[bool] = ...,
+ expires_on: Optional[datetime] = ...,
+ not_before: Optional[datetime] = ...,
+ tags: Optional[Dict[str, str]] = ...,
+ **kwargs: Any
+ ) -> SecretProperties: ...
+
+
+```
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_keyvault_secrets-4.11.0/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py
new/azure_keyvault_secrets-4.11.1/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py
---
old/azure_keyvault_secrets-4.11.0/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py
2026-04-17 01:49:40.000000000 +0200
+++
new/azure_keyvault_secrets-4.11.1/azure/keyvault/secrets/_shared/async_challenge_auth_policy.py
2026-08-12 03:17:58.000000000 +0200
@@ -30,7 +30,7 @@
from .http_challenge import HttpChallenge
from . import http_challenge_cache as ChallengeCache
-from .challenge_auth_policy import _enforce_tls, _has_claims, _update_challenge
+from .challenge_auth_policy import _enforce_tls, _has_claims,
_update_challenge, _REQUEST_COPY_KEY
if sys.version_info < (3, 9):
from typing import Awaitable
@@ -81,7 +81,6 @@
self._credential: AsyncTokenProvider = credential
self._token: Optional[Union["AccessToken", "AccessTokenInfo"]] = None
self._verify_challenge_resource =
kwargs.pop("verify_challenge_resource", True)
- self._request_copy: Optional[HttpRequest] = None
async def send(
self, request: PipelineRequest[HttpRequest]
@@ -175,8 +174,10 @@
# saving it for later. Key Vault will reject the request as
unauthorized and respond with a challenge.
# on_challenge will parse that challenge, use the original request
including the body, authorize the
# request, and tell super to send it again.
- if request.http_request.content:
- self._request_copy = request.http_request
+ # The original request is stashed on the request's context
(per-request), so it cannot leak into a later
+ # request made by the same client. Don't overwrite a previously
stashed copy if this is a retry.
+ if request.http_request.content and _REQUEST_COPY_KEY not in
request.context:
+ request.context[_REQUEST_COPY_KEY] = request.http_request
bodiless_request = HttpRequest(
method=request.http_request.method,
url=request.http_request.url,
@@ -218,9 +219,10 @@
"See https://aka.ms/azsdk/blog/vault-uri for more
information."
)
- # If we had created a request copy in on_request, use it now to send
along the original body content
- if self._request_copy:
- request.http_request = self._request_copy
+ # If we stashed the original request in on_request, use it now to send
along the original body content
+ request_copy = request.context.get(_REQUEST_COPY_KEY)
+ if request_copy:
+ request.http_request = request_copy
# The tenant parsed from AD FS challenges is "adfs"; we don't actually
need a tenant for AD FS authentication
# For AD FS we skip cross-tenant authentication per
https://github.com/Azure/azure-sdk-for-python/issues/28648
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_keyvault_secrets-4.11.0/azure/keyvault/secrets/_shared/challenge_auth_policy.py
new/azure_keyvault_secrets-4.11.1/azure/keyvault/secrets/_shared/challenge_auth_policy.py
---
old/azure_keyvault_secrets-4.11.0/azure/keyvault/secrets/_shared/challenge_auth_policy.py
2026-04-17 01:49:40.000000000 +0200
+++
new/azure_keyvault_secrets-4.11.1/azure/keyvault/secrets/_shared/challenge_auth_policy.py
2026-08-12 03:17:58.000000000 +0200
@@ -36,6 +36,12 @@
from . import http_challenge_cache as ChallengeCache
+# Key under which the original request is stashed on the per-request pipeline
context during the challenge flow.
+# Storing this per-request (rather than on the policy instance) prevents the
body of one request from leaking into a
+# subsequent request made by the same client.
+_REQUEST_COPY_KEY = "key_vault_request_copy"
+
+
def _enforce_tls(request: PipelineRequest) -> None:
if not request.http_request.url.lower().startswith("https"):
raise ServiceRequestError(
@@ -93,7 +99,6 @@
self._credential: TokenProvider = credential
self._token: Optional[Union["AccessToken", "AccessTokenInfo"]] = None
self._verify_challenge_resource =
kwargs.pop("verify_challenge_resource", True)
- self._request_copy: Optional[HttpRequest] = None
def send(self, request: PipelineRequest[HttpRequest]) ->
PipelineResponse[HttpRequest, HttpResponse]:
"""Authorize request with a bearer token and send it to the next
policy.
@@ -184,8 +189,10 @@
# saving it for later. Key Vault will reject the request as
unauthorized and respond with a challenge.
# on_challenge will parse that challenge, use the original request
including the body, authorize the
# request, and tell super to send it again.
- if request.http_request.content:
- self._request_copy = request.http_request
+ # The original request is stashed on the request's context
(per-request), so it cannot leak into a later
+ # request made by the same client. Don't overwrite a previously
stashed copy if this is a retry.
+ if request.http_request.content and _REQUEST_COPY_KEY not in
request.context:
+ request.context[_REQUEST_COPY_KEY] = request.http_request
bodiless_request = HttpRequest(
method=request.http_request.method,
url=request.http_request.url,
@@ -227,9 +234,10 @@
"See https://aka.ms/azsdk/blog/vault-uri for more
information."
)
- # If we had created a request copy in on_request, use it now to send
along the original body content
- if self._request_copy:
- request.http_request = self._request_copy
+ # If we stashed the original request in on_request, use it now to send
along the original body content
+ request_copy = request.context.get(_REQUEST_COPY_KEY)
+ if request_copy:
+ request.http_request = request_copy
# The tenant parsed from AD FS challenges is "adfs"; we don't actually
need a tenant for AD FS authentication
# For AD FS we skip cross-tenant authentication per
https://github.com/Azure/azure-sdk-for-python/issues/28648
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_keyvault_secrets-4.11.0/azure/keyvault/secrets/_version.py
new/azure_keyvault_secrets-4.11.1/azure/keyvault/secrets/_version.py
--- old/azure_keyvault_secrets-4.11.0/azure/keyvault/secrets/_version.py
2026-04-17 01:49:40.000000000 +0200
+++ new/azure_keyvault_secrets-4.11.1/azure/keyvault/secrets/_version.py
2026-08-12 03:17:58.000000000 +0200
@@ -3,4 +3,4 @@
# Licensed under the MIT License.
# ------------------------------------
-VERSION = "4.11.0"
+VERSION = "4.11.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_keyvault_secrets-4.11.0/azure_keyvault_secrets.egg-info/PKG-INFO
new/azure_keyvault_secrets-4.11.1/azure_keyvault_secrets.egg-info/PKG-INFO
--- old/azure_keyvault_secrets-4.11.0/azure_keyvault_secrets.egg-info/PKG-INFO
2026-04-17 01:50:26.000000000 +0200
+++ new/azure_keyvault_secrets-4.11.1/azure_keyvault_secrets.egg-info/PKG-INFO
2026-08-12 03:19:09.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: azure-keyvault-secrets
-Version: 4.11.0
+Version: 4.11.1
Summary: Microsoft Corporation Key Vault Secrets Client Library for Python
Author-email: Microsoft Corporation <[email protected]>
License: MIT License
@@ -406,6 +406,17 @@
# Release History
+## 4.11.1 (2026-08-12)
+
+### Bugs Fixed
+
+- Fixed a replay bug in the challenge authentication policy where a request
copy stashed on the shared policy
+ instance was never cleared, allowing one request's method, URL, and body to
leak into a later, unrelated request
+ made by the same client (for example, after an Entra ID Continuous Access
Evaluation challenge). The original
+ request is now stored per-request instead of on the policy instance. This
mirrors the fix already applied to
+ `azure-keyvault-keys` and `azure-keyvault-administration` in
+ [#47742](https://github.com/Azure/azure-sdk-for-python/pull/47742).
+
## 4.11.0 (2026-03-26)
### Features Added
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_keyvault_secrets-4.11.0/azure_keyvault_secrets.egg-info/SOURCES.txt
new/azure_keyvault_secrets-4.11.1/azure_keyvault_secrets.egg-info/SOURCES.txt
---
old/azure_keyvault_secrets-4.11.0/azure_keyvault_secrets.egg-info/SOURCES.txt
2026-04-17 01:50:27.000000000 +0200
+++
new/azure_keyvault_secrets-4.11.1/azure_keyvault_secrets.egg-info/SOURCES.txt
2026-08-12 03:19:09.000000000 +0200
@@ -3,6 +3,7 @@
MANIFEST.in
README.md
TROUBLESHOOTING.md
+api.md
migration_guide.md
pyproject.toml
azure/__init__.py
@@ -66,6 +67,8 @@
tests/_async_test_case.py
tests/_test_case.py
tests/conftest.py
+tests/test_challenge_auth.py
+tests/test_challenge_auth_async.py
tests/test_context_manager.py
tests/test_context_manager_async.py
tests/test_multi_api.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_keyvault_secrets-4.11.0/tests/test_challenge_auth.py
new/azure_keyvault_secrets-4.11.1/tests/test_challenge_auth.py
--- old/azure_keyvault_secrets-4.11.0/tests/test_challenge_auth.py
1970-01-01 01:00:00.000000000 +0100
+++ new/azure_keyvault_secrets-4.11.1/tests/test_challenge_auth.py
2026-08-12 03:17:58.000000000 +0200
@@ -0,0 +1,106 @@
+# ------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------
+"""
+Tests for the HTTP challenge authentication implementation. These tests aren't
parallelizable, because
+the challenge cache is global to the process.
+"""
+
+import functools
+import time
+from unittest.mock import Mock
+from uuid import uuid4
+
+import pytest
+from azure.core.credentials import AccessToken, AccessTokenInfo
+from azure.core.pipeline import Pipeline
+from azure.core.rest import HttpRequest
+from azure.keyvault.secrets._shared import ChallengeAuthPolicy,
HttpChallengeCache
+
+TOKEN_TYPES = [AccessToken, AccessTokenInfo]
+
+
+def empty_challenge_cache(fn):
+ @functools.wraps(fn)
+ def wrapper(**kwargs):
+ HttpChallengeCache.clear()
+ assert len(HttpChallengeCache._cache) == 0
+ return fn(**kwargs)
+
+ return wrapper
+
+
+def get_random_url():
+ """The challenge cache is keyed on URLs. Random URLs defend against tests
interfering with each other."""
+
+ return f"https://{uuid4()}.vault.azure.net/{uuid4()}".replace("-", "")
+
+
+@empty_challenge_cache
[email protected]("token_type", TOKEN_TYPES)
+def test_request_body_not_reused_across_requests(token_type):
+ """A request's body must not leak into a later request made by the same
client.
+
+ Regression test for the replay bug: the original request used to be
stashed on the policy instance and was
+ never cleared, so a subsequent bodiless request (e.g. a polling GET) that
triggered its own challenge would
+ have the earlier request's body (and method/URL) replayed onto it. The
copy is now stored per-request on the
+ pipeline context, so it cannot leak across requests. See
+ https://github.com/Azure/azure-sdk-for-python/pull/47742.
+ """
+
+ expected_token = "expected_token"
+ first_content = b"a duck"
+ first_url = get_random_url()
+ second_url = get_random_url()
+ challenge = Mock(
+ status_code=401,
+ headers={"WWW-Authenticate": 'Bearer
authorization="https://authority.net/tenant",
resource=https://vault.azure.net'},
+ )
+
+ class Requests:
+ count = 0
+
+ def send(request):
+ Requests.count += 1
+ if Requests.count == 1:
+ # first request (POST with body): the body is stripped to elicit a
challenge
+ assert not request.body
+ assert request.headers["Content-Length"] == "0"
+ return challenge
+ elif Requests.count == 2:
+ # first request is retried with its original body and authorization
+ assert request.body == first_content
+ assert expected_token in request.headers["Authorization"]
+ return Mock(status_code=200)
+ elif Requests.count == 3:
+ # second request (bodiless GET): elicits its own challenge and
must have no body
+ assert not request.body
+ return challenge
+ elif Requests.count == 4:
+ # second request is retried: it must remain a bodiless GET, i.e.
the first request's body and
+ # method/URL must NOT be replayed onto it
+ assert not request.body
+ assert request.method == "GET"
+ assert request.url == second_url
+ assert expected_token in request.headers["Authorization"]
+ return Mock(status_code=200)
+ raise ValueError("unexpected request")
+
+ def get_token(*_, **__):
+ return token_type(expected_token, time.time() + 3600)
+
+ if token_type == AccessToken:
+ credential = Mock(spec_set=["get_token"],
get_token=Mock(wraps=get_token))
+ else:
+ credential = Mock(spec_set=["get_token_info"],
get_token_info=Mock(wraps=get_token))
+
+ # a single policy instance handles both requests; the fix prevents state
from one request leaking into the next
+ policy = ChallengeAuthPolicy(credential=credential)
+ pipeline = Pipeline(policies=[policy], transport=Mock(send=send))
+
+ first_request = HttpRequest("POST", first_url)
+ first_request.set_bytes_body(first_content)
+ pipeline.run(first_request)
+
+ pipeline.run(HttpRequest("GET", second_url))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure_keyvault_secrets-4.11.0/tests/test_challenge_auth_async.py
new/azure_keyvault_secrets-4.11.1/tests/test_challenge_auth_async.py
--- old/azure_keyvault_secrets-4.11.0/tests/test_challenge_auth_async.py
1970-01-01 01:00:00.000000000 +0100
+++ new/azure_keyvault_secrets-4.11.1/tests/test_challenge_auth_async.py
2026-08-12 03:17:58.000000000 +0200
@@ -0,0 +1,100 @@
+# ------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------
+"""
+Tests for the HTTP challenge authentication implementation. These tests aren't
parallelizable, because
+the challenge cache is global to the process.
+"""
+
+import functools
+import time
+from unittest.mock import Mock
+
+import pytest
+from azure.core.credentials import AccessToken, AccessTokenInfo
+from azure.core.pipeline import AsyncPipeline
+from azure.core.rest import HttpRequest
+from azure.keyvault.secrets._shared import AsyncChallengeAuthPolicy,
HttpChallengeCache
+
+from test_challenge_auth import get_random_url, TOKEN_TYPES
+
+
+def empty_challenge_cache(fn):
+ @functools.wraps(fn)
+ async def wrapper(**kwargs):
+ HttpChallengeCache.clear()
+ assert len(HttpChallengeCache._cache) == 0
+ return await fn(**kwargs)
+
+ return wrapper
+
+
[email protected]
+@empty_challenge_cache
[email protected]("token_type", TOKEN_TYPES)
+async def test_request_body_not_reused_across_requests(token_type):
+ """A request's body must not leak into a later request made by the same
client.
+
+ Regression test for the replay bug: the original request used to be
stashed on the policy instance and was
+ never cleared, so a subsequent bodiless request (e.g. a polling GET) that
triggered its own challenge would
+ have the earlier request's body (and method/URL) replayed onto it. The
copy is now stored per-request on the
+ pipeline context, so it cannot leak across requests. See
+ https://github.com/Azure/azure-sdk-for-python/pull/47742.
+ """
+
+ expected_token = "expected_token"
+ first_content = b"a duck"
+ first_url = get_random_url()
+ second_url = get_random_url()
+ challenge = Mock(
+ status_code=401,
+ headers={"WWW-Authenticate": 'Bearer
authorization="https://authority.net/tenant",
resource=https://vault.azure.net'},
+ )
+
+ class Requests:
+ count = 0
+
+ async def send(request):
+ Requests.count += 1
+ if Requests.count == 1:
+ # first request (POST with body): the body is stripped to elicit a
challenge
+ assert not request.body
+ assert request.headers["Content-Length"] == "0"
+ return challenge
+ elif Requests.count == 2:
+ # first request is retried with its original body and authorization
+ assert request.body == first_content
+ assert expected_token in request.headers["Authorization"]
+ return Mock(status_code=200)
+ elif Requests.count == 3:
+ # second request (bodiless GET): elicits its own challenge and
must have no body
+ assert not request.body
+ return challenge
+ elif Requests.count == 4:
+ # second request is retried: it must remain a bodiless GET, i.e.
the first request's body and
+ # method/URL must NOT be replayed onto it
+ assert not request.body
+ assert request.method == "GET"
+ assert request.url == second_url
+ assert expected_token in request.headers["Authorization"]
+ return Mock(status_code=200)
+ raise ValueError("unexpected request")
+
+ async def get_token(*_, **__):
+ return token_type(expected_token, time.time() + 3600)
+
+ if token_type == AccessToken:
+ credential = Mock(spec_set=["get_token"],
get_token=Mock(wraps=get_token))
+ else:
+ credential = Mock(spec_set=["get_token_info"],
get_token_info=Mock(wraps=get_token))
+
+ # a single policy instance handles both requests; the fix prevents state
from one request leaking into the next
+ policy = AsyncChallengeAuthPolicy(credential=credential)
+ pipeline = AsyncPipeline(policies=[policy], transport=Mock(send=send))
+
+ first_request = HttpRequest("POST", first_url)
+ first_request.set_bytes_body(first_content)
+ await pipeline.run(first_request)
+
+ await pipeline.run(HttpRequest("GET", second_url))