py2docfx 0.1.19.dev2192635__py3-none-any.whl → 0.1.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py2docfx/__main__.py +2 -2
- py2docfx/convert_prepare/arg_parser.py +1 -1
- py2docfx/convert_prepare/get_source.py +40 -8
- py2docfx/convert_prepare/git.py +1 -1
- py2docfx/docfx_yaml/logger.py +23 -19
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/authorization_code.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/broker.py +79 -0
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/chained.py +8 -2
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/default.py +145 -54
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/imds.py +25 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/vscode.py +163 -144
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/workload_identity.py +23 -12
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/__init__.py +2 -0
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/pipeline.py +4 -2
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/utils.py +85 -0
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_version.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/authorization_code.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/default.py +112 -56
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/imds.py +27 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/on_behalf_of.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/vscode.py +15 -67
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/workload_identity.py +17 -13
- py2docfx/venv/venv1/Lib/site-packages/cryptography/__about__.py +1 -1
- {py2docfx-0.1.19.dev2192635.dist-info → py2docfx-0.1.20.dist-info}/METADATA +1 -1
- {py2docfx-0.1.19.dev2192635.dist-info → py2docfx-0.1.20.dist-info}/RECORD +27 -29
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/linux_vscode_adapter.py +0 -100
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/macos_vscode_adapter.py +0 -34
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/win_vscode_adapter.py +0 -77
- {py2docfx-0.1.19.dev2192635.dist-info → py2docfx-0.1.20.dist-info}/WHEEL +0 -0
- {py2docfx-0.1.19.dev2192635.dist-info → py2docfx-0.1.20.dist-info}/top_level.txt +0 -0
@@ -2,149 +2,190 @@
|
|
2
2
|
# Copyright (c) Microsoft Corporation.
|
3
3
|
# Licensed under the MIT License.
|
4
4
|
# ------------------------------------
|
5
|
-
import abc
|
6
5
|
import os
|
7
|
-
import
|
8
|
-
from typing import
|
9
|
-
import warnings
|
6
|
+
import json
|
7
|
+
from typing import Any, Optional
|
10
8
|
|
9
|
+
import msal
|
11
10
|
from azure.core.credentials import AccessToken, TokenRequestOptions, AccessTokenInfo
|
12
11
|
from azure.core.exceptions import ClientAuthenticationError
|
12
|
+
|
13
|
+
from .._auth_record import AuthenticationRecord
|
13
14
|
from .._exceptions import CredentialUnavailableError
|
14
|
-
from .._constants import
|
15
|
-
from .._internal import
|
16
|
-
|
17
|
-
from .._internal.get_token_mixin import GetTokenMixin
|
15
|
+
from .._constants import AZURE_VSCODE_CLIENT_ID
|
16
|
+
from .._internal import within_dac
|
17
|
+
|
18
18
|
from .._internal.decorators import log_get_token
|
19
|
+
from .._internal.utils import get_broker_credential, validate_tenant_id
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
from .._internal.linux_vscode_adapter import get_refresh_token, get_user_settings
|
21
|
+
MAX_AUTH_RECORD_SIZE = 10 * 1024 # 10KB - more than enough for a small auth record
|
22
|
+
VSCODE_AUTH_RECORD_PATHS = [
|
23
|
+
"~/.azure/ms-azuretools.vscode-azureresourcegroups/authRecord.json",
|
24
|
+
"~/.Azure/ms-azuretools.vscode-azureresourcegroups/authRecord.json",
|
25
|
+
]
|
26
26
|
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
warnings.warn(
|
31
|
-
"This credential is deprecated because the Azure Account extension for Visual Studio Code, which this "
|
32
|
-
"credential relies on, has been deprecated. See the Azure Account extension deprecation notice here: "
|
33
|
-
"https://github.com/microsoft/vscode-azure-account/issues/964. Consider using other developer credentials "
|
34
|
-
"such as AzureCliCredential, AzureDeveloperCliCredential, or AzurePowerShellCredential.",
|
35
|
-
DeprecationWarning,
|
36
|
-
stacklevel=2,
|
37
|
-
)
|
38
|
-
super(_VSCodeCredentialBase, self).__init__()
|
39
|
-
|
40
|
-
user_settings = get_user_settings()
|
41
|
-
self._cloud = user_settings.get("azure.cloud", "AzureCloud")
|
42
|
-
self._refresh_token = None
|
43
|
-
self._unavailable_reason = ""
|
44
|
-
|
45
|
-
self._client = kwargs.get("_client")
|
46
|
-
if not self._client:
|
47
|
-
self._initialize(user_settings, **kwargs)
|
48
|
-
if not (self._client or self._unavailable_reason):
|
49
|
-
self._unavailable_reason = "Initialization failed"
|
50
|
-
|
51
|
-
@abc.abstractmethod
|
52
|
-
def _get_client(self, **kwargs: Any) -> AadClientBase:
|
53
|
-
pass
|
54
|
-
|
55
|
-
def _get_refresh_token(self) -> str:
|
56
|
-
if not self._refresh_token:
|
57
|
-
self._refresh_token = get_refresh_token(self._cloud)
|
58
|
-
if not self._refresh_token:
|
59
|
-
message = (
|
60
|
-
"Failed to get Azure user details from Visual Studio Code. "
|
61
|
-
"Currently, the VisualStudioCodeCredential only works with the Azure "
|
62
|
-
"Account extension version 0.9.11 and earlier. A long-term fix is in "
|
63
|
-
"progress, see https://github.com/Azure/azure-sdk-for-python/issues/25713"
|
64
|
-
)
|
65
|
-
raise CredentialUnavailableError(message=message)
|
66
|
-
return self._refresh_token
|
67
|
-
|
68
|
-
def _initialize(self, vscode_user_settings: Dict, **kwargs: Any) -> None:
|
69
|
-
"""Build a client from kwargs merged with VS Code user settings.
|
28
|
+
def load_vscode_auth_record() -> Optional[AuthenticationRecord]:
|
29
|
+
"""Load the authentication record corresponding to a known location.
|
70
30
|
|
71
|
-
|
72
|
-
|
31
|
+
This will load from ~/.azure/ms-azuretools.vscode-azureresourcegroups/authRecord.json
|
32
|
+
or ~/.Azure/ms-azuretools.vscode-azureresourcegroups/authRecord.json
|
73
33
|
|
74
|
-
|
75
|
-
|
34
|
+
:return: The authentication record if it exists, otherwise None.
|
35
|
+
:rtype: Optional[AuthenticationRecord]
|
36
|
+
:raises: ValueError if the authentication record is not in the expected format
|
37
|
+
"""
|
76
38
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
authority = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD
|
87
|
-
elif self._cloud == "AzureChinaCloud":
|
88
|
-
authority = AzureAuthorityHosts.AZURE_CHINA
|
89
|
-
elif self._cloud == "AzureUSGovernment":
|
90
|
-
authority = AzureAuthorityHosts.AZURE_GOVERNMENT
|
91
|
-
else:
|
92
|
-
# If the value is anything else ("AzureCustomCloud" is the only other known value),
|
93
|
-
# we need the user to provide the authority because VS Code has no setting for it and
|
94
|
-
# we can't guess confidently.
|
95
|
-
self._unavailable_reason = (
|
96
|
-
'VS Code is configured to use a custom cloud. Set keyword argument "authority"'
|
97
|
-
+ ' with the Microsoft Entra endpoint for cloud "{}"'.format(self._cloud)
|
39
|
+
# Try each possible auth record path
|
40
|
+
for auth_record_path in VSCODE_AUTH_RECORD_PATHS:
|
41
|
+
expanded_path = os.path.expanduser(auth_record_path)
|
42
|
+
if os.path.exists(expanded_path):
|
43
|
+
file_size = os.path.getsize(expanded_path)
|
44
|
+
if file_size > MAX_AUTH_RECORD_SIZE:
|
45
|
+
error_message = (
|
46
|
+
"VS Code auth record file is unexpectedly large. "
|
47
|
+
"Please check the file for corruption or unexpected content."
|
98
48
|
)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
49
|
+
raise ValueError(error_message)
|
50
|
+
with open(expanded_path, "r", encoding="utf-8") as f:
|
51
|
+
deserialized = json.load(f)
|
52
|
+
|
53
|
+
# Validate the authentication record for security and structural integrity
|
54
|
+
_validate_auth_record_json(deserialized)
|
55
|
+
|
56
|
+
# Deserialize the authentication record
|
57
|
+
auth_record = AuthenticationRecord(
|
58
|
+
authority=deserialized["authority"],
|
59
|
+
client_id=deserialized["clientId"],
|
60
|
+
home_account_id=deserialized["homeAccountId"],
|
61
|
+
tenant_id=deserialized["tenantId"],
|
62
|
+
username=deserialized["username"],
|
63
|
+
)
|
64
|
+
|
65
|
+
return auth_record
|
114
66
|
|
67
|
+
# No auth record found in any of the expected locations
|
68
|
+
return None
|
115
69
|
|
116
|
-
class VisualStudioCodeCredential(_VSCodeCredentialBase, GetTokenMixin):
|
117
|
-
"""Authenticates as the Azure user signed in to Visual Studio Code via the 'Azure Account' extension.
|
118
70
|
|
119
|
-
|
120
|
-
|
121
|
-
https://github.com/microsoft/vscode-azure-account/issues/964. Consider using other developer credentials such as
|
122
|
-
AzureCliCredential, AzureDeveloperCliCredential, or AzurePowerShellCredential.
|
71
|
+
def _validate_auth_record_json(data: dict) -> None:
|
72
|
+
"""Validate the authentication record.
|
123
73
|
|
124
|
-
:
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
74
|
+
:param dict data: The authentication record data to validate.
|
75
|
+
:raises ValueError: If the authentication record fails validation checks.
|
76
|
+
"""
|
77
|
+
errors = []
|
78
|
+
|
79
|
+
# Schema Validation - Required Fields
|
80
|
+
try:
|
81
|
+
tenant_id = data["tenantId"]
|
82
|
+
if not tenant_id or not isinstance(tenant_id, str):
|
83
|
+
errors.append("tenantId must be a non-empty string")
|
84
|
+
else:
|
85
|
+
try:
|
86
|
+
validate_tenant_id(tenant_id)
|
87
|
+
except ValueError as e:
|
88
|
+
errors.append(f"tenantId validation failed: {e}")
|
89
|
+
except KeyError:
|
90
|
+
errors.append("tenantId field is missing")
|
91
|
+
|
92
|
+
try:
|
93
|
+
client_id = data["clientId"]
|
94
|
+
if not client_id or not isinstance(client_id, str):
|
95
|
+
errors.append("clientId must be a non-empty string")
|
96
|
+
elif client_id != AZURE_VSCODE_CLIENT_ID:
|
97
|
+
errors.append(
|
98
|
+
f"clientId must match expected VS Code Azure Resources extension client ID: {AZURE_VSCODE_CLIENT_ID}"
|
99
|
+
)
|
100
|
+
except KeyError:
|
101
|
+
errors.append("clientId field is missing")
|
102
|
+
|
103
|
+
try:
|
104
|
+
username = data["username"]
|
105
|
+
if not username or not isinstance(username, str):
|
106
|
+
errors.append("username must be a non-empty string")
|
107
|
+
except KeyError:
|
108
|
+
errors.append("username field is missing")
|
109
|
+
|
110
|
+
try:
|
111
|
+
home_account_id = data["homeAccountId"]
|
112
|
+
if not home_account_id or not isinstance(home_account_id, str):
|
113
|
+
errors.append("homeAccountId must be a non-empty string")
|
114
|
+
except KeyError:
|
115
|
+
errors.append("homeAccountId field is missing")
|
116
|
+
|
117
|
+
try:
|
118
|
+
authority = data["authority"]
|
119
|
+
if not authority or not isinstance(authority, str):
|
120
|
+
errors.append("authority must be a non-empty string")
|
121
|
+
except KeyError:
|
122
|
+
errors.append("authority field is missing")
|
123
|
+
|
124
|
+
if errors:
|
125
|
+
error_message = "Authentication record validation failed: " + "; ".join(errors)
|
126
|
+
raise ValueError(error_message)
|
127
|
+
|
128
|
+
|
129
|
+
class VisualStudioCodeCredential:
|
130
|
+
"""Authenticates as the Azure user signed in to Visual Studio Code via the 'Azure Resources' extension.
|
131
|
+
|
132
|
+
This currently only works in Windows/WSL environments and requires the 'azure-identity-broker'
|
133
|
+
package to be installed.
|
134
|
+
|
135
|
+
:keyword str tenant_id: A Microsoft Entra tenant ID. Defaults to the tenant specified in the authentication
|
136
|
+
record file used by the Azure Resources extension.
|
131
137
|
:keyword List[str] additionally_allowed_tenants: Specifies tenants in addition to the specified "tenant_id"
|
132
138
|
for which the credential may acquire tokens. Add the wildcard value "*" to allow the credential to
|
133
139
|
acquire tokens for any tenant the application can access.
|
134
140
|
"""
|
135
141
|
|
142
|
+
def __init__(self, **kwargs: Any) -> None:
|
143
|
+
|
144
|
+
self._broker_credential = None
|
145
|
+
self._unavailable_message = (
|
146
|
+
"VisualStudioCodeCredential requires the 'azure-identity-broker' package to be installed. "
|
147
|
+
"You must also ensure you have the Azure Resources extension installed and have "
|
148
|
+
"signed in to Azure via Visual Studio Code."
|
149
|
+
)
|
150
|
+
|
151
|
+
broker_credential_class = get_broker_credential()
|
152
|
+
if broker_credential_class:
|
153
|
+
try:
|
154
|
+
# Load the authentication record from the VS Code extension
|
155
|
+
authentication_record = load_vscode_auth_record()
|
156
|
+
if not authentication_record:
|
157
|
+
self._unavailable_message = (
|
158
|
+
"VisualStudioCodeCredential requires the user to be signed in to Azure via Visual Studio Code. "
|
159
|
+
"Please ensure you have the Azure Resources extension installed and have signed in."
|
160
|
+
)
|
161
|
+
return
|
162
|
+
self._broker_credential = broker_credential_class(
|
163
|
+
client_id=AZURE_VSCODE_CLIENT_ID,
|
164
|
+
authentication_record=authentication_record,
|
165
|
+
parent_window_handle=msal.PublicClientApplication.CONSOLE_WINDOW_HANDLE,
|
166
|
+
use_default_broker_account=True,
|
167
|
+
disable_interactive_fallback=True,
|
168
|
+
**kwargs,
|
169
|
+
)
|
170
|
+
except ValueError as ex:
|
171
|
+
self._unavailable_message = (
|
172
|
+
"Failed to load authentication record from Visual Studio Code: "
|
173
|
+
f"{ex}. Please ensure you have the Azure Resources extension installed and signed in."
|
174
|
+
)
|
175
|
+
|
136
176
|
def __enter__(self) -> "VisualStudioCodeCredential":
|
137
|
-
if self.
|
138
|
-
self.
|
177
|
+
if self._broker_credential:
|
178
|
+
self._broker_credential.__enter__()
|
139
179
|
return self
|
140
180
|
|
141
181
|
def __exit__(self, *args: Any) -> None:
|
142
|
-
if self.
|
143
|
-
self.
|
182
|
+
if self._broker_credential:
|
183
|
+
self._broker_credential.__exit__(*args)
|
144
184
|
|
145
185
|
def close(self) -> None:
|
146
186
|
"""Close the credential's transport session."""
|
147
|
-
self.
|
187
|
+
if self._broker_credential:
|
188
|
+
self._broker_credential.close()
|
148
189
|
|
149
190
|
@log_get_token
|
150
191
|
def get_token(
|
@@ -166,20 +207,15 @@ class VisualStudioCodeCredential(_VSCodeCredentialBase, GetTokenMixin):
|
|
166
207
|
:raises ~azure.identity.CredentialUnavailableError: the credential cannot retrieve user details from Visual
|
167
208
|
Studio Code
|
168
209
|
"""
|
169
|
-
if self.
|
170
|
-
|
171
|
-
self._unavailable_reason + "\n"
|
172
|
-
"Visit https://aka.ms/azsdk/python/identity/vscodecredential/troubleshoot"
|
173
|
-
" to troubleshoot this issue."
|
174
|
-
)
|
175
|
-
raise CredentialUnavailableError(message=error_message)
|
210
|
+
if not self._broker_credential:
|
211
|
+
raise CredentialUnavailableError(message=self._unavailable_message)
|
176
212
|
if within_dac.get():
|
177
213
|
try:
|
178
|
-
token =
|
214
|
+
token = self._broker_credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
|
179
215
|
return token
|
180
216
|
except ClientAuthenticationError as ex:
|
181
217
|
raise CredentialUnavailableError(message=ex.message) from ex
|
182
|
-
return
|
218
|
+
return self._broker_credential.get_token(*scopes, claims=claims, tenant_id=tenant_id, **kwargs)
|
183
219
|
|
184
220
|
def get_token_info(self, *scopes: str, options: Optional[TokenRequestOptions] = None) -> AccessTokenInfo:
|
185
221
|
"""Request an access token for `scopes` as the user currently signed in to Visual Studio Code.
|
@@ -197,29 +233,12 @@ class VisualStudioCodeCredential(_VSCodeCredentialBase, GetTokenMixin):
|
|
197
233
|
:raises ~azure.identity.CredentialUnavailableError: the credential cannot retrieve user details from Visual
|
198
234
|
Studio Code.
|
199
235
|
"""
|
200
|
-
if self.
|
201
|
-
|
202
|
-
self._unavailable_reason + "\n"
|
203
|
-
"Visit https://aka.ms/azsdk/python/identity/vscodecredential/troubleshoot"
|
204
|
-
" to troubleshoot this issue."
|
205
|
-
)
|
206
|
-
raise CredentialUnavailableError(message=error_message)
|
236
|
+
if not self._broker_credential:
|
237
|
+
raise CredentialUnavailableError(message=self._unavailable_message)
|
207
238
|
if within_dac.get():
|
208
239
|
try:
|
209
|
-
token =
|
240
|
+
token = self._broker_credential.get_token_info(*scopes, options=options)
|
210
241
|
return token
|
211
242
|
except ClientAuthenticationError as ex:
|
212
243
|
raise CredentialUnavailableError(message=ex.message) from ex
|
213
|
-
return
|
214
|
-
|
215
|
-
def _acquire_token_silently(self, *scopes: str, **kwargs: Any) -> Optional[AccessTokenInfo]:
|
216
|
-
self._client = cast(AadClient, self._client)
|
217
|
-
return self._client.get_cached_access_token(scopes, **kwargs)
|
218
|
-
|
219
|
-
def _request_token(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo:
|
220
|
-
refresh_token = self._get_refresh_token()
|
221
|
-
self._client = cast(AadClient, self._client)
|
222
|
-
return self._client.obtain_token_by_refresh_token(scopes, refresh_token, **kwargs)
|
223
|
-
|
224
|
-
def _get_client(self, **kwargs: Any) -> AadClient:
|
225
|
-
return AadClient(**kwargs)
|
244
|
+
return self._broker_credential.get_token_info(*scopes, options=options)
|
@@ -11,6 +11,13 @@ from .client_assertion import ClientAssertionCredential
|
|
11
11
|
from .._constants import EnvironmentVariables
|
12
12
|
|
13
13
|
|
14
|
+
WORKLOAD_CONFIG_ERROR = (
|
15
|
+
"WorkloadIdentityCredential authentication unavailable. The workload options are not fully "
|
16
|
+
"configured. See the troubleshooting guide for more information: "
|
17
|
+
"https://aka.ms/azsdk/python/identity/workloadidentitycredential/troubleshoot"
|
18
|
+
)
|
19
|
+
|
20
|
+
|
14
21
|
class TokenFileMixin:
|
15
22
|
|
16
23
|
_token_file_path: str
|
@@ -72,21 +79,25 @@ class WorkloadIdentityCredential(ClientAssertionCredential, TokenFileMixin):
|
|
72
79
|
tenant_id = tenant_id or os.environ.get(EnvironmentVariables.AZURE_TENANT_ID)
|
73
80
|
client_id = client_id or os.environ.get(EnvironmentVariables.AZURE_CLIENT_ID)
|
74
81
|
token_file_path = token_file_path or os.environ.get(EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE)
|
82
|
+
|
83
|
+
missing_args = []
|
75
84
|
if not tenant_id:
|
76
|
-
|
77
|
-
"'tenant_id' is required. Please pass it in or set the "
|
78
|
-
f"{EnvironmentVariables.AZURE_TENANT_ID} environment variable"
|
79
|
-
)
|
85
|
+
missing_args.append("'tenant_id'")
|
80
86
|
if not client_id:
|
81
|
-
|
82
|
-
"'client_id' is required. Please pass it in or set the "
|
83
|
-
f"{EnvironmentVariables.AZURE_CLIENT_ID} environment variable"
|
84
|
-
)
|
87
|
+
missing_args.append("'client_id'")
|
85
88
|
if not token_file_path:
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
)
|
89
|
+
missing_args.append("'token_file_path'")
|
90
|
+
|
91
|
+
if missing_args:
|
92
|
+
missing_args_str = ", ".join(missing_args)
|
93
|
+
error_message = f"{WORKLOAD_CONFIG_ERROR}. Missing required arguments: {missing_args_str}."
|
94
|
+
raise ValueError(error_message)
|
95
|
+
|
96
|
+
# Type assertions since we've validated these are not None
|
97
|
+
assert tenant_id is not None
|
98
|
+
assert client_id is not None
|
99
|
+
assert token_file_path is not None
|
100
|
+
|
90
101
|
self._token_file_path = token_file_path
|
91
102
|
super(WorkloadIdentityCredential, self).__init__(
|
92
103
|
tenant_id=tenant_id,
|
@@ -11,6 +11,7 @@ from .interactive import InteractiveCredential
|
|
11
11
|
from .utils import (
|
12
12
|
get_default_authority,
|
13
13
|
normalize_authority,
|
14
|
+
process_credential_exclusions,
|
14
15
|
resolve_tenant,
|
15
16
|
validate_scope,
|
16
17
|
validate_subscription,
|
@@ -48,6 +49,7 @@ __all__ = [
|
|
48
49
|
"get_default_authority",
|
49
50
|
"InteractiveCredential",
|
50
51
|
"normalize_authority",
|
52
|
+
"process_credential_exclusions",
|
51
53
|
"resolve_tenant",
|
52
54
|
"validate_scope",
|
53
55
|
"validate_subscription",
|
@@ -63,7 +63,8 @@ def _get_policies(config, _per_retry_policies=None, **kwargs):
|
|
63
63
|
def build_pipeline(transport=None, policies=None, **kwargs):
|
64
64
|
if not policies:
|
65
65
|
config = _get_config(**kwargs)
|
66
|
-
|
66
|
+
retry_policy_class = kwargs.pop("retry_policy_class", None)
|
67
|
+
config.retry_policy = retry_policy_class(**kwargs) if retry_policy_class else RetryPolicy(**kwargs)
|
67
68
|
policies = _get_policies(config, **kwargs)
|
68
69
|
if not transport:
|
69
70
|
from azure.core.pipeline.transport import ( # pylint: disable=non-abstract-transport-import, no-name-in-module
|
@@ -82,7 +83,8 @@ def build_async_pipeline(transport=None, policies=None, **kwargs):
|
|
82
83
|
from azure.core.pipeline.policies import AsyncRetryPolicy
|
83
84
|
|
84
85
|
config = _get_config(**kwargs)
|
85
|
-
|
86
|
+
retry_policy_class = kwargs.pop("retry_policy_class", None)
|
87
|
+
config.retry_policy = retry_policy_class(**kwargs) if retry_policy_class else AsyncRetryPolicy(**kwargs)
|
86
88
|
policies = _get_policies(config, **kwargs)
|
87
89
|
if not transport:
|
88
90
|
from azure.core.pipeline.transport import ( # pylint: disable=non-abstract-transport-import, no-name-in-module
|
@@ -3,6 +3,7 @@
|
|
3
3
|
# Licensed under the MIT License.
|
4
4
|
# ------------------------------------
|
5
5
|
import os
|
6
|
+
import platform
|
6
7
|
import logging
|
7
8
|
from contextvars import ContextVar
|
8
9
|
from string import ascii_letters, digits
|
@@ -133,3 +134,87 @@ def resolve_tenant(
|
|
133
134
|
'when creating the credential, or add "*" to additionally_allowed_tenants to allow '
|
134
135
|
"acquiring tokens for any tenant.".format(tenant_id)
|
135
136
|
)
|
137
|
+
|
138
|
+
|
139
|
+
def process_credential_exclusions(credential_config: dict, exclude_flags: dict, user_excludes: dict) -> dict:
|
140
|
+
"""Process credential exclusions based on environment variable and user overrides.
|
141
|
+
|
142
|
+
This method handles the AZURE_TOKEN_CREDENTIALS environment variable to determine
|
143
|
+
which credentials should be excluded from the credential chain, and then applies
|
144
|
+
any user-provided exclude overrides which take precedence over environment settings.
|
145
|
+
|
146
|
+
:param credential_config: Configuration mapping for all available credentials, containing
|
147
|
+
exclude parameter names, environment names, and default exclude settings
|
148
|
+
:type credential_config: dict
|
149
|
+
:param exclude_flags: Dictionary of exclude flags for each credential (will be modified)
|
150
|
+
:type exclude_flags: dict
|
151
|
+
:param user_excludes: User-provided exclude overrides from constructor kwargs
|
152
|
+
:type user_excludes: dict
|
153
|
+
|
154
|
+
:return: Dictionary of final exclude flags for each credential
|
155
|
+
:rtype: dict
|
156
|
+
|
157
|
+
:raises ValueError: If token_credentials_env contains an invalid credential name
|
158
|
+
"""
|
159
|
+
# Handle AZURE_TOKEN_CREDENTIALS environment variable
|
160
|
+
token_credentials_env = os.environ.get(EnvironmentVariables.AZURE_TOKEN_CREDENTIALS, "").strip().lower()
|
161
|
+
|
162
|
+
if token_credentials_env == "dev":
|
163
|
+
# In dev mode, use only developer credentials
|
164
|
+
dev_credentials = {"visual_studio_code", "cli", "developer_cli", "powershell", "shared_token_cache"}
|
165
|
+
for cred_key in credential_config:
|
166
|
+
exclude_flags[cred_key] = cred_key not in dev_credentials
|
167
|
+
elif token_credentials_env == "prod":
|
168
|
+
# In prod mode, use only production credentials
|
169
|
+
prod_credentials = {"environment", "workload_identity", "managed_identity"}
|
170
|
+
for cred_key in credential_config:
|
171
|
+
exclude_flags[cred_key] = cred_key not in prod_credentials
|
172
|
+
elif token_credentials_env:
|
173
|
+
# If a specific credential is specified, exclude all others except the specified one
|
174
|
+
valid_credentials = {config["env_name"] for config in credential_config.values() if "env_name" in config}
|
175
|
+
|
176
|
+
if token_credentials_env not in valid_credentials:
|
177
|
+
valid_values = ["dev", "prod"] + sorted(valid_credentials)
|
178
|
+
raise ValueError(
|
179
|
+
f"Invalid value for {EnvironmentVariables.AZURE_TOKEN_CREDENTIALS}: {token_credentials_env}. "
|
180
|
+
f"Valid values are: {', '.join(valid_values)}."
|
181
|
+
)
|
182
|
+
|
183
|
+
# Find which credential was selected and exclude all others
|
184
|
+
selected_cred_key = None
|
185
|
+
for cred_key, config in credential_config.items():
|
186
|
+
if config.get("env_name") == token_credentials_env:
|
187
|
+
selected_cred_key = cred_key
|
188
|
+
break
|
189
|
+
|
190
|
+
for cred_key in credential_config:
|
191
|
+
exclude_flags[cred_key] = cred_key != selected_cred_key
|
192
|
+
|
193
|
+
# Apply user-provided exclude flags (these override environment variable settings)
|
194
|
+
for cred_key, user_value in user_excludes.items():
|
195
|
+
if user_value is not None:
|
196
|
+
exclude_flags[cred_key] = user_value
|
197
|
+
|
198
|
+
return exclude_flags
|
199
|
+
|
200
|
+
|
201
|
+
def get_broker_credential() -> Optional[type]:
|
202
|
+
"""Return the InteractiveBrowserBrokerCredential class if available, otherwise None.
|
203
|
+
|
204
|
+
:return: InteractiveBrowserBrokerCredential class or None
|
205
|
+
:rtype: Optional[type]
|
206
|
+
"""
|
207
|
+
try:
|
208
|
+
from azure.identity.broker import InteractiveBrowserBrokerCredential
|
209
|
+
|
210
|
+
return InteractiveBrowserBrokerCredential
|
211
|
+
except ImportError:
|
212
|
+
return None
|
213
|
+
|
214
|
+
|
215
|
+
def is_wsl() -> bool:
|
216
|
+
# This is how MSAL checks for WSL.
|
217
|
+
uname = platform.uname()
|
218
|
+
platform_name = getattr(uname, "system", uname[0]).lower()
|
219
|
+
release = getattr(uname, "release", uname[2]).lower()
|
220
|
+
return platform_name == "linux" and "microsoft" in release
|
@@ -133,7 +133,7 @@ class AuthorizationCodeCredential(AsyncContextManager, GetTokenMixin):
|
|
133
133
|
return token
|
134
134
|
|
135
135
|
token = cast(AccessTokenInfo, None)
|
136
|
-
for refresh_token in self._client.get_cached_refresh_tokens(scopes):
|
136
|
+
for refresh_token in self._client.get_cached_refresh_tokens(scopes, **kwargs):
|
137
137
|
if "secret" in refresh_token:
|
138
138
|
token = await self._client.obtain_token_by_refresh_token(scopes, refresh_token["secret"], **kwargs)
|
139
139
|
if token:
|