Skip to content

Commit 0464b2a

Browse files
authored
[Identity] Correctly implement TokenCredential protocols (#31047)
1 parent 85856b2 commit 0464b2a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+526
-187
lines changed

sdk/identity/azure-identity/CHANGELOG.md

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

1515
### Bugs Fixed
1616

17+
- Credential types correctly implement `azure-core`'s `TokenCredential` protocol.
18+
([#25175](https://github.com/Azure/azure-sdk-for-python/issues/25175))
19+
1720
### Other Changes
1821

1922
## 1.14.0b2 (2023-07-11)

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# ------------------------------------
55
import logging
66
import os
7-
from typing import Any
7+
from typing import Any, Optional
88

99
from azure.core.credentials import AccessToken
1010
from .chained import ChainedTokenCredential
@@ -63,24 +63,30 @@ def __init__(self, **kwargs: Any) -> None:
6363
ManagedIdentityCredential(client_id=managed_identity_client_id, **kwargs),
6464
)
6565

66-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
66+
def get_token(
67+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
68+
) -> AccessToken:
6769
"""Request an access token for `scopes`.
6870
6971
This method is called automatically by Azure SDK clients.
7072
7173
:param str scopes: desired scopes for the access token. This method requires at least one scope.
7274
For more information about scopes, see
7375
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
76+
:keyword str claims: additional claims required in the token, such as those returned in a resource provider's
77+
claims challenge following an authorization failure.
78+
:keyword str tenant_id: optional tenant to include in the token request.
79+
7480
:return: An access token with the desired scopes.
7581
:rtype: ~azure.core.credentials.AccessToken
7682
:raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The exception has a
7783
`message` attribute listing each authentication attempt and its error message.
7884
"""
7985
if self._successful_credential:
80-
token = self._successful_credential.get_token(*scopes, **kwargs)
86+
token = self._successful_credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
8187
_LOGGER.info(
8288
"%s acquired a token from %s", self.__class__.__name__, self._successful_credential.__class__.__name__
8389
)
8490
return token
8591

86-
return super(AzureApplicationCredential, self).get_token(*scopes, **kwargs)
92+
return super(AzureApplicationCredential, self).get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ def close(self) -> None:
6161
"""Close the credential's transport session."""
6262
self.__exit__()
6363

64-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
64+
def get_token(
65+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
66+
) -> AccessToken:
6567
"""Request an access token for `scopes`.
6668
6769
This method is called automatically by Azure SDK clients.
@@ -73,6 +75,8 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
7375
:param str scopes: desired scopes for the access token. This method requires at least one scope.
7476
For more information about scopes, see
7577
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
78+
:keyword str claims: additional claims required in the token, such as those returned in a resource provider's
79+
claims challenge following an authorization failure.
7680
:keyword str tenant_id: optional tenant to include in the token request.
7781
7882
:return: An access token with the desired scopes.
@@ -82,7 +86,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
8286
``response`` attribute.
8387
"""
8488
# pylint:disable=useless-super-delegation
85-
return super(AuthorizationCodeCredential, self).get_token(*scopes, **kwargs)
89+
return super(AuthorizationCodeCredential, self).get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
8690

8791
def _acquire_token_silently(self, *scopes: str, **kwargs) -> Optional[AccessToken]:
8892
return self._client.get_cached_access_token(scopes, **kwargs)

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,13 @@ def close(self) -> None:
9191
"""Calling this method is unnecessary."""
9292

9393
@log_get_token("AzureDeveloperCliCredential")
94-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
94+
def get_token(
95+
self,
96+
*scopes: str,
97+
claims: Optional[str] = None, # pylint:disable=unused-argument
98+
tenant_id: Optional[str] = None,
99+
**kwargs: Any,
100+
) -> AccessToken:
95101
"""Request an access token for `scopes`.
96102
97103
This method is called automatically by Azure SDK clients. Applications calling this method directly must
@@ -100,6 +106,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
100106
:param str scopes: desired scope for the access token. This credential allows only one scope per request.
101107
For more information about scopes, see
102108
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
109+
:keyword str claims: not used by this credential; any value provided will be ignored.
103110
:keyword str tenant_id: optional tenant to include in the token request.
104111
105112
:return: An access token with the desired scopes.
@@ -117,7 +124,10 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
117124
commandString = " --scope ".join(scopes)
118125
command = COMMAND_LINE.format(commandString)
119126
tenant = resolve_tenant(
120-
default_tenant=self.tenant_id, additionally_allowed_tenants=self._additionally_allowed_tenants, **kwargs
127+
default_tenant=self.tenant_id,
128+
tenant_id=tenant_id,
129+
additionally_allowed_tenants=self._additionally_allowed_tenants,
130+
**kwargs,
121131
)
122132
if tenant:
123133
command += " --tenant-id " + tenant

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ def close(self) -> None:
6969
"""Calling this method is unnecessary."""
7070

7171
@log_get_token("AzureCliCredential")
72-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
72+
def get_token(
73+
self,
74+
*scopes: str,
75+
claims: Optional[str] = None, # pylint:disable=unused-argument
76+
tenant_id: Optional[str] = None,
77+
**kwargs: Any,
78+
) -> AccessToken:
7379
"""Request an access token for `scopes`.
7480
7581
This method is called automatically by Azure SDK clients. Applications calling this method directly must
@@ -78,6 +84,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
7884
:param str scopes: desired scope for the access token. This credential allows only one scope per request.
7985
For more information about scopes, see
8086
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
87+
:keyword str claims: not used by this credential; any value provided will be ignored.
8188
:keyword str tenant_id: optional tenant to include in the token request.
8289
8390
:return: An access token with the desired scopes.
@@ -91,7 +98,10 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
9198
resource = _scopes_to_resource(*scopes)
9299
command = COMMAND_LINE.format(resource)
93100
tenant = resolve_tenant(
94-
default_tenant=self.tenant_id, additionally_allowed_tenants=self._additionally_allowed_tenants, **kwargs
101+
default_tenant=self.tenant_id,
102+
tenant_id=tenant_id,
103+
additionally_allowed_tenants=self._additionally_allowed_tenants,
104+
**kwargs,
95105
)
96106
if tenant:
97107
command += " --tenant " + tenant

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import logging
77
import subprocess
88
import sys
9-
from typing import List, Tuple, Optional, Any
9+
from typing import Any, List, Tuple, Optional
1010

1111
from azure.core.credentials import AccessToken
1212
from azure.core.exceptions import ClientAuthenticationError
@@ -83,7 +83,13 @@ def close(self) -> None:
8383
"""Calling this method is unnecessary."""
8484

8585
@log_get_token("AzurePowerShellCredential")
86-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
86+
def get_token(
87+
self,
88+
*scopes: str,
89+
claims: Optional[str] = None, # pylint:disable=unused-argument
90+
tenant_id: Optional[str] = None,
91+
**kwargs: Any,
92+
) -> AccessToken:
8793
"""Request an access token for `scopes`.
8894
8995
This method is called automatically by Azure SDK clients. Applications calling this method directly must
@@ -92,6 +98,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
9298
:param str scopes: desired scope for the access token. This credential allows only one scope per request.
9399
For more information about scopes, see
94100
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
101+
:keyword str claims: not used by this credential; any value provided will be ignored.
95102
:keyword str tenant_id: optional tenant to include in the token request.
96103
97104
:return: An access token with the desired scopes.
@@ -103,7 +110,10 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
103110
receive an access token
104111
"""
105112
tenant_id = resolve_tenant(
106-
default_tenant=self.tenant_id, additionally_allowed_tenants=self._additionally_allowed_tenants, **kwargs
113+
default_tenant=self.tenant_id,
114+
tenant_id=tenant_id,
115+
additionally_allowed_tenants=self._additionally_allowed_tenants,
116+
**kwargs,
107117
)
108118
command_line = get_command_line(scopes, tenant_id)
109119
output = run_command_line(command_line, self._process_timeout)

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,19 @@ def close(self) -> None:
6969
"""Close the transport session of each credential in the chain."""
7070
self.__exit__()
7171

72-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: # pylint:disable=unused-argument
72+
def get_token(
73+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
74+
) -> AccessToken:
7375
"""Request a token from each chained credential, in order, returning the first token received.
7476
7577
This method is called automatically by Azure SDK clients.
7678
7779
:param str scopes: desired scopes for the access token. This method requires at least one scope.
7880
For more information about scopes, see
7981
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
82+
:keyword str claims: additional claims required in the token, such as those returned in a resource provider's
83+
claims challenge following an authorization failure.
84+
:keyword str tenant_id: optional tenant to include in the token request.
8085
8186
:return: An access token with the desired scopes.
8287
:rtype: ~azure.core.credentials.AccessToken
@@ -86,7 +91,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: # pylint:disab
8691
history = []
8792
for credential in self.credentials:
8893
try:
89-
token = credential.get_token(*scopes, **kwargs)
94+
token = credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
9095
_LOGGER.info("%s acquired a token from %s", self.__class__.__name__, credential.__class__.__name__)
9196
self._successful_credential = credential
9297
return token

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# ------------------------------------
55
import logging
66
import os
7-
from typing import List, TYPE_CHECKING, Any, cast
7+
from typing import List, TYPE_CHECKING, Any, Optional, cast
88

99
from azure.core.credentials import AccessToken
1010
from .._constants import EnvironmentVariables
@@ -195,14 +195,18 @@ def __init__(self, **kwargs: Any) -> None: # pylint: disable=too-many-statement
195195

196196
super(DefaultAzureCredential, self).__init__(*credentials)
197197

198-
def get_token(self, *scopes: str, **kwargs) -> AccessToken:
198+
def get_token(
199+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
200+
) -> AccessToken:
199201
"""Request an access token for `scopes`.
200202
201203
This method is called automatically by Azure SDK clients.
202204
203205
:param str scopes: desired scopes for the access token. This method requires at least one scope.
204206
For more information about scopes, see
205207
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
208+
:keyword str claims: additional claims required in the token, such as those returned in a resource provider's
209+
claims challenge following an authorization failure.
206210
:keyword str tenant_id: optional tenant to include in the token request.
207211
208212
:return: An access token with the desired scopes.
@@ -212,12 +216,12 @@ def get_token(self, *scopes: str, **kwargs) -> AccessToken:
212216
`message` attribute listing each authentication attempt and its error message.
213217
"""
214218
if self._successful_credential:
215-
token = self._successful_credential.get_token(*scopes, **kwargs)
219+
token = self._successful_credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
216220
_LOGGER.info(
217221
"%s acquired a token from %s", self.__class__.__name__, self._successful_credential.__class__.__name__
218222
)
219223
return token
220224
within_dac.set(True)
221-
token = super(DefaultAzureCredential, self).get_token(*scopes, **kwargs)
225+
token = super().get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
222226
within_dac.set(False)
223227
return token

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,18 @@ def close(self) -> None:
120120
self.__exit__()
121121

122122
@log_get_token("EnvironmentCredential")
123-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
123+
def get_token(
124+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
125+
) -> AccessToken:
124126
"""Request an access token for `scopes`.
125127
126128
This method is called automatically by Azure SDK clients.
127129
128130
:param str scopes: desired scopes for the access token. This method requires at least one scope.
129131
For more information about scopes, see
130132
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
133+
:keyword str claims: additional claims required in the token, such as those returned in a resource provider's
134+
claims challenge following an authorization failure.
131135
:keyword str tenant_id: optional tenant to include in the token request.
132136
133137
:return: An access token with the desired scopes.
@@ -142,4 +146,4 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
142146
"this issue."
143147
)
144148
raise CredentialUnavailableError(message=message)
145-
return self._credential.get_token(*scopes, **kwargs)
149+
return self._credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ def close(self) -> None:
108108
self.__exit__()
109109

110110
@log_get_token("ManagedIdentityCredential")
111-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
111+
def get_token(
112+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
113+
) -> AccessToken:
112114
"""Request an access token for `scopes`.
113115
114116
This method is called automatically by Azure SDK clients.
@@ -117,6 +119,9 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
117119
For more information about scopes, see
118120
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
119121
122+
:keyword str claims: not used by this credential; any value provided will be ignored.
123+
:keyword str tenant_id: not used by this credential; any value provided will be ignored.
124+
120125
:return: An access token with the desired scopes.
121126
:rtype: ~azure.core.credentials.AccessToken
122127
:raises ~azure.identity.CredentialUnavailableError: managed identity isn't available in the hosting environment
@@ -129,4 +134,4 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
129134
"Visit https://aka.ms/azsdk/python/identity/managedidentitycredential/troubleshoot to "
130135
"troubleshoot this issue."
131136
)
132-
return self._credential.get_token(*scopes, **kwargs)
137+
return self._credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ def close(self) -> None:
5252
self.__exit__()
5353

5454
@log_get_token("SharedTokenCacheCredential")
55-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
55+
def get_token(
56+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
57+
) -> AccessToken:
5658
"""Get an access token for `scopes` from the shared cache.
5759
5860
If no access token is cached, attempt to acquire one using a cached refresh token.
@@ -64,16 +66,18 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
6466
https://learn.microsoft.com/azure/active-directory/develop/scopes-oidc.
6567
:keyword str claims: additional claims required in the token, such as those returned in a resource provider's
6668
claims challenge following an authorization failure
69+
:keyword str tenant_id: not used by this credential; any value provided will be ignored.
6770
:keyword bool enable_cae: indicates whether to enable Continuous Access Evaluation (CAE) for the requested
6871
token. Defaults to False.
72+
6973
:return: An access token with the desired scopes.
7074
:rtype: ~azure.core.credentials.AccessToken
7175
:raises ~azure.identity.CredentialUnavailableError: the cache is unavailable or contains insufficient user
7276
information
7377
:raises ~azure.core.exceptions.ClientAuthenticationError: authentication failed. The error's ``message``
7478
attribute gives a reason.
7579
"""
76-
return self._credential.get_token(*scopes, **kwargs)
80+
return self._credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
7781

7882
@staticmethod
7983
def supported() -> bool:
@@ -97,7 +101,9 @@ def __exit__(self, *args):
97101
if self._client:
98102
self._client.__exit__(*args)
99103

100-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
104+
def get_token(
105+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
106+
) -> AccessToken:
101107
if not scopes:
102108
raise ValueError("'get_token' requires at least one scope")
103109

@@ -123,7 +129,9 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
123129

124130
# try each refresh token, returning the first access token acquired
125131
for refresh_token in self._get_refresh_tokens(account, is_cae=is_cae):
126-
token = self._client.obtain_token_by_refresh_token(scopes, refresh_token, **kwargs)
132+
token = self._client.obtain_token_by_refresh_token(
133+
scopes, refresh_token, claims=claims, tenant_id=tenant_id, **kwargs
134+
)
127135
return token
128136

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

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ def __enter__(self):
5454
def __exit__(self, *args):
5555
self._client.__exit__(*args)
5656

57-
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
57+
def get_token(
58+
self, *scopes: str, claims: Optional[str] = None, tenant_id: Optional[str] = None, **kwargs: Any
59+
) -> AccessToken:
5860
if not scopes:
5961
raise ValueError('"get_token" requires at least one scope')
6062

@@ -70,7 +72,7 @@ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
7072
raise CredentialUnavailableError(message="Shared token cache unavailable")
7173
raise ClientAuthenticationError(message="Shared token cache unavailable")
7274

73-
return self._acquire_token_silent(*scopes, **kwargs)
75+
return self._acquire_token_silent(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
7476

7577
def _initialize_cache(self, is_cae: bool = False) -> Optional[TokenCache]:
7678

0 commit comments

Comments
 (0)