Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 67f2e45

Browse files
committed
[Identity] SharedTokenCredential in DAC update
Signed-off-by: Paul Van Eck <[email protected]>
1 parent b928db1 commit 67f2e45

File tree

5 files changed

+94
-8
lines changed

5 files changed

+94
-8
lines changed

sdk/identity/azure-identity/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
### Other Changes
1818

19+
- Updated `SharedTokenCacheCredential` to raise `CredentialUnavailableError` instead of `ClientAuthenticationError` during token refresh failures when within the context of `DefaultAzureCredential`. ([#42934](https://github.com/Azure/azure-sdk-for-python/pull/42934))
20+
21+
1922
## 1.24.0 (2025-08-07)
2023

2124
### Bugs Fixed

sdk/identity/azure-identity/azure/identity/_credentials/shared_cache.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
# ------------------------------------
55
from typing import Any, Optional, TypeVar, cast
66
from azure.core.credentials import AccessToken, TokenRequestOptions, AccessTokenInfo, SupportsTokenInfo, TokenCredential
7+
from azure.core.exceptions import ClientAuthenticationError
78

89
from .silent import SilentAuthenticationCredential
910
from .. import CredentialUnavailableError
1011
from .._constants import DEVELOPER_SIGN_ON_CLIENT_ID
11-
from .._internal import AadClient, AadClientBase
12+
from .._internal import AadClient, AadClientBase, within_dac
1213
from .._internal.decorators import log_get_token
1314
from .._internal.shared_token_cache import NO_TOKEN, SharedTokenCacheBase
1415

@@ -191,9 +192,16 @@ def _get_token_base(
191192

192193
# try each refresh token, returning the first access token acquired
193194
for refresh_token in self._get_refresh_tokens(account, is_cae=is_cae):
194-
token = cast(AadClient, self._client).obtain_token_by_refresh_token(
195-
scopes, refresh_token, claims=claims, tenant_id=tenant_id, enable_cae=is_cae, **kwargs
196-
)
195+
try:
196+
token = cast(AadClient, self._client).obtain_token_by_refresh_token(
197+
scopes, refresh_token, claims=claims, tenant_id=tenant_id, enable_cae=is_cae, **kwargs
198+
)
199+
except ClientAuthenticationError as e:
200+
if within_dac.get():
201+
raise CredentialUnavailableError( # pylint: disable=raise-missing-from
202+
message=e.message, response=e.response
203+
)
204+
raise
197205
return token
198206

199207
raise CredentialUnavailableError(message=NO_TOKEN.format(account.get("username")))

sdk/identity/azure-identity/azure/identity/aio/_credentials/shared_cache.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
# ------------------------------------
55
from typing import Any, Optional, cast
66
from azure.core.credentials import AccessToken, AccessTokenInfo, TokenRequestOptions
7+
from azure.core.exceptions import ClientAuthenticationError
78
from ..._internal.aad_client import AadClientBase
89
from ... import CredentialUnavailableError
910
from ..._constants import DEVELOPER_SIGN_ON_CLIENT_ID
1011
from ..._internal.shared_token_cache import NO_TOKEN, SharedTokenCacheBase
12+
from ..._internal.utils import within_dac
1113
from .._internal import AsyncContextManager
1214
from .._internal.aad_client import AadClient
1315
from .._internal.decorators import log_get_token_async
@@ -143,10 +145,17 @@ async def _get_token_base(
143145

144146
# try each refresh token, returning the first access token acquired
145147
for refresh_token in self._get_refresh_tokens(account, is_cae=is_cae):
146-
token = await cast(AadClient, self._client).obtain_token_by_refresh_token(
147-
scopes, refresh_token, claims=claims, tenant_id=tenant_id, enable_cae=is_cae, **kwargs
148-
)
149-
return token
148+
try:
149+
token = await cast(AadClient, self._client).obtain_token_by_refresh_token(
150+
scopes, refresh_token, claims=claims, tenant_id=tenant_id, enable_cae=is_cae, **kwargs
151+
)
152+
return token
153+
except ClientAuthenticationError as e:
154+
if within_dac.get():
155+
raise CredentialUnavailableError( # pylint: disable=raise-missing-from
156+
message=e.message, response=e.response
157+
)
158+
raise
150159

151160
raise CredentialUnavailableError(message=NO_TOKEN.format(account.get("username")))
152161

sdk/identity/azure-identity/tests/test_shared_cache_credential.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,36 @@ def send(request, **kwargs):
917917
within_dac.set(False)
918918

919919

920+
@pytest.mark.parametrize("get_token_method", GET_TOKEN_METHODS)
921+
def test_within_dac_refresh_token_error(get_token_method):
922+
"""When within DAC context and refresh token fails, should raise CredentialUnavailableError"""
923+
924+
925+
refresh_token = "invalid-refresh-token"
926+
scope = "scope"
927+
account = get_account_event(uid="uid_a", utid="utid", username=upn, refresh_token=refresh_token)
928+
cache = populated_cache(account)
929+
930+
def send(request, **kwargs):
931+
# Mock a token request that fails with invalid_grant (401/400 error)
932+
if "refresh_token" in (request.data or {}):
933+
return mock_response(
934+
status_code=400, json_payload={"error": "invalid_grant", "error_description": "Refresh token expired"}
935+
)
936+
# Allow discovery requests to succeed
937+
return get_discovery_response("https://localhost/tenant")
938+
939+
transport = Mock(send=send)
940+
credential = SharedTokenCacheCredential(_cache=cache, transport=transport, username=upn)
941+
942+
within_dac.set(True)
943+
try:
944+
with pytest.raises(CredentialUnavailableError):
945+
getattr(credential, get_token_method)(scope)
946+
finally:
947+
within_dac.set(False)
948+
949+
920950
@pytest.mark.parametrize("get_token_method", GET_TOKEN_METHODS)
921951
def test_claims_challenge(get_token_method):
922952
"""get_token should pass any claims challenge to MSAL token acquisition APIs"""

sdk/identity/azure-identity/tests/test_shared_cache_credential_async.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
NO_ACCOUNTS,
1818
NO_MATCHING_ACCOUNTS,
1919
)
20+
from azure.identity._internal import within_dac
2021
from azure.identity._internal.user_agent import USER_AGENT
2122
from msal import TokenCache
2223
import pytest
@@ -390,6 +391,41 @@ async def test_no_refresh_token(get_token_method):
390391
await getattr(credential, get_token_method)("scope")
391392

392393

394+
@pytest.mark.asyncio
395+
@pytest.mark.parametrize("get_token_method", GET_TOKEN_METHODS)
396+
async def test_within_dac_refresh_token_error(get_token_method):
397+
"""When within DAC context and refresh token fails, should raise CredentialUnavailableError"""
398+
399+
400+
refresh_token = "invalid-refresh-token"
401+
scope = "scope"
402+
account = get_account_event(uid="uid_a", utid="utid", username=upn, refresh_token=refresh_token)
403+
cache = populated_cache(account)
404+
405+
async def send(request, **kwargs):
406+
# Mock a token request that fails with invalid_grant (401/400 error)
407+
if "refresh_token" in (request.data or {}):
408+
return mock_response(
409+
status_code=400, json_payload={"error": "invalid_grant", "error_description": "Refresh token expired"}
410+
)
411+
# Allow discovery requests to succeed
412+
from helpers import get_discovery_response
413+
414+
return get_discovery_response("https://localhost/tenant")
415+
416+
transport = AsyncMockTransport(send=send)
417+
credential = SharedTokenCacheCredential(_cache=cache, transport=transport, username=upn)
418+
419+
# Set within_dac context to True
420+
within_dac.set(True)
421+
try:
422+
# When within DAC context, should raise CredentialUnavailableError instead of ClientAuthenticationError
423+
with pytest.raises(CredentialUnavailableError):
424+
await getattr(credential, get_token_method)(scope)
425+
finally:
426+
within_dac.set(False)
427+
428+
393429
@pytest.mark.asyncio
394430
@pytest.mark.parametrize("get_token_method", GET_TOKEN_METHODS)
395431
async def test_two_accounts_no_username_or_tenant(get_token_method):

0 commit comments

Comments
 (0)