py2docfx 0.1.20rc2196756__py3-none-any.whl → 0.1.20rc2245107__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/venv/basevenv/Lib/site-packages/charset_normalizer/api.py +3 -2
- py2docfx/venv/basevenv/Lib/site-packages/charset_normalizer/legacy.py +17 -1
- py2docfx/venv/basevenv/Lib/site-packages/charset_normalizer/version.py +1 -1
- py2docfx/venv/basevenv/Lib/site-packages/requests/__version__.py +2 -2
- py2docfx/venv/basevenv/Lib/site-packages/requests/adapters.py +17 -40
- py2docfx/venv/basevenv/Lib/site-packages/requests/sessions.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/core/_version.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/core/pipeline/policies/_authentication.py +21 -9
- py2docfx/venv/venv1/Lib/site-packages/azure/core/pipeline/policies/_authentication_async.py +21 -9
- py2docfx/venv/venv1/Lib/site-packages/azure/core/pipeline/policies/_retry.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_bearer_token_provider.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/authorization_code.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/azd_cli.py +82 -17
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/azure_cli.py +28 -5
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/azure_powershell.py +28 -4
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/broker.py +79 -0
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/chained.py +9 -3
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/default.py +153 -53
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/imds.py +25 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_credentials/shared_cache.py +12 -5
- 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 +4 -0
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/interactive.py +14 -2
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/pipeline.py +4 -2
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_internal/utils.py +96 -0
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/_version.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_bearer_token_provider.py +3 -3
- 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/azd_cli.py +32 -13
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/azure_cli.py +26 -5
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/azure_powershell.py +13 -2
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/chained.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/azure/identity/aio/_credentials/default.py +120 -55
- 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/shared_cache.py +12 -5
- 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/cffi/__init__.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/cffi/cparser.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/cffi/recompiler.py +5 -5
- py2docfx/venv/venv1/Lib/site-packages/cffi/setuptools_ext.py +13 -0
- py2docfx/venv/venv1/Lib/site-packages/cffi/vengine_cpy.py +3 -0
- py2docfx/venv/venv1/Lib/site-packages/charset_normalizer/api.py +3 -2
- py2docfx/venv/venv1/Lib/site-packages/charset_normalizer/legacy.py +17 -1
- py2docfx/venv/venv1/Lib/site-packages/charset_normalizer/version.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/cryptography/__about__.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/cryptography/__init__.py +0 -13
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/_oid.py +8 -0
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/asn1/__init__.py +10 -0
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/asn1/asn1.py +116 -0
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/backends/openssl/backend.py +3 -9
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi +32 -0
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi +23 -0
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/_rust/x509.pyi +1 -13
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/openssl/_conditional.py +16 -0
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/bindings/openssl/binding.py +16 -1
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/asymmetric/padding.py +0 -2
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/asymmetric/rsa.py +8 -0
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.py +0 -47
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/kdf/hkdf.py +6 -91
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.py +1 -3
- py2docfx/venv/venv1/Lib/site-packages/cryptography/hazmat/primitives/serialization/ssh.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/cryptography/utils.py +0 -2
- py2docfx/venv/venv1/Lib/site-packages/cryptography/x509/name.py +2 -3
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/__init__.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/any_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/api_pb2.py +12 -8
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/compiler/plugin_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/descriptor.py +398 -246
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/descriptor_pb2.py +74 -72
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/descriptor_pool.py +5 -4
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/duration_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/empty_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/field_mask_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/api_implementation.py +0 -6
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/extension_dict.py +3 -3
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/field_mask.py +3 -3
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/python_edition_defaults.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/python_message.py +10 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/internal/type_checkers.py +47 -5
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/json_format.py +55 -32
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/runtime_version.py +6 -26
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/source_context_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/struct_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/text_format.py +30 -19
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/timestamp_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/type_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/google/protobuf/wrappers_pb2.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/pycparser/__init__.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/pycparser/c_generator.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/pycparser/c_lexer.py +14 -0
- py2docfx/venv/venv1/Lib/site-packages/pycparser/c_parser.py +30 -7
- py2docfx/venv/venv1/Lib/site-packages/pycparser/lextab.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/pycparser/yacctab.py +132 -127
- py2docfx/venv/venv1/Lib/site-packages/requests/__version__.py +2 -2
- py2docfx/venv/venv1/Lib/site-packages/requests/adapters.py +17 -40
- py2docfx/venv/venv1/Lib/site-packages/requests/sessions.py +1 -1
- py2docfx/venv/venv1/Lib/site-packages/typing_extensions.py +91 -18
- {py2docfx-0.1.20rc2196756.dist-info → py2docfx-0.1.20rc2245107.dist-info}/METADATA +1 -1
- {py2docfx-0.1.20rc2196756.dist-info → py2docfx-0.1.20rc2245107.dist-info}/RECORD +104 -103
- 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.20rc2196756.dist-info → py2docfx-0.1.20rc2245107.dist-info}/WHEEL +0 -0
- {py2docfx-0.1.20rc2196756.dist-info → py2docfx-0.1.20rc2245107.dist-info}/top_level.txt +0 -0
@@ -369,14 +369,15 @@ def from_bytes(
|
|
369
369
|
# Preparing those fallbacks in case we got nothing.
|
370
370
|
if (
|
371
371
|
enable_fallback
|
372
|
-
and encoding_iana
|
372
|
+
and encoding_iana
|
373
|
+
in ["ascii", "utf_8", specified_encoding, "utf_16", "utf_32"]
|
373
374
|
and not lazy_str_hard_failure
|
374
375
|
):
|
375
376
|
fallback_entry = CharsetMatch(
|
376
377
|
sequences,
|
377
378
|
encoding_iana,
|
378
379
|
threshold,
|
379
|
-
|
380
|
+
bom_or_sig_available,
|
380
381
|
[],
|
381
382
|
decoded_payload,
|
382
383
|
preemptive_declaration=specified_encoding,
|
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any
|
|
4
4
|
from warnings import warn
|
5
5
|
|
6
6
|
from .api import from_bytes
|
7
|
-
from .constant import CHARDET_CORRESPONDENCE
|
7
|
+
from .constant import CHARDET_CORRESPONDENCE, TOO_SMALL_SEQUENCE
|
8
8
|
|
9
9
|
# TODO: remove this check when dropping Python 3.7 support
|
10
10
|
if TYPE_CHECKING:
|
@@ -49,6 +49,22 @@ def detect(
|
|
49
49
|
language = r.language if r is not None and r.language != "Unknown" else ""
|
50
50
|
confidence = 1.0 - r.chaos if r is not None else None
|
51
51
|
|
52
|
+
# automatically lower confidence
|
53
|
+
# on small bytes samples.
|
54
|
+
# https://github.com/jawah/charset_normalizer/issues/391
|
55
|
+
if (
|
56
|
+
confidence is not None
|
57
|
+
and confidence >= 0.9
|
58
|
+
and encoding
|
59
|
+
not in {
|
60
|
+
"utf_8",
|
61
|
+
"ascii",
|
62
|
+
}
|
63
|
+
and r.bom is False # type: ignore[union-attr]
|
64
|
+
and len(byte_str) < TOO_SMALL_SEQUENCE
|
65
|
+
):
|
66
|
+
confidence -= 0.2
|
67
|
+
|
52
68
|
# Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process
|
53
69
|
# but chardet does return 'utf-8-sig' and it is a valid codec name.
|
54
70
|
if r is not None and encoding == "utf_8" and r.bom:
|
@@ -5,8 +5,8 @@
|
|
5
5
|
__title__ = "requests"
|
6
6
|
__description__ = "Python HTTP for Humans."
|
7
7
|
__url__ = "https://requests.readthedocs.io"
|
8
|
-
__version__ = "2.32.
|
9
|
-
__build__ =
|
8
|
+
__version__ = "2.32.5"
|
9
|
+
__build__ = 0x023205
|
10
10
|
__author__ = "Kenneth Reitz"
|
11
11
|
__author_email__ = "me@kennethreitz.org"
|
12
12
|
__license__ = "Apache-2.0"
|
@@ -27,7 +27,6 @@ from urllib3.poolmanager import PoolManager, proxy_from_url
|
|
27
27
|
from urllib3.util import Timeout as TimeoutSauce
|
28
28
|
from urllib3.util import parse_url
|
29
29
|
from urllib3.util.retry import Retry
|
30
|
-
from urllib3.util.ssl_ import create_urllib3_context
|
31
30
|
|
32
31
|
from .auth import _basic_auth_str
|
33
32
|
from .compat import basestring, urlparse
|
@@ -74,19 +73,6 @@ DEFAULT_RETRIES = 0
|
|
74
73
|
DEFAULT_POOL_TIMEOUT = None
|
75
74
|
|
76
75
|
|
77
|
-
try:
|
78
|
-
import ssl # noqa: F401
|
79
|
-
|
80
|
-
_preloaded_ssl_context = create_urllib3_context()
|
81
|
-
_preloaded_ssl_context.load_verify_locations(
|
82
|
-
extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
|
83
|
-
)
|
84
|
-
except ImportError:
|
85
|
-
# Bypass default SSLContext creation when Python
|
86
|
-
# interpreter isn't built with the ssl module.
|
87
|
-
_preloaded_ssl_context = None
|
88
|
-
|
89
|
-
|
90
76
|
def _urllib3_request_context(
|
91
77
|
request: "PreparedRequest",
|
92
78
|
verify: "bool | str | None",
|
@@ -99,19 +85,9 @@ def _urllib3_request_context(
|
|
99
85
|
scheme = parsed_request_url.scheme.lower()
|
100
86
|
port = parsed_request_url.port
|
101
87
|
|
102
|
-
# Determine if we have and should use our default SSLContext
|
103
|
-
# to optimize performance on standard requests.
|
104
|
-
poolmanager_kwargs = getattr(poolmanager, "connection_pool_kw", {})
|
105
|
-
has_poolmanager_ssl_context = poolmanager_kwargs.get("ssl_context")
|
106
|
-
should_use_default_ssl_context = (
|
107
|
-
_preloaded_ssl_context is not None and not has_poolmanager_ssl_context
|
108
|
-
)
|
109
|
-
|
110
88
|
cert_reqs = "CERT_REQUIRED"
|
111
89
|
if verify is False:
|
112
90
|
cert_reqs = "CERT_NONE"
|
113
|
-
elif verify is True and should_use_default_ssl_context:
|
114
|
-
pool_kwargs["ssl_context"] = _preloaded_ssl_context
|
115
91
|
elif isinstance(verify, str):
|
116
92
|
if not os.path.isdir(verify):
|
117
93
|
pool_kwargs["ca_certs"] = verify
|
@@ -314,26 +290,27 @@ class HTTPAdapter(BaseAdapter):
|
|
314
290
|
:param cert: The SSL certificate to verify.
|
315
291
|
"""
|
316
292
|
if url.lower().startswith("https") and verify:
|
317
|
-
|
293
|
+
cert_loc = None
|
318
294
|
|
319
|
-
#
|
320
|
-
# Otherwise, if verify is a boolean, we don't load anything since
|
321
|
-
# the connection will be using a context with the default certificates already loaded,
|
322
|
-
# and this avoids a call to the slow load_verify_locations()
|
295
|
+
# Allow self-specified cert location.
|
323
296
|
if verify is not True:
|
324
|
-
# `verify` must be a str with a path then
|
325
297
|
cert_loc = verify
|
326
298
|
|
327
|
-
|
328
|
-
|
329
|
-
f"Could not find a suitable TLS CA certificate bundle, "
|
330
|
-
f"invalid path: {cert_loc}"
|
331
|
-
)
|
299
|
+
if not cert_loc:
|
300
|
+
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
|
332
301
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
302
|
+
if not cert_loc or not os.path.exists(cert_loc):
|
303
|
+
raise OSError(
|
304
|
+
f"Could not find a suitable TLS CA certificate bundle, "
|
305
|
+
f"invalid path: {cert_loc}"
|
306
|
+
)
|
307
|
+
|
308
|
+
conn.cert_reqs = "CERT_REQUIRED"
|
309
|
+
|
310
|
+
if not os.path.isdir(cert_loc):
|
311
|
+
conn.ca_certs = cert_loc
|
312
|
+
else:
|
313
|
+
conn.ca_cert_dir = cert_loc
|
337
314
|
else:
|
338
315
|
conn.cert_reqs = "CERT_NONE"
|
339
316
|
conn.ca_certs = None
|
@@ -410,7 +387,7 @@ class HTTPAdapter(BaseAdapter):
|
|
410
387
|
``"cert_reqs"`` will be set
|
411
388
|
* If ``verify`` is a string, (i.e., it is a user-specified trust bundle)
|
412
389
|
``"ca_certs"`` will be set if the string is not a directory recognized
|
413
|
-
by :py:func:`os.path.isdir`, otherwise ``"
|
390
|
+
by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be
|
414
391
|
set.
|
415
392
|
* If ``"cert"`` is specified, ``"cert_file"`` will always be set. If
|
416
393
|
``"cert"`` is a tuple with a second item, ``"key_file"`` will also
|
@@ -535,7 +535,7 @@ class Session(SessionRedirectMixin):
|
|
535
535
|
for multipart encoding upload.
|
536
536
|
:param auth: (optional) Auth tuple or callable to enable
|
537
537
|
Basic/Digest/Custom HTTP Auth.
|
538
|
-
:param timeout: (optional) How
|
538
|
+
:param timeout: (optional) How many seconds to wait for the server to send
|
539
539
|
data before giving up, as a float, or a :ref:`(connect timeout,
|
540
540
|
read timeout) <timeouts>` tuple.
|
541
541
|
:type timeout: float or tuple
|
@@ -6,12 +6,14 @@
|
|
6
6
|
import time
|
7
7
|
import base64
|
8
8
|
from typing import TYPE_CHECKING, Optional, TypeVar, MutableMapping, Any, Union, cast
|
9
|
+
|
9
10
|
from azure.core.credentials import (
|
10
11
|
TokenCredential,
|
11
12
|
SupportsTokenInfo,
|
12
13
|
TokenRequestOptions,
|
13
14
|
TokenProvider,
|
14
15
|
)
|
16
|
+
from azure.core.exceptions import HttpResponseError
|
15
17
|
from azure.core.pipeline import PipelineRequest, PipelineResponse
|
16
18
|
from azure.core.pipeline.transport import (
|
17
19
|
HttpResponse as LegacyHttpResponse,
|
@@ -165,7 +167,20 @@ class BearerTokenCredentialPolicy(_BearerTokenCredentialPolicyBase, HTTPPolicy[H
|
|
165
167
|
if response.http_response.status_code == 401:
|
166
168
|
self._token = None # any cached token is invalid
|
167
169
|
if "WWW-Authenticate" in response.http_response.headers:
|
168
|
-
|
170
|
+
try:
|
171
|
+
request_authorized = self.on_challenge(request, response)
|
172
|
+
except Exception as ex:
|
173
|
+
# If the response is streamed, read it so the error message is immediately available to the user.
|
174
|
+
# Otherwise, a generic error message will be given and the user will have to read the response
|
175
|
+
# body to see the actual error.
|
176
|
+
if response.context.options.get("stream"):
|
177
|
+
try:
|
178
|
+
response.http_response.read() # type: ignore
|
179
|
+
except Exception: # pylint:disable=broad-except
|
180
|
+
pass
|
181
|
+
# Raise the exception from the token request with the original 401 response
|
182
|
+
raise ex from HttpResponseError(response=response.http_response)
|
183
|
+
|
169
184
|
if request_authorized:
|
170
185
|
# if we receive a challenge response, we retrieve a new token
|
171
186
|
# which matches the new target. In this case, we don't want to remove
|
@@ -200,14 +215,11 @@ class BearerTokenCredentialPolicy(_BearerTokenCredentialPolicyBase, HTTPPolicy[H
|
|
200
215
|
encoded_claims = get_challenge_parameter(headers, "Bearer", "claims")
|
201
216
|
if not encoded_claims:
|
202
217
|
return False
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
return True
|
209
|
-
except Exception: # pylint:disable=broad-except
|
210
|
-
return False
|
218
|
+
padding_needed = -len(encoded_claims) % 4
|
219
|
+
claims = base64.urlsafe_b64decode(encoded_claims + "=" * padding_needed).decode("utf-8")
|
220
|
+
if claims:
|
221
|
+
self.authorize_request(request, *self._scopes, claims=claims)
|
222
|
+
return True
|
211
223
|
return False
|
212
224
|
|
213
225
|
def on_response(
|
@@ -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
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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.
|
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.
|
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
|
-
|
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,
|
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:
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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
|
-
|
271
|
-
|
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
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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,
|
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:
|
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
|
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,
|
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(
|
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
|
)
|