py2docfx 0.1.20rc2196756__py3-none-any.whl → 0.1.21.dev2246704__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.
Files changed (140) hide show
  1. py2docfx/convert_prepare/get_source.py +1 -1
  2. py2docfx/convert_prepare/package_info.py +37 -27
  3. py2docfx/convert_prepare/tests/test_get_source.py +3 -1
  4. py2docfx/convert_prepare/tests/test_package_info.py +159 -1
  5. py2docfx/docfx_yaml/build_finished.py +1 -1
  6. py2docfx/docfx_yaml/logger.py +42 -28
  7. py2docfx/venv/basevenv/Lib/site-packages/charset_normalizer/api.py +3 -2
  8. py2docfx/venv/basevenv/Lib/site-packages/charset_normalizer/legacy.py +17 -1
  9. py2docfx/venv/basevenv/Lib/site-packages/charset_normalizer/version.py +1 -1
  10. py2docfx/venv/basevenv/Lib/site-packages/requests/__version__.py +2 -2
  11. py2docfx/venv/basevenv/Lib/site-packages/requests/adapters.py +17 -40
  12. py2docfx/venv/basevenv/Lib/site-packages/requests/sessions.py +1 -1
  13. py2docfx/venv/venv1/Lib/site-packages/azure/core/_version.py +1 -1
  14. py2docfx/venv/venv1/Lib/site-packages/azure/core/pipeline/policies/_authentication.py +21 -9
  15. py2docfx/venv/venv1/Lib/site-packages/azure/core/pipeline/policies/_authentication_async.py +21 -9
  16. py2docfx/venv/venv1/Lib/site-packages/azure/core/pipeline/policies/_retry.py +1 -1
  17. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_bearer_token_provider.py +1 -1
  18. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/authorization_code.py +1 -1
  19. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/azd_cli.py +82 -17
  20. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/azure_cli.py +28 -5
  21. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/azure_powershell.py +28 -4
  22. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/broker.py +79 -0
  23. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/chained.py +9 -3
  24. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/default.py +153 -53
  25. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/imds.py +25 -1
  26. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/shared_cache.py +12 -5
  27. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/vscode.py +163 -144
  28. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/workload_identity.py +23 -12
  29. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/__init__.py +4 -0
  30. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/interactive.py +14 -2
  31. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/pipeline.py +4 -2
  32. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/utils.py +96 -0
  33. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_version.py +1 -1
  34. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_bearer_token_provider.py +3 -3
  35. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/authorization_code.py +1 -1
  36. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/azd_cli.py +32 -13
  37. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/azure_cli.py +26 -5
  38. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/azure_powershell.py +13 -2
  39. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/chained.py +1 -1
  40. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/default.py +120 -55
  41. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/imds.py +27 -1
  42. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/on_behalf_of.py +1 -1
  43. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/shared_cache.py +12 -5
  44. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/vscode.py +15 -67
  45. py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/workload_identity.py +17 -13
  46. py2docfx/venv/venv1/Lib/site-packages/cffi/__init__.py +2 -2
  47. py2docfx/venv/venv1/Lib/site-packages/cffi/cparser.py +1 -1
  48. py2docfx/venv/venv1/Lib/site-packages/cffi/recompiler.py +5 -5
  49. py2docfx/venv/venv1/Lib/site-packages/cffi/setuptools_ext.py +13 -0
  50. py2docfx/venv/venv1/Lib/site-packages/cffi/vengine_cpy.py +3 -0
  51. py2docfx/venv/venv1/Lib/site-packages/charset_normalizer/api.py +3 -2
  52. py2docfx/venv/venv1/Lib/site-packages/charset_normalizer/legacy.py +17 -1
  53. py2docfx/venv/venv1/Lib/site-packages/charset_normalizer/version.py +1 -1
  54. py2docfx/venv/venv1/Lib/site-packages/cryptography/__about__.py +1 -1
  55. py2docfx/venv/venv1/Lib/site-packages/cryptography/__init__.py +0 -13
  56. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/_oid.py +8 -0
  57. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/asn1/__init__.py +10 -0
  58. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/asn1/asn1.py +116 -0
  59. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/backends/openssl/backend.py +3 -9
  60. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi +32 -0
  61. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi +23 -0
  62. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/_rust/x509.pyi +1 -13
  63. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/openssl/_conditional.py +16 -0
  64. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/openssl/binding.py +16 -1
  65. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/asymmetric/padding.py +0 -2
  66. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/asymmetric/rsa.py +8 -0
  67. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.py +0 -47
  68. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py +6 -91
  69. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py +1 -3
  70. py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/serialization/ssh.py +1 -1
  71. py2docfx/venv/venv1/Lib/site-packages/cryptography/utils.py +0 -2
  72. py2docfx/venv/venv1/Lib/site-packages/cryptography/x509/name.py +2 -3
  73. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/__init__.py +1 -1
  74. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/any_pb2.py +2 -2
  75. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/api_pb2.py +12 -8
  76. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/compiler/plugin_pb2.py +2 -2
  77. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/descriptor.py +398 -246
  78. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/descriptor_pb2.py +74 -72
  79. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/descriptor_pool.py +5 -4
  80. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/duration_pb2.py +2 -2
  81. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/empty_pb2.py +2 -2
  82. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/field_mask_pb2.py +2 -2
  83. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/api_implementation.py +0 -6
  84. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/extension_dict.py +3 -3
  85. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/field_mask.py +3 -3
  86. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/python_edition_defaults.py +1 -1
  87. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/python_message.py +10 -2
  88. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/type_checkers.py +47 -5
  89. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/json_format.py +55 -32
  90. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/runtime_version.py +6 -26
  91. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/source_context_pb2.py +2 -2
  92. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/struct_pb2.py +2 -2
  93. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/text_format.py +30 -19
  94. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/timestamp_pb2.py +2 -2
  95. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/type_pb2.py +2 -2
  96. py2docfx/venv/venv1/Lib/site-packages/google/protobuf/wrappers_pb2.py +2 -2
  97. py2docfx/venv/venv1/Lib/site-packages/psutil/__init__.py +39 -19
  98. py2docfx/venv/venv1/Lib/site-packages/psutil/_common.py +3 -5
  99. py2docfx/venv/venv1/Lib/site-packages/psutil/_psaix.py +1 -2
  100. py2docfx/venv/venv1/Lib/site-packages/psutil/_psbsd.py +53 -78
  101. py2docfx/venv/venv1/Lib/site-packages/psutil/_pslinux.py +55 -38
  102. py2docfx/venv/venv1/Lib/site-packages/psutil/_psosx.py +40 -12
  103. py2docfx/venv/venv1/Lib/site-packages/psutil/_psposix.py +0 -1
  104. py2docfx/venv/venv1/Lib/site-packages/psutil/_pssunos.py +1 -2
  105. py2docfx/venv/venv1/Lib/site-packages/psutil/_pswindows.py +33 -13
  106. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/__init__.py +185 -122
  107. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/__main__.py +2 -3
  108. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_bsd.py +5 -10
  109. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_connections.py +3 -4
  110. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_contracts.py +41 -45
  111. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_linux.py +35 -38
  112. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_memleaks.py +4 -8
  113. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_misc.py +6 -12
  114. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_osx.py +17 -8
  115. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_posix.py +29 -17
  116. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_process.py +74 -75
  117. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_process_all.py +11 -13
  118. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_scripts.py +2 -3
  119. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_sudo.py +117 -0
  120. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_system.py +21 -31
  121. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_testutils.py +23 -23
  122. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_unicode.py +15 -8
  123. py2docfx/venv/venv1/Lib/site-packages/psutil/tests/test_windows.py +65 -33
  124. py2docfx/venv/venv1/Lib/site-packages/pycparser/__init__.py +1 -1
  125. py2docfx/venv/venv1/Lib/site-packages/pycparser/c_generator.py +1 -1
  126. py2docfx/venv/venv1/Lib/site-packages/pycparser/c_lexer.py +14 -0
  127. py2docfx/venv/venv1/Lib/site-packages/pycparser/c_parser.py +30 -7
  128. py2docfx/venv/venv1/Lib/site-packages/pycparser/lextab.py +1 -1
  129. py2docfx/venv/venv1/Lib/site-packages/pycparser/yacctab.py +132 -127
  130. py2docfx/venv/venv1/Lib/site-packages/requests/__version__.py +2 -2
  131. py2docfx/venv/venv1/Lib/site-packages/requests/adapters.py +17 -40
  132. py2docfx/venv/venv1/Lib/site-packages/requests/sessions.py +1 -1
  133. py2docfx/venv/venv1/Lib/site-packages/typing_extensions.py +91 -18
  134. {py2docfx-0.1.20rc2196756.dist-info → py2docfx-0.1.21.dev2246704.dist-info}/METADATA +1 -1
  135. {py2docfx-0.1.20rc2196756.dist-info → py2docfx-0.1.21.dev2246704.dist-info}/RECORD +137 -135
  136. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/linux_vscode_adapter.py +0 -100
  137. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/macos_vscode_adapter.py +0 -34
  138. py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/win_vscode_adapter.py +0 -77
  139. {py2docfx-0.1.20rc2196756.dist-info → py2docfx-0.1.21.dev2246704.dist-info}/WHEEL +0 -0
  140. {py2docfx-0.1.20rc2196756.dist-info → py2docfx-0.1.21.dev2246704.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ from azure.core.credentials_async import (
13
13
  AsyncSupportsTokenInfo,
14
14
  AsyncTokenProvider,
15
15
  )
16
+ from azure.core.exceptions import HttpResponseError
16
17
  from azure.core.pipeline import PipelineRequest, PipelineResponse
17
18
  from azure.core.pipeline.policies import AsyncHTTPPolicy
18
19
  from azure.core.pipeline.policies._authentication import (
@@ -110,7 +111,21 @@ class AsyncBearerTokenCredentialPolicy(AsyncHTTPPolicy[HTTPRequestType, AsyncHTT
110
111
  if response.http_response.status_code == 401:
111
112
  self._token = None # any cached token is invalid
112
113
  if "WWW-Authenticate" in response.http_response.headers:
113
- request_authorized = await self.on_challenge(request, response)
114
+ try:
115
+ request_authorized = await self.on_challenge(request, response)
116
+ except Exception as ex:
117
+ # If the response is streamed, read it so the error message is immediately available to the user.
118
+ # Otherwise, a generic error message will be given and the user will have to read the response
119
+ # body to see the actual error.
120
+ if response.context.options.get("stream"):
121
+ try:
122
+ await response.http_response.read() # type: ignore
123
+ except Exception: # pylint:disable=broad-except
124
+ pass
125
+
126
+ # Raise the exception from the token request with the original 401 response
127
+ raise ex from HttpResponseError(response=response.http_response)
128
+
114
129
  if request_authorized:
115
130
  # if we receive a challenge response, we retrieve a new token
116
131
  # which matches the new target. In this case, we don't want to remove
@@ -145,14 +160,11 @@ class AsyncBearerTokenCredentialPolicy(AsyncHTTPPolicy[HTTPRequestType, AsyncHTT
145
160
  encoded_claims = get_challenge_parameter(headers, "Bearer", "claims")
146
161
  if not encoded_claims:
147
162
  return False
148
- try:
149
- padding_needed = -len(encoded_claims) % 4
150
- claims = base64.urlsafe_b64decode(encoded_claims + "=" * padding_needed).decode("utf-8")
151
- if claims:
152
- await self.authorize_request(request, *self._scopes, claims=claims)
153
- return True
154
- except Exception: # pylint:disable=broad-except
155
- return False
163
+ padding_needed = -len(encoded_claims) % 4
164
+ claims = base64.urlsafe_b64decode(encoded_claims + "=" * padding_needed).decode("utf-8")
165
+ if claims:
166
+ await self.authorize_request(request, *self._scopes, claims=claims)
167
+ return True
156
168
  return False
157
169
 
158
170
  def on_response(
@@ -118,7 +118,7 @@ class RetryPolicyBase:
118
118
  "read": options.pop("retry_read", self.read_retries),
119
119
  "status": options.pop("retry_status", self.status_retries),
120
120
  "backoff": options.pop("retry_backoff_factor", self.backoff_factor),
121
- "max_backoff": options.pop("retry_backoff_max", self.BACKOFF_MAX),
121
+ "max_backoff": options.pop("retry_backoff_max", self.backoff_max),
122
122
  "methods": options.pop("retry_on_methods", self._method_whitelist),
123
123
  "timeout": options.pop("timeout", self.timeout),
124
124
  "history": [],
@@ -30,7 +30,7 @@ def get_bearer_token_provider(credential: TokenProvider, *scopes: str) -> Callab
30
30
  request.headers["Authorization"] = "Bearer " + bearer_token_provider()
31
31
 
32
32
  :param credential: The credential used to authenticate the request.
33
- :type credential: ~azure.core.credentials.TokenCredential
33
+ :type credential: ~azure.core.credentials.TokenProvider
34
34
  :param str scopes: The scopes required for the bearer token.
35
35
  :rtype: callable
36
36
  :return: A callable that returns a bearer token.
@@ -127,7 +127,7 @@ class AuthorizationCodeCredential(GetTokenMixin):
127
127
  return token
128
128
 
129
129
  token = None
130
- for refresh_token in self._client.get_cached_refresh_tokens(scopes):
130
+ for refresh_token in self._client.get_cached_refresh_tokens(scopes, **kwargs):
131
131
  if "secret" in refresh_token:
132
132
  token = self._client.obtain_token_by_refresh_token(scopes, refresh_token["secret"], **kwargs)
133
133
  if token:
@@ -17,7 +17,7 @@ from azure.core.credentials import AccessToken, AccessTokenInfo, TokenRequestOpt
17
17
  from azure.core.exceptions import ClientAuthenticationError
18
18
 
19
19
  from .. import CredentialUnavailableError
20
- from .._internal import resolve_tenant, within_dac, validate_tenant_id, validate_scope
20
+ from .._internal import encode_base64, resolve_tenant, within_dac, validate_tenant_id, validate_scope
21
21
  from .._internal.decorators import log_get_token
22
22
 
23
23
 
@@ -28,7 +28,11 @@ CLI_NOT_FOUND = (
28
28
  "Please visit https://aka.ms/azure-dev for installation instructions and then,"
29
29
  "once installed, authenticate to your Azure account using 'azd auth login'."
30
30
  )
31
- COMMAND_LINE = ["auth", "token", "--output", "json"]
31
+ UNKNOWN_CLAIMS_FLAG = (
32
+ "Claims challenges are not supported by the Azure Developer CLI version you are using. "
33
+ "Please update to version 1.18.1 or later."
34
+ )
35
+ COMMAND_LINE = ["auth", "token", "--output", "json", "--no-prompt"]
32
36
  EXECUTABLE_NAME = "azd"
33
37
  NOT_LOGGED_IN = "Please run 'azd auth login' from a command prompt to authenticate before using this credential."
34
38
 
@@ -99,7 +103,7 @@ class AzureDeveloperCliCredential:
99
103
  def get_token(
100
104
  self,
101
105
  *scopes: str,
102
- claims: Optional[str] = None, # pylint:disable=unused-argument
106
+ claims: Optional[str] = None,
103
107
  tenant_id: Optional[str] = None,
104
108
  **kwargs: Any,
105
109
  ) -> AccessToken:
@@ -111,7 +115,8 @@ class AzureDeveloperCliCredential:
111
115
  :param str scopes: desired scope for the access token. This credential allows only one scope per request.
112
116
  For more information about scopes, see
113
117
  https://learn.microsoft.com/entra/identity-platform/scopes-oidc.
114
- :keyword str claims: not used by this credential; any value provided will be ignored.
118
+ :keyword str claims: additional claims required in the token, such as those returned in a resource provider's
119
+ claims challenge following an authorization failure.
115
120
  :keyword str tenant_id: optional tenant to include in the token request.
116
121
 
117
122
  :return: An access token with the desired scopes.
@@ -125,6 +130,8 @@ class AzureDeveloperCliCredential:
125
130
  options: TokenRequestOptions = {}
126
131
  if tenant_id:
127
132
  options["tenant_id"] = tenant_id
133
+ if claims:
134
+ options["claims"] = claims
128
135
 
129
136
  token_info = self._get_token_base(*scopes, options=options, **kwargs)
130
137
  return AccessToken(token_info.token, token_info.expires_on)
@@ -159,6 +166,7 @@ class AzureDeveloperCliCredential:
159
166
  raise ValueError("Missing scope in request. \n")
160
167
 
161
168
  tenant_id = options.get("tenant_id") if options else None
169
+ claims = options.get("claims") if options else None
162
170
  if tenant_id:
163
171
  validate_tenant_id(tenant_id)
164
172
  for scope in scopes:
@@ -175,16 +183,23 @@ class AzureDeveloperCliCredential:
175
183
  )
176
184
  if tenant:
177
185
  command_args += ["--tenant-id", tenant]
186
+ if claims:
187
+ command_args += ["--claims", encode_base64(claims)]
178
188
  output = _run_command(command_args, self._process_timeout)
179
189
 
180
190
  token = parse_token(output)
181
191
  if not token:
182
- sanitized_output = sanitize_output(output)
183
- message = (
184
- f"Unexpected output from Azure Developer CLI: '{sanitized_output}'. \n"
185
- f"To mitigate this issue, please refer to the troubleshooting guidelines here at "
186
- f"https://aka.ms/azsdk/python/identity/azdevclicredential/troubleshoot."
187
- )
192
+ # Try to extract a meaningful error from azd consoleMessage JSON lines
193
+ extracted = extract_cli_error_message(output)
194
+ if extracted:
195
+ message = extracted
196
+ else:
197
+ sanitized_output = sanitize_output(output)
198
+ message = (
199
+ f"Unexpected output from Azure Developer CLI: '{sanitized_output}'. \n"
200
+ f"To mitigate this issue, please refer to the troubleshooting guidelines here at "
201
+ f"https://aka.ms/azsdk/python/identity/azdevclicredential/troubleshoot."
202
+ )
188
203
  if within_dac.get():
189
204
  raise CredentialUnavailableError(message=message)
190
205
  raise ClientAuthenticationError(message=message)
@@ -241,6 +256,54 @@ def sanitize_output(output: str) -> str:
241
256
  return re.sub(r"\"token\": \"(.*?)(\"|$)", "****", output)
242
257
 
243
258
 
259
+ def extract_cli_error_message(output: str) -> Optional[str]:
260
+ """
261
+ Extract a single, user-friendly message from azd consoleMessage JSON output.
262
+
263
+ :param str output: The output from the Azure Developer CLI command.
264
+ :return: A user-friendly error message if found, otherwise None.
265
+ :rtype: Optional[str]
266
+
267
+ Preference order:
268
+ 1) A message containing "Suggestion" (case-insensitive)
269
+ 2) The second message if multiple are present
270
+ 3) The first message if only one exists
271
+ Returns None if no messages can be parsed.
272
+ """
273
+ messages: List[str] = []
274
+ for line in output.splitlines():
275
+ line = line.strip()
276
+ if not line:
277
+ continue
278
+ try:
279
+ obj = json.loads(line)
280
+ except json.JSONDecodeError: # not JSON -> ignore
281
+ continue
282
+ if isinstance(obj, dict):
283
+ data = obj.get("data")
284
+ if isinstance(data, dict):
285
+ msg = data.get("message")
286
+ if isinstance(msg, str) and msg.strip():
287
+ messages.append(msg.strip())
288
+ continue
289
+ msg = obj.get("message")
290
+ if isinstance(msg, str) and msg.strip():
291
+ messages.append(msg.strip())
292
+
293
+ if not messages:
294
+ return None
295
+
296
+ # Prefer the suggestion line if present
297
+ for msg in messages:
298
+ if "suggestion" in msg.lower():
299
+ return sanitize_output(msg)
300
+
301
+ # If more than one message exists, return the last one
302
+ if len(messages) > 1:
303
+ return sanitize_output(messages[-1])
304
+ return sanitize_output(messages[0])
305
+
306
+
244
307
  def _run_command(command_args: List[str], timeout: int) -> str:
245
308
  # Ensure executable exists in PATH first. This avoids a subprocess call that would fail anyway.
246
309
  azd_path = shutil.which(EXECUTABLE_NAME)
@@ -267,16 +330,18 @@ def _run_command(command_args: List[str], timeout: int) -> str:
267
330
  # Fallback check in case the executable is not found while executing subprocess.
268
331
  if ex.returncode == 127 or (ex.stderr is not None and ex.stderr.startswith("'azd' is not recognized")):
269
332
  raise CredentialUnavailableError(message=CLI_NOT_FOUND) from ex
270
- if ex.stderr is not None and (
271
- "not logged in, run `azd auth login` to login" in ex.stderr and "AADSTS" not in ex.stderr
272
- ):
333
+ combined_text = "{}\n{}".format(ex.output or "", ex.stderr or "")
334
+ if "not logged in, run `azd auth login` to login" in combined_text and "AADSTS" not in combined_text:
273
335
  raise CredentialUnavailableError(message=NOT_LOGGED_IN) from ex
336
+ if "unknown flag: --claims" in combined_text:
337
+ raise CredentialUnavailableError(message=UNKNOWN_CLAIMS_FLAG) from ex
274
338
 
275
339
  # return code is from the CLI -> propagate its output
276
- if ex.stderr:
277
- message = sanitize_output(ex.stderr)
278
- else:
279
- message = "Failed to invoke Azure Developer CLI"
340
+ message = (
341
+ extract_cli_error_message(ex.output or "")
342
+ or extract_cli_error_message(ex.stderr or "")
343
+ or (sanitize_output(ex.stderr) if ex.stderr else "Failed to invoke Azure Developer CLI")
344
+ )
280
345
  if within_dac.get():
281
346
  raise CredentialUnavailableError(message=message) from ex
282
347
  raise ClientAuthenticationError(message=message) from ex
@@ -18,6 +18,7 @@ from azure.core.exceptions import ClientAuthenticationError
18
18
  from .. import CredentialUnavailableError
19
19
  from .._internal import (
20
20
  _scopes_to_resource,
21
+ encode_base64,
21
22
  resolve_tenant,
22
23
  within_dac,
23
24
  validate_tenant_id,
@@ -34,6 +35,11 @@ CLI_NOT_FOUND = "Azure CLI not found on path"
34
35
  COMMAND_LINE = ["account", "get-access-token", "--output", "json"]
35
36
  EXECUTABLE_NAME = "az"
36
37
  NOT_LOGGED_IN = "Please run 'az login' to set up an account"
38
+ CLAIMS_UNSUPPORTED_ERROR = (
39
+ "This credential doesn't support claims challenges. To authenticate with the required "
40
+ "claims, please run the following command (requires Azure CLI version 2.76.0 or later): "
41
+ "az login --claims-challenge {claims_value}"
42
+ )
37
43
 
38
44
 
39
45
  class AzureCliCredential:
@@ -90,7 +96,7 @@ class AzureCliCredential:
90
96
  def get_token(
91
97
  self,
92
98
  *scopes: str,
93
- claims: Optional[str] = None, # pylint:disable=unused-argument
99
+ claims: Optional[str] = None,
94
100
  tenant_id: Optional[str] = None,
95
101
  **kwargs: Any,
96
102
  ) -> AccessToken:
@@ -102,20 +108,23 @@ class AzureCliCredential:
102
108
  :param str scopes: desired scope for the access token. This credential allows only one scope per request.
103
109
  For more information about scopes, see
104
110
  https://learn.microsoft.com/entra/identity-platform/scopes-oidc.
105
- :keyword str claims: not used by this credential; any value provided will be ignored.
111
+ :keyword str claims: additional claims required in the token. This credential does not support claims
112
+ challenges.
106
113
  :keyword str tenant_id: optional tenant to include in the token request.
107
114
 
108
115
  :return: An access token with the desired scopes.
109
116
  :rtype: ~azure.core.credentials.AccessToken
110
117
 
111
- :raises ~azure.identity.CredentialUnavailableError: the credential was unable to invoke the Azure CLI.
118
+ :raises ~azure.identity.CredentialUnavailableError: the credential was either unable to invoke the Azure CLI
119
+ or a claims challenge was provided.
112
120
  :raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked the Azure CLI but didn't
113
121
  receive an access token.
114
122
  """
115
-
116
123
  options: TokenRequestOptions = {}
117
124
  if tenant_id:
118
125
  options["tenant_id"] = tenant_id
126
+ if claims:
127
+ options["claims"] = claims
119
128
 
120
129
  token_info = self._get_token_base(*scopes, options=options, **kwargs)
121
130
  return AccessToken(token_info.token, token_info.expires_on)
@@ -136,7 +145,8 @@ class AzureCliCredential:
136
145
  :rtype: ~azure.core.credentials.AccessTokenInfo
137
146
  :return: An AccessTokenInfo instance containing information about the token.
138
147
 
139
- :raises ~azure.identity.CredentialUnavailableError: the credential was unable to invoke the Azure CLI.
148
+ :raises ~azure.identity.CredentialUnavailableError: the credential was either unable to invoke the Azure CLI
149
+ or a claims challenge was provided.
140
150
  :raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked the Azure CLI but didn't
141
151
  receive an access token.
142
152
  """
@@ -145,6 +155,19 @@ class AzureCliCredential:
145
155
  def _get_token_base(
146
156
  self, *scopes: str, options: Optional[TokenRequestOptions] = None, **kwargs: Any
147
157
  ) -> AccessTokenInfo:
158
+ # Check for claims challenge first
159
+ if options and "claims" in options and options["claims"]:
160
+ error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=encode_base64(options["claims"]))
161
+
162
+ # Add tenant if provided in options
163
+ if options.get("tenant_id"):
164
+ error_message += f" --tenant {options.get('tenant_id')}"
165
+
166
+ # Add scope if provided
167
+ if scopes:
168
+ error_message += f" --scope {scopes[0]}"
169
+
170
+ raise CredentialUnavailableError(message=error_message)
148
171
 
149
172
  tenant_id = options.get("tenant_id") if options else None
150
173
  if tenant_id:
@@ -13,7 +13,14 @@ from azure.core.exceptions import ClientAuthenticationError
13
13
 
14
14
  from .azure_cli import get_safe_working_dir
15
15
  from .. import CredentialUnavailableError
16
- from .._internal import _scopes_to_resource, resolve_tenant, within_dac, validate_tenant_id, validate_scope
16
+ from .._internal import (
17
+ _scopes_to_resource,
18
+ encode_base64,
19
+ resolve_tenant,
20
+ within_dac,
21
+ validate_tenant_id,
22
+ validate_scope,
23
+ )
17
24
  from .._internal.decorators import log_get_token
18
25
 
19
26
 
@@ -24,6 +31,11 @@ BLOCKED_BY_EXECUTION_POLICY = "Execution policy prevented invoking Azure PowerSh
24
31
  NO_AZ_ACCOUNT_MODULE = "NO_AZ_ACCOUNT_MODULE"
25
32
  POWERSHELL_NOT_INSTALLED = "PowerShell is not installed"
26
33
  RUN_CONNECT_AZ_ACCOUNT = 'Please run "Connect-AzAccount" to set up account'
34
+ CLAIMS_UNSUPPORTED_ERROR = (
35
+ "This credential doesn't support claims challenges. To authenticate with the required "
36
+ "claims, please run the following command (requires Az.Accounts module version 5.2.0 or later): "
37
+ "Connect-AzAccount -ClaimsChallenge {claims_value}"
38
+ )
27
39
  SCRIPT = """$ErrorActionPreference = 'Stop'
28
40
  [version]$minimumVersion = '2.2.0'
29
41
 
@@ -112,7 +124,7 @@ class AzurePowerShellCredential:
112
124
  def get_token(
113
125
  self,
114
126
  *scopes: str,
115
- claims: Optional[str] = None, # pylint:disable=unused-argument
127
+ claims: Optional[str] = None,
116
128
  tenant_id: Optional[str] = None,
117
129
  **kwargs: Any,
118
130
  ) -> AccessToken:
@@ -135,10 +147,11 @@ class AzurePowerShellCredential:
135
147
  :raises ~azure.core.exceptions.ClientAuthenticationError: the credential invoked Azure PowerShell but didn't
136
148
  receive an access token
137
149
  """
138
-
139
150
  options: TokenRequestOptions = {}
140
151
  if tenant_id:
141
152
  options["tenant_id"] = tenant_id
153
+ if claims:
154
+ options["claims"] = claims
142
155
 
143
156
  token_info = self._get_token_base(*scopes, options=options, **kwargs)
144
157
  return AccessToken(token_info.token, token_info.expires_on)
@@ -170,6 +183,13 @@ class AzurePowerShellCredential:
170
183
  self, *scopes: str, options: Optional[TokenRequestOptions] = None, **kwargs: Any
171
184
  ) -> AccessTokenInfo:
172
185
 
186
+ # Check if claims challenge is provided
187
+ if options and "claims" in options and options["claims"]:
188
+ error_message = CLAIMS_UNSUPPORTED_ERROR.format(claims_value=encode_base64(options["claims"]))
189
+ if options.get("tenant_id"):
190
+ error_message += f" -Tenant {options.get('tenant_id')}"
191
+ raise CredentialUnavailableError(message=error_message)
192
+
173
193
  tenant_id = options.get("tenant_id") if options else None
174
194
  if tenant_id:
175
195
  validate_tenant_id(tenant_id)
@@ -269,7 +289,11 @@ def raise_for_error(return_code: int, stdout: str, stderr: str) -> None:
269
289
 
270
290
  if stderr:
271
291
  # stderr is too noisy to include with an exception but may be useful for debugging
272
- _LOGGER.debug('%s received an error from Azure PowerShell: "%s"', AzurePowerShellCredential.__name__, stderr)
292
+ _LOGGER.debug(
293
+ '%s received an error from Azure PowerShell: "%s"',
294
+ AzurePowerShellCredential.__name__,
295
+ stderr,
296
+ )
273
297
  raise CredentialUnavailableError(
274
298
  message="Failed to invoke PowerShell. Enable debug logging for additional information."
275
299
  )
@@ -0,0 +1,79 @@
1
+ # ------------------------------------
2
+ # Copyright (c) Microsoft Corporation.
3
+ # Licensed under the MIT License.
4
+ # ------------------------------------
5
+ import sys
6
+ from typing import Any
7
+
8
+ import msal
9
+ from azure.core.credentials import AccessToken, AccessTokenInfo, SupportsTokenInfo
10
+ from .._exceptions import CredentialUnavailableError
11
+ from .._internal.utils import get_broker_credential, is_wsl
12
+
13
+
14
+ class BrokerCredential(SupportsTokenInfo):
15
+ """A broker credential that handles prerequisite checking and falls back appropriately.
16
+
17
+ This credential checks if the azure-identity-broker package is available and the platform
18
+ is supported. If both conditions are met, it uses the real broker credential. Otherwise,
19
+ it raises CredentialUnavailableError with an appropriate message.
20
+ """
21
+
22
+ def __init__(self, **kwargs: Any) -> None:
23
+
24
+ self._tenant_id = kwargs.pop("tenant_id", None)
25
+ self._client_id = kwargs.pop("client_id", None)
26
+ self._broker_credential = None
27
+ self._unavailable_message = None
28
+
29
+ # Check prerequisites and initialize the appropriate credential
30
+ broker_credential_class = get_broker_credential()
31
+ if broker_credential_class and (sys.platform.startswith("win") or is_wsl()):
32
+ # The silent auth flow for brokered auth is available on Windows/WSL with the broker package
33
+ try:
34
+ broker_credential_args = {
35
+ "tenant_id": self._tenant_id,
36
+ "parent_window_handle": msal.PublicClientApplication.CONSOLE_WINDOW_HANDLE,
37
+ "use_default_broker_account": True,
38
+ "disable_interactive_fallback": True,
39
+ **kwargs,
40
+ }
41
+ if self._client_id:
42
+ broker_credential_args["client_id"] = self._client_id
43
+ self._broker_credential = broker_credential_class(**broker_credential_args)
44
+ except Exception as ex: # pylint: disable=broad-except
45
+ self._unavailable_message = f"InteractiveBrowserBrokerCredential initialization failed: {ex}"
46
+ else:
47
+ # Determine the specific reason for unavailability
48
+ if broker_credential_class is None:
49
+ self._unavailable_message = (
50
+ "InteractiveBrowserBrokerCredential unavailable. "
51
+ "The 'azure-identity-broker' package is required to use brokered authentication."
52
+ )
53
+ else:
54
+ self._unavailable_message = (
55
+ "InteractiveBrowserBrokerCredential unavailable. "
56
+ "Brokered authentication is only supported on Windows and WSL platforms."
57
+ )
58
+
59
+ def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
60
+ if self._broker_credential:
61
+ return self._broker_credential.get_token(*scopes, **kwargs)
62
+ raise CredentialUnavailableError(message=self._unavailable_message)
63
+
64
+ def get_token_info(self, *scopes: str, **kwargs: Any) -> AccessTokenInfo:
65
+ if self._broker_credential:
66
+ return self._broker_credential.get_token_info(*scopes, **kwargs)
67
+ raise CredentialUnavailableError(message=self._unavailable_message)
68
+
69
+ def __enter__(self) -> "BrokerCredential":
70
+ if self._broker_credential:
71
+ self._broker_credential.__enter__()
72
+ return self
73
+
74
+ def __exit__(self, *args):
75
+ if self._broker_credential:
76
+ self._broker_credential.__exit__(*args)
77
+
78
+ def close(self) -> None:
79
+ self.__exit__()
@@ -23,10 +23,16 @@ _LOGGER = logging.getLogger(__name__)
23
23
  def _get_error_message(history):
24
24
  attempts = []
25
25
  for credential, error in history:
26
+ # Check if credential has a custom name (for DACErrorReporter instances)
27
+ if hasattr(credential, "_credential_name"):
28
+ credential_name = credential._credential_name # pylint: disable=protected-access
29
+ else:
30
+ credential_name = credential.__class__.__name__
31
+
26
32
  if error:
27
- attempts.append("{}: {}".format(credential.__class__.__name__, error))
33
+ attempts.append("{}: {}".format(credential_name, error))
28
34
  else:
29
- attempts.append(credential.__class__.__name__)
35
+ attempts.append(credential_name)
30
36
  return """
31
37
  Attempted credentials:\n\t{}""".format(
32
38
  "\n\t".join(attempts)
@@ -41,7 +47,7 @@ class ChainedTokenCredential:
41
47
  <"https://aka.ms/azsdk/python/identity/credential-chains#chainedtokencredential-overview">`__.
42
48
 
43
49
  :param credentials: credential instances to form the chain
44
- :type credentials: ~azure.core.credentials.TokenCredential
50
+ :type credentials: ~azure.core.credentials.TokenProvider
45
51
 
46
52
  .. admonition:: Example:
47
53