snowflake-connector-python 3.16.0__cp313-cp313-win_amd64.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.
- snowflake/connector/__init__.py +96 -0
- snowflake/connector/_query_context_cache.py +276 -0
- snowflake/connector/_sql_util.py +48 -0
- snowflake/connector/_utils.py +70 -0
- snowflake/connector/arrow_context.py +203 -0
- snowflake/connector/auth/__init__.py +53 -0
- snowflake/connector/auth/_auth.py +586 -0
- snowflake/connector/auth/_http_server.py +220 -0
- snowflake/connector/auth/_oauth_base.py +400 -0
- snowflake/connector/auth/by_plugin.py +219 -0
- snowflake/connector/auth/default.py +36 -0
- snowflake/connector/auth/idtoken.py +72 -0
- snowflake/connector/auth/keypair.py +222 -0
- snowflake/connector/auth/no_auth.py +39 -0
- snowflake/connector/auth/oauth.py +49 -0
- snowflake/connector/auth/oauth_code.py +479 -0
- snowflake/connector/auth/oauth_credentials.py +66 -0
- snowflake/connector/auth/okta.py +335 -0
- snowflake/connector/auth/pat.py +39 -0
- snowflake/connector/auth/usrpwdmfa.py +69 -0
- snowflake/connector/auth/webbrowser.py +502 -0
- snowflake/connector/auth/workload_identity.py +94 -0
- snowflake/connector/azure_storage_client.py +276 -0
- snowflake/connector/backoff_policies.py +141 -0
- snowflake/connector/bind_upload_agent.py +89 -0
- snowflake/connector/cache.py +696 -0
- snowflake/connector/compat.py +127 -0
- snowflake/connector/config_manager.py +496 -0
- snowflake/connector/connection.py +2273 -0
- snowflake/connector/connection_diagnostic.py +776 -0
- snowflake/connector/constants.py +442 -0
- snowflake/connector/converter.py +785 -0
- snowflake/connector/converter_issue23517.py +87 -0
- snowflake/connector/converter_null.py +14 -0
- snowflake/connector/converter_snowsql.py +205 -0
- snowflake/connector/cursor.py +1951 -0
- snowflake/connector/dbapi.py +53 -0
- snowflake/connector/description.py +19 -0
- snowflake/connector/direct_file_operation_utils.py +88 -0
- snowflake/connector/encryption_util.py +220 -0
- snowflake/connector/errorcode.py +91 -0
- snowflake/connector/errors.py +615 -0
- snowflake/connector/externals_utils/__init__.py +0 -0
- snowflake/connector/externals_utils/externals_setup.py +27 -0
- snowflake/connector/feature.py +4 -0
- snowflake/connector/file_compression_type.py +118 -0
- snowflake/connector/file_lock.py +72 -0
- snowflake/connector/file_transfer_agent.py +1215 -0
- snowflake/connector/file_util.py +153 -0
- snowflake/connector/gcs_storage_client.py +474 -0
- snowflake/connector/gzip_decoder.py +85 -0
- snowflake/connector/local_storage_client.py +90 -0
- snowflake/connector/log_configuration.py +60 -0
- snowflake/connector/logging_utils/__init__.py +0 -0
- snowflake/connector/logging_utils/filters.py +72 -0
- snowflake/connector/nanoarrow_arrow_iterator.cp313-win_amd64.pyd +0 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +557 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1005 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +71 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +56 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +65 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +256 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
- snowflake/connector/network.py +1297 -0
- snowflake/connector/ocsp_asn1crypto.py +447 -0
- snowflake/connector/ocsp_snowflake.py +1883 -0
- snowflake/connector/options.py +131 -0
- snowflake/connector/pandas_tools.py +732 -0
- snowflake/connector/proxy.py +43 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +786 -0
- snowflake/connector/result_set.py +319 -0
- snowflake/connector/s3_storage_client.py +605 -0
- snowflake/connector/secret_detector.py +181 -0
- snowflake/connector/sf_dirs.py +64 -0
- snowflake/connector/sfbinaryformat.py +35 -0
- snowflake/connector/sfdatetime.py +345 -0
- snowflake/connector/snow_logging.py +144 -0
- snowflake/connector/sqlstate.py +9 -0
- snowflake/connector/ssd_internal_keys.py +30 -0
- snowflake/connector/ssl_wrap_socket.py +134 -0
- snowflake/connector/storage_client.py +478 -0
- snowflake/connector/telemetry.py +249 -0
- snowflake/connector/telemetry_oob.py +543 -0
- snowflake/connector/test_util.py +30 -0
- snowflake/connector/time_util.py +159 -0
- snowflake/connector/token_cache.py +402 -0
- snowflake/connector/tool/__init__.py +0 -0
- snowflake/connector/tool/dump_certs.py +57 -0
- snowflake/connector/tool/dump_ocsp_response.py +139 -0
- snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
- snowflake/connector/tool/probe_connection.py +69 -0
- snowflake/connector/url_util.py +49 -0
- snowflake/connector/util_text.py +301 -0
- snowflake/connector/vendored/__init__.py +3 -0
- snowflake/connector/vendored/requests/LICENSE +175 -0
- snowflake/connector/vendored/requests/__init__.py +169 -0
- snowflake/connector/vendored/requests/__version__.py +14 -0
- snowflake/connector/vendored/requests/_internal_utils.py +50 -0
- snowflake/connector/vendored/requests/adapters.py +537 -0
- snowflake/connector/vendored/requests/api.py +157 -0
- snowflake/connector/vendored/requests/auth.py +315 -0
- snowflake/connector/vendored/requests/certs.py +17 -0
- snowflake/connector/vendored/requests/compat.py +79 -0
- snowflake/connector/vendored/requests/cookies.py +561 -0
- snowflake/connector/vendored/requests/exceptions.py +140 -0
- snowflake/connector/vendored/requests/help.py +134 -0
- snowflake/connector/vendored/requests/hooks.py +33 -0
- snowflake/connector/vendored/requests/models.py +1033 -0
- snowflake/connector/vendored/requests/sessions.py +833 -0
- snowflake/connector/vendored/requests/status_codes.py +128 -0
- snowflake/connector/vendored/requests/structures.py +99 -0
- snowflake/connector/vendored/requests/utils.py +1093 -0
- snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
- snowflake/connector/vendored/urllib3/__init__.py +85 -0
- snowflake/connector/vendored/urllib3/_collections.py +355 -0
- snowflake/connector/vendored/urllib3/_version.py +2 -0
- snowflake/connector/vendored/urllib3/connection.py +572 -0
- snowflake/connector/vendored/urllib3/connectionpool.py +1137 -0
- snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/_appengine_environ.py +36 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/bindings.py +519 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/low_level.py +397 -0
- snowflake/connector/vendored/urllib3/contrib/appengine.py +314 -0
- snowflake/connector/vendored/urllib3/contrib/ntlmpool.py +130 -0
- snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +509 -0
- snowflake/connector/vendored/urllib3/contrib/securetransport.py +920 -0
- snowflake/connector/vendored/urllib3/contrib/socks.py +216 -0
- snowflake/connector/vendored/urllib3/exceptions.py +323 -0
- snowflake/connector/vendored/urllib3/fields.py +274 -0
- snowflake/connector/vendored/urllib3/filepost.py +98 -0
- snowflake/connector/vendored/urllib3/packages/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/packages/backports/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/packages/backports/makefile.py +51 -0
- snowflake/connector/vendored/urllib3/packages/backports/weakref_finalize.py +155 -0
- snowflake/connector/vendored/urllib3/packages/six.py +1076 -0
- snowflake/connector/vendored/urllib3/poolmanager.py +540 -0
- snowflake/connector/vendored/urllib3/request.py +191 -0
- snowflake/connector/vendored/urllib3/response.py +885 -0
- snowflake/connector/vendored/urllib3/util/__init__.py +49 -0
- snowflake/connector/vendored/urllib3/util/connection.py +156 -0
- snowflake/connector/vendored/urllib3/util/proxy.py +57 -0
- snowflake/connector/vendored/urllib3/util/queue.py +22 -0
- snowflake/connector/vendored/urllib3/util/request.py +146 -0
- snowflake/connector/vendored/urllib3/util/response.py +107 -0
- snowflake/connector/vendored/urllib3/util/retry.py +620 -0
- snowflake/connector/vendored/urllib3/util/ssl_.py +495 -0
- snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
- snowflake/connector/vendored/urllib3/util/ssltransport.py +221 -0
- snowflake/connector/vendored/urllib3/util/timeout.py +271 -0
- snowflake/connector/vendored/urllib3/util/url.py +435 -0
- snowflake/connector/vendored/urllib3/util/wait.py +152 -0
- snowflake/connector/version.py +3 -0
- snowflake/connector/wif_util.py +407 -0
- snowflake_connector_python-3.16.0.dist-info/METADATA +1475 -0
- snowflake_connector_python-3.16.0.dist-info/RECORD +246 -0
- snowflake_connector_python-3.16.0.dist-info/WHEEL +5 -0
- snowflake_connector_python-3.16.0.dist-info/entry_points.txt +4 -0
- snowflake_connector_python-3.16.0.dist-info/licenses/LICENSE.txt +202 -0
- snowflake_connector_python-3.16.0.dist-info/licenses/NOTICE +8 -0
- snowflake_connector_python-3.16.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import select
|
|
10
|
+
import socket
|
|
11
|
+
import time
|
|
12
|
+
import urllib.parse
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from types import TracebackType
|
|
15
|
+
|
|
16
|
+
from typing_extensions import Self
|
|
17
|
+
|
|
18
|
+
from ..compat import IS_WINDOWS
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _use_msg_dont_wait() -> bool:
|
|
24
|
+
if os.getenv("SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT", "false").lower() != "true":
|
|
25
|
+
return False
|
|
26
|
+
if IS_WINDOWS:
|
|
27
|
+
logger.warning(
|
|
28
|
+
"Configuration SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT is not available in Windows. Ignoring."
|
|
29
|
+
)
|
|
30
|
+
return False
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _wrap_socket_recv() -> Callable[[socket.socket, int], bytes]:
|
|
35
|
+
dont_wait = _use_msg_dont_wait()
|
|
36
|
+
if dont_wait:
|
|
37
|
+
# WSL containerized environment sometimes causes socket_client.recv to hang indefinetly
|
|
38
|
+
# To avoid this, passing the socket.MSG_DONTWAIT flag which raises BlockingIOError if
|
|
39
|
+
# operation would block
|
|
40
|
+
logger.debug(
|
|
41
|
+
"Will call socket.recv with MSG_DONTWAIT flag due to SNOWFLAKE_AUTH_SOCKET_MSG_DONTWAIT env var"
|
|
42
|
+
)
|
|
43
|
+
socket_recv = (
|
|
44
|
+
(lambda sock, buf_size: socket.socket.recv(sock, buf_size, socket.MSG_DONTWAIT))
|
|
45
|
+
if dont_wait
|
|
46
|
+
else (lambda sock, buf_size: socket.socket.recv(sock, buf_size))
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def socket_recv_checked(sock: socket.socket, buf_size: int) -> bytes:
|
|
50
|
+
raw = socket_recv(sock, buf_size)
|
|
51
|
+
# when running in a containerized environment, socket_client.recv occasionally returns an empty byte array
|
|
52
|
+
# an immediate successive call to socket_client.recv gets the actual data
|
|
53
|
+
if len(raw) == 0:
|
|
54
|
+
raw = socket_recv(sock, buf_size)
|
|
55
|
+
return raw
|
|
56
|
+
|
|
57
|
+
return socket_recv_checked
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AuthHttpServer:
|
|
61
|
+
"""Simple HTTP server to receive callbacks through for auth purposes."""
|
|
62
|
+
|
|
63
|
+
DEFAULT_MAX_ATTEMPTS = 15
|
|
64
|
+
DEFAULT_TIMEOUT = 30.0
|
|
65
|
+
|
|
66
|
+
PORT_BIND_MAX_ATTEMPTS = 10
|
|
67
|
+
PORT_BIND_TIMEOUT = 20.0
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
uri: str,
|
|
72
|
+
buf_size: int = 16384,
|
|
73
|
+
) -> None:
|
|
74
|
+
parsed_uri = urllib.parse.urlparse(uri)
|
|
75
|
+
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
76
|
+
self.buf_size = buf_size
|
|
77
|
+
if os.getenv("SNOWFLAKE_AUTH_SOCKET_REUSE_PORT", "False").lower() == "true":
|
|
78
|
+
if IS_WINDOWS:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"Configuration SNOWFLAKE_AUTH_SOCKET_REUSE_PORT is not available in Windows. Ignoring."
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
|
84
|
+
|
|
85
|
+
port = parsed_uri.port or 0
|
|
86
|
+
for attempt in range(1, self.DEFAULT_MAX_ATTEMPTS + 1):
|
|
87
|
+
try:
|
|
88
|
+
self._socket.bind(
|
|
89
|
+
(
|
|
90
|
+
parsed_uri.hostname,
|
|
91
|
+
port,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
break
|
|
95
|
+
except socket.gaierror as ex:
|
|
96
|
+
logger.error(
|
|
97
|
+
f"Failed to bind authorization callback server to port {port}: {ex}"
|
|
98
|
+
)
|
|
99
|
+
raise
|
|
100
|
+
except OSError as ex:
|
|
101
|
+
if attempt == self.DEFAULT_MAX_ATTEMPTS:
|
|
102
|
+
logger.error(
|
|
103
|
+
f"Failed to bind authorization callback server to port {port}: {ex}"
|
|
104
|
+
)
|
|
105
|
+
raise
|
|
106
|
+
logger.warning(
|
|
107
|
+
f"Attempt {attempt}/{self.DEFAULT_MAX_ATTEMPTS}. "
|
|
108
|
+
f"Failed to bind authorization callback server to port {port}: {ex}"
|
|
109
|
+
)
|
|
110
|
+
time.sleep(self.PORT_BIND_TIMEOUT / self.PORT_BIND_MAX_ATTEMPTS)
|
|
111
|
+
try:
|
|
112
|
+
self._socket.listen(0) # no backlog
|
|
113
|
+
except Exception as ex:
|
|
114
|
+
logger.error(f"Failed to start listening for auth callback: {ex}")
|
|
115
|
+
self.close()
|
|
116
|
+
raise
|
|
117
|
+
port = self._socket.getsockname()[1]
|
|
118
|
+
self._uri = urllib.parse.ParseResult(
|
|
119
|
+
scheme=parsed_uri.scheme,
|
|
120
|
+
netloc=parsed_uri.hostname + ":" + str(port),
|
|
121
|
+
path=parsed_uri.path,
|
|
122
|
+
params=parsed_uri.params,
|
|
123
|
+
query=parsed_uri.query,
|
|
124
|
+
fragment=parsed_uri.fragment,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def url(self) -> str:
|
|
129
|
+
return self._uri.geturl()
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def port(self) -> int:
|
|
133
|
+
return self._uri.port
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def hostname(self) -> str:
|
|
137
|
+
return self._uri.hostname
|
|
138
|
+
|
|
139
|
+
def _try_poll(
|
|
140
|
+
self, attempts: int, attempt_timeout: float | None
|
|
141
|
+
) -> (socket.socket | None, int):
|
|
142
|
+
for attempt in range(attempts):
|
|
143
|
+
read_sockets = select.select([self._socket], [], [], attempt_timeout)[0]
|
|
144
|
+
if read_sockets and read_sockets[0] is not None:
|
|
145
|
+
return self._socket.accept()[0], attempt
|
|
146
|
+
return None, attempts
|
|
147
|
+
|
|
148
|
+
def _try_receive_block(
|
|
149
|
+
self, client_socket: socket.socket, attempts: int, attempt_timeout: float | None
|
|
150
|
+
) -> bytes | None:
|
|
151
|
+
if attempt_timeout is not None:
|
|
152
|
+
client_socket.settimeout(attempt_timeout)
|
|
153
|
+
recv = _wrap_socket_recv()
|
|
154
|
+
for attempt in range(attempts):
|
|
155
|
+
try:
|
|
156
|
+
return recv(client_socket, self.buf_size)
|
|
157
|
+
except BlockingIOError:
|
|
158
|
+
if attempt < attempts - 1:
|
|
159
|
+
cooldown = min(attempt_timeout, 0.25) if attempt_timeout else 0.25
|
|
160
|
+
logger.debug(
|
|
161
|
+
f"BlockingIOError raised from socket.recv on {1 + attempt}/{attempts} attempt."
|
|
162
|
+
f"Waiting for {cooldown} seconds before trying again"
|
|
163
|
+
)
|
|
164
|
+
time.sleep(cooldown)
|
|
165
|
+
except socket.timeout:
|
|
166
|
+
logger.debug(
|
|
167
|
+
f"socket.recv timed out on {1 + attempt}/{attempts} attempt."
|
|
168
|
+
)
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def receive_block(
|
|
172
|
+
self,
|
|
173
|
+
max_attempts: int = None,
|
|
174
|
+
timeout: float | int | None = None,
|
|
175
|
+
) -> (list[str] | None, socket.socket | None):
|
|
176
|
+
if max_attempts is None:
|
|
177
|
+
max_attempts = self.DEFAULT_MAX_ATTEMPTS
|
|
178
|
+
if timeout is None:
|
|
179
|
+
timeout = self.DEFAULT_TIMEOUT
|
|
180
|
+
"""Receive a message with a maximum attempt count and a timeout in seconds, blocking."""
|
|
181
|
+
if not self._socket:
|
|
182
|
+
raise RuntimeError(
|
|
183
|
+
"Operation is not supported, server was already shut down."
|
|
184
|
+
)
|
|
185
|
+
attempt_timeout = timeout / max_attempts if timeout else None
|
|
186
|
+
client_socket, poll_attempts = self._try_poll(max_attempts, attempt_timeout)
|
|
187
|
+
if client_socket is None:
|
|
188
|
+
return None, None
|
|
189
|
+
raw_block = self._try_receive_block(
|
|
190
|
+
client_socket, max_attempts - poll_attempts, attempt_timeout
|
|
191
|
+
)
|
|
192
|
+
if raw_block:
|
|
193
|
+
return raw_block.decode("utf-8").split("\r\n"), client_socket
|
|
194
|
+
try:
|
|
195
|
+
client_socket.shutdown(socket.SHUT_RDWR)
|
|
196
|
+
except OSError:
|
|
197
|
+
pass
|
|
198
|
+
client_socket.close()
|
|
199
|
+
return None, None
|
|
200
|
+
|
|
201
|
+
def close(self) -> None:
|
|
202
|
+
"""Closes the underlying socket.
|
|
203
|
+
After having close() being called the server object cannot be reused.
|
|
204
|
+
"""
|
|
205
|
+
if self._socket:
|
|
206
|
+
self._socket.close()
|
|
207
|
+
self._socket = None
|
|
208
|
+
|
|
209
|
+
def __enter__(self) -> Self:
|
|
210
|
+
"""Context manager."""
|
|
211
|
+
return self
|
|
212
|
+
|
|
213
|
+
def __exit__(
|
|
214
|
+
self,
|
|
215
|
+
exc_type: type[BaseException] | None,
|
|
216
|
+
exc_val: BaseException | None,
|
|
217
|
+
exc_tb: TracebackType | None,
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Context manager with disposing underlying networking objects."""
|
|
220
|
+
self.close()
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import urllib.parse
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
from urllib.error import HTTPError, URLError
|
|
14
|
+
|
|
15
|
+
from ..errorcode import (
|
|
16
|
+
ER_FAILED_TO_REQUEST,
|
|
17
|
+
ER_IDP_CONNECTION_ERROR,
|
|
18
|
+
ER_NO_CLIENT_ID,
|
|
19
|
+
ER_NO_CLIENT_SECRET,
|
|
20
|
+
)
|
|
21
|
+
from ..errors import Error, ProgrammingError
|
|
22
|
+
from ..network import OAUTH_AUTHENTICATOR
|
|
23
|
+
from ..secret_detector import SecretDetector
|
|
24
|
+
from ..token_cache import TokenCache, TokenKey, TokenType
|
|
25
|
+
from ..vendored import urllib3
|
|
26
|
+
from .by_plugin import AuthByPlugin, AuthType
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from .. import SnowflakeConnection
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _OAuthTokensMixin:
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
token_cache: TokenCache | None,
|
|
38
|
+
refresh_token_enabled: bool,
|
|
39
|
+
idp_host: str,
|
|
40
|
+
) -> None:
|
|
41
|
+
self._access_token = None
|
|
42
|
+
self._refresh_token_enabled = refresh_token_enabled
|
|
43
|
+
if self._refresh_token_enabled:
|
|
44
|
+
self._refresh_token = None
|
|
45
|
+
self._token_cache = token_cache
|
|
46
|
+
if self._token_cache:
|
|
47
|
+
logger.debug("token cache is going to be used if needed")
|
|
48
|
+
self._idp_host = idp_host
|
|
49
|
+
self._access_token_key: TokenKey | None = None
|
|
50
|
+
if self._refresh_token_enabled:
|
|
51
|
+
self._refresh_token_key: TokenKey | None = None
|
|
52
|
+
|
|
53
|
+
def _update_cache_keys(self, user: str) -> None:
|
|
54
|
+
if self._token_cache:
|
|
55
|
+
self._user = user
|
|
56
|
+
|
|
57
|
+
def _get_access_token_cache_key(self) -> TokenKey | None:
|
|
58
|
+
return (
|
|
59
|
+
TokenKey(self._user, self._idp_host, TokenType.OAUTH_ACCESS_TOKEN)
|
|
60
|
+
if self._token_cache and self._user
|
|
61
|
+
else None
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def _get_refresh_token_cache_key(self) -> TokenKey | None:
|
|
65
|
+
return (
|
|
66
|
+
TokenKey(self._user, self._idp_host, TokenType.OAUTH_REFRESH_TOKEN)
|
|
67
|
+
if self._refresh_token_enabled and self._token_cache and self._user
|
|
68
|
+
else None
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _pop_cached_token(self, key: TokenKey | None) -> str | None:
|
|
72
|
+
if self._token_cache is None or key is None:
|
|
73
|
+
return None
|
|
74
|
+
return self._token_cache.retrieve(key)
|
|
75
|
+
|
|
76
|
+
def _pop_cached_access_token(self) -> bool:
|
|
77
|
+
"""Retrieves OAuth access token from the token cache if enabled"""
|
|
78
|
+
self._access_token = self._pop_cached_token(self._get_access_token_cache_key())
|
|
79
|
+
return self._access_token is not None
|
|
80
|
+
|
|
81
|
+
def _pop_cached_refresh_token(self) -> bool:
|
|
82
|
+
"""Retrieves OAuth refresh token from the token cache if enabled"""
|
|
83
|
+
if self._refresh_token_enabled:
|
|
84
|
+
self._refresh_token = self._pop_cached_token(
|
|
85
|
+
self._get_refresh_token_cache_key()
|
|
86
|
+
)
|
|
87
|
+
return self._refresh_token is not None
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def _reset_cached_token(self, key: TokenKey | None, token: str | None) -> None:
|
|
91
|
+
if self._token_cache is None or key is None:
|
|
92
|
+
return
|
|
93
|
+
if token:
|
|
94
|
+
self._token_cache.store(key, token)
|
|
95
|
+
else:
|
|
96
|
+
self._token_cache.remove(key)
|
|
97
|
+
|
|
98
|
+
def _reset_access_token(self, access_token: str | None = None) -> None:
|
|
99
|
+
"""Updates OAuth access token both in memory and in the token cache if enabled"""
|
|
100
|
+
logger.debug(
|
|
101
|
+
"resetting access token to %s",
|
|
102
|
+
"*" * len(access_token) if access_token else None,
|
|
103
|
+
)
|
|
104
|
+
self._access_token = access_token
|
|
105
|
+
self._reset_cached_token(self._get_access_token_cache_key(), self._access_token)
|
|
106
|
+
|
|
107
|
+
def _reset_refresh_token(self, refresh_token: str | None = None) -> None:
|
|
108
|
+
"""Updates OAuth refresh token both in memory and in the token cache if necessary"""
|
|
109
|
+
if self._refresh_token_enabled:
|
|
110
|
+
logger.debug(
|
|
111
|
+
"resetting refresh token to %s",
|
|
112
|
+
"*" * len(refresh_token) if refresh_token else None,
|
|
113
|
+
)
|
|
114
|
+
self._refresh_token = refresh_token
|
|
115
|
+
self._reset_cached_token(
|
|
116
|
+
self._get_refresh_token_cache_key(), self._refresh_token
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def _reset_temporary_state(self) -> None:
|
|
120
|
+
self._access_token = None
|
|
121
|
+
if self._refresh_token_enabled:
|
|
122
|
+
self._refresh_token = None
|
|
123
|
+
if self._token_cache:
|
|
124
|
+
self._user = None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class AuthByOAuthBase(AuthByPlugin, _OAuthTokensMixin, ABC):
|
|
128
|
+
"""A base abstract class for OAuth authenticators"""
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
client_id: str,
|
|
133
|
+
client_secret: str,
|
|
134
|
+
token_request_url: str,
|
|
135
|
+
scope: str,
|
|
136
|
+
token_cache: TokenCache | None,
|
|
137
|
+
refresh_token_enabled: bool,
|
|
138
|
+
**kwargs,
|
|
139
|
+
) -> None:
|
|
140
|
+
super().__init__(**kwargs)
|
|
141
|
+
_OAuthTokensMixin.__init__(
|
|
142
|
+
self,
|
|
143
|
+
token_cache=token_cache,
|
|
144
|
+
refresh_token_enabled=refresh_token_enabled,
|
|
145
|
+
idp_host=urllib.parse.urlparse(token_request_url).hostname,
|
|
146
|
+
)
|
|
147
|
+
self._client_id = client_id
|
|
148
|
+
self._client_secret = client_secret
|
|
149
|
+
self._token_request_url = token_request_url
|
|
150
|
+
self._scope = scope
|
|
151
|
+
if refresh_token_enabled:
|
|
152
|
+
logger.debug("oauth refresh token is going to be used if needed")
|
|
153
|
+
self._scope += (" " if self._scope else "") + "offline_access"
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def _request_tokens(
|
|
157
|
+
self,
|
|
158
|
+
*,
|
|
159
|
+
conn: SnowflakeConnection,
|
|
160
|
+
authenticator: str,
|
|
161
|
+
service_name: str | None,
|
|
162
|
+
account: str,
|
|
163
|
+
user: str,
|
|
164
|
+
password: str | None,
|
|
165
|
+
**kwargs: Any,
|
|
166
|
+
) -> (str | None, str | None):
|
|
167
|
+
"""Request new access and optionally refresh tokens from IdP.
|
|
168
|
+
|
|
169
|
+
This function should implement specific tokens querying flow.
|
|
170
|
+
"""
|
|
171
|
+
raise NotImplementedError
|
|
172
|
+
|
|
173
|
+
@abstractmethod
|
|
174
|
+
def _get_oauth_type_id(self) -> str:
|
|
175
|
+
"""Get OAuth specific authenticator id to be passed to Snowflake.
|
|
176
|
+
|
|
177
|
+
This function should return a unique OAuth authenticator id.
|
|
178
|
+
"""
|
|
179
|
+
raise NotImplementedError
|
|
180
|
+
|
|
181
|
+
def reset_secrets(self) -> None:
|
|
182
|
+
logger.debug("resetting secrets")
|
|
183
|
+
self._reset_temporary_state()
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def type_(self) -> AuthType:
|
|
187
|
+
return AuthType.OAUTH
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def assertion_content(self) -> str:
|
|
191
|
+
"""Returns the token."""
|
|
192
|
+
return self._access_token or ""
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _validate_client_credentials_present(
|
|
196
|
+
client_id: str, client_secret: str, connection: SnowflakeConnection
|
|
197
|
+
) -> tuple[str, str]:
|
|
198
|
+
if client_id is None or client_id == "":
|
|
199
|
+
Error.errorhandler_wrapper(
|
|
200
|
+
connection,
|
|
201
|
+
None,
|
|
202
|
+
ProgrammingError,
|
|
203
|
+
{
|
|
204
|
+
"msg": "Oauth code flow requirement 'client_id' is empty",
|
|
205
|
+
"errno": ER_NO_CLIENT_ID,
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
if client_secret is None or client_secret == "":
|
|
209
|
+
Error.errorhandler_wrapper(
|
|
210
|
+
connection,
|
|
211
|
+
None,
|
|
212
|
+
ProgrammingError,
|
|
213
|
+
{
|
|
214
|
+
"msg": "Oauth code flow requirement 'client_secret' is empty",
|
|
215
|
+
"errno": ER_NO_CLIENT_SECRET,
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return client_id, client_secret
|
|
220
|
+
|
|
221
|
+
def reauthenticate(
|
|
222
|
+
self,
|
|
223
|
+
*,
|
|
224
|
+
conn: SnowflakeConnection,
|
|
225
|
+
**kwargs: Any,
|
|
226
|
+
) -> dict[str, bool]:
|
|
227
|
+
self._reset_access_token()
|
|
228
|
+
if self._pop_cached_refresh_token():
|
|
229
|
+
logger.debug(
|
|
230
|
+
"OAuth refresh token is available, try to use it and get a new access token"
|
|
231
|
+
)
|
|
232
|
+
self._do_refresh_token(conn=conn)
|
|
233
|
+
conn.authenticate_with_retry(self)
|
|
234
|
+
return {"success": True}
|
|
235
|
+
|
|
236
|
+
def prepare(
|
|
237
|
+
self,
|
|
238
|
+
*,
|
|
239
|
+
conn: SnowflakeConnection,
|
|
240
|
+
authenticator: str,
|
|
241
|
+
service_name: str | None,
|
|
242
|
+
account: str,
|
|
243
|
+
user: str,
|
|
244
|
+
**kwargs: Any,
|
|
245
|
+
) -> None:
|
|
246
|
+
"""Web Browser based Authentication."""
|
|
247
|
+
logger.debug("authenticating with OAuth authorization code flow")
|
|
248
|
+
self._update_cache_keys(user=user)
|
|
249
|
+
if self._pop_cached_access_token():
|
|
250
|
+
logger.info(
|
|
251
|
+
"OAuth access token is already available in cache, no need to authenticate."
|
|
252
|
+
)
|
|
253
|
+
return
|
|
254
|
+
access_token, refresh_token = self._request_tokens(
|
|
255
|
+
conn=conn,
|
|
256
|
+
authenticator=authenticator,
|
|
257
|
+
service_name=service_name,
|
|
258
|
+
account=account,
|
|
259
|
+
user=user,
|
|
260
|
+
**kwargs,
|
|
261
|
+
)
|
|
262
|
+
self._reset_access_token(access_token)
|
|
263
|
+
self._reset_refresh_token(refresh_token)
|
|
264
|
+
|
|
265
|
+
def update_body(self, body: dict[Any, Any]) -> None:
|
|
266
|
+
"""Used by Auth to update the request that gets sent to /v1/login-request.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
body: existing request dictionary
|
|
270
|
+
"""
|
|
271
|
+
body["data"]["AUTHENTICATOR"] = OAUTH_AUTHENTICATOR
|
|
272
|
+
body["data"]["TOKEN"] = self._access_token
|
|
273
|
+
body["data"]["OAUTH_TYPE"] = self._get_oauth_type_id()
|
|
274
|
+
|
|
275
|
+
def _do_refresh_token(self, conn: SnowflakeConnection) -> None:
|
|
276
|
+
"""If a refresh token is available exchanges it with a new access token.
|
|
277
|
+
Updates self as a side-effect. Needs at lest self._refresh_token and client_id set.
|
|
278
|
+
"""
|
|
279
|
+
if not self._refresh_token_enabled:
|
|
280
|
+
logger.debug("refresh_token feature is disabled")
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
resp = self._get_refresh_token_response(conn)
|
|
284
|
+
if not resp:
|
|
285
|
+
logger.info(
|
|
286
|
+
"failed to exchange the refresh token on a new OAuth access token"
|
|
287
|
+
)
|
|
288
|
+
self._reset_refresh_token()
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
json_resp = json.loads(resp.data.decode())
|
|
293
|
+
self._reset_access_token(json_resp["access_token"])
|
|
294
|
+
if "refresh_token" in json_resp:
|
|
295
|
+
self._reset_refresh_token(json_resp["refresh_token"])
|
|
296
|
+
except (
|
|
297
|
+
json.JSONDecodeError,
|
|
298
|
+
KeyError,
|
|
299
|
+
):
|
|
300
|
+
logger.error(
|
|
301
|
+
"refresh token exchange response did not contain 'access_token'"
|
|
302
|
+
)
|
|
303
|
+
logger.debug(
|
|
304
|
+
"received the following response body when exchanging refresh token: %s",
|
|
305
|
+
SecretDetector.mask_secrets(str(resp.data)),
|
|
306
|
+
)
|
|
307
|
+
self._reset_refresh_token()
|
|
308
|
+
|
|
309
|
+
def _get_refresh_token_response(
|
|
310
|
+
self, conn: SnowflakeConnection
|
|
311
|
+
) -> urllib3.BaseHTTPResponse | None:
|
|
312
|
+
fields = {
|
|
313
|
+
"grant_type": "refresh_token",
|
|
314
|
+
"refresh_token": self._refresh_token,
|
|
315
|
+
}
|
|
316
|
+
if self._scope:
|
|
317
|
+
fields["scope"] = self._scope
|
|
318
|
+
try:
|
|
319
|
+
return urllib3.PoolManager().request_encode_body(
|
|
320
|
+
# TODO: use network pool to gain use of proxy settings and so on
|
|
321
|
+
"POST",
|
|
322
|
+
self._token_request_url,
|
|
323
|
+
encode_multipart=False,
|
|
324
|
+
headers=self._create_token_request_headers(),
|
|
325
|
+
fields=fields,
|
|
326
|
+
)
|
|
327
|
+
except HTTPError as e:
|
|
328
|
+
self._handle_failure(
|
|
329
|
+
conn=conn,
|
|
330
|
+
ret={
|
|
331
|
+
"code": ER_FAILED_TO_REQUEST,
|
|
332
|
+
"message": f"Failed to request new OAuth access token with a refresh token,"
|
|
333
|
+
f" url={e.url}, code={e.code}, reason={e.reason}",
|
|
334
|
+
},
|
|
335
|
+
)
|
|
336
|
+
except URLError as e:
|
|
337
|
+
self._handle_failure(
|
|
338
|
+
conn=conn,
|
|
339
|
+
ret={
|
|
340
|
+
"code": ER_FAILED_TO_REQUEST,
|
|
341
|
+
"message": f"Failed to request new OAuth access token with a refresh token, reason: {e.reason}",
|
|
342
|
+
},
|
|
343
|
+
)
|
|
344
|
+
except Exception:
|
|
345
|
+
self._handle_failure(
|
|
346
|
+
conn=conn,
|
|
347
|
+
ret={
|
|
348
|
+
"code": ER_FAILED_TO_REQUEST,
|
|
349
|
+
"message": "Failed to request new OAuth access token with a refresh token by unknown reason",
|
|
350
|
+
},
|
|
351
|
+
)
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
def _get_request_token_response(
|
|
355
|
+
self,
|
|
356
|
+
connection: SnowflakeConnection,
|
|
357
|
+
fields: dict[str, str],
|
|
358
|
+
) -> (str | None, str | None):
|
|
359
|
+
resp = urllib3.PoolManager().request_encode_body(
|
|
360
|
+
# TODO: use network pool to gain use of proxy settings and so on
|
|
361
|
+
"POST",
|
|
362
|
+
self._token_request_url,
|
|
363
|
+
headers=self._create_token_request_headers(),
|
|
364
|
+
encode_multipart=False,
|
|
365
|
+
fields=fields,
|
|
366
|
+
)
|
|
367
|
+
try:
|
|
368
|
+
logger.debug("OAuth IdP response received, try to parse it")
|
|
369
|
+
json_resp: dict = json.loads(resp.data)
|
|
370
|
+
access_token = json_resp["access_token"]
|
|
371
|
+
refresh_token = json_resp.get("refresh_token")
|
|
372
|
+
return access_token, refresh_token
|
|
373
|
+
except (
|
|
374
|
+
json.JSONDecodeError,
|
|
375
|
+
KeyError,
|
|
376
|
+
):
|
|
377
|
+
logger.error("oauth response invalid, does not contain 'access_token'")
|
|
378
|
+
logger.debug(
|
|
379
|
+
"received the following response body when requesting oauth token: %s",
|
|
380
|
+
SecretDetector.mask_secrets(str(resp.data)),
|
|
381
|
+
)
|
|
382
|
+
self._handle_failure(
|
|
383
|
+
conn=connection,
|
|
384
|
+
ret={
|
|
385
|
+
"code": ER_IDP_CONNECTION_ERROR,
|
|
386
|
+
"message": "Invalid HTTP request from web browser. Idp "
|
|
387
|
+
"authentication could have failed.",
|
|
388
|
+
},
|
|
389
|
+
)
|
|
390
|
+
return None, None
|
|
391
|
+
|
|
392
|
+
def _create_token_request_headers(self) -> dict[str, str]:
|
|
393
|
+
return {
|
|
394
|
+
"Authorization": "Basic "
|
|
395
|
+
+ base64.b64encode(
|
|
396
|
+
f"{self._client_id}:{self._client_secret}".encode()
|
|
397
|
+
).decode(),
|
|
398
|
+
"Accept": "application/json",
|
|
399
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
400
|
+
}
|