|
32 | 32 | from ..connection import _get_private_bytes_from_file
|
33 | 33 | from ..constants import (
|
34 | 34 | _CONNECTIVITY_ERR_MSG,
|
| 35 | + _OAUTH_DEFAULT_SCOPE, |
35 | 36 | ENV_VAR_EXPERIMENTAL_AUTHENTICATION,
|
36 | 37 | ENV_VAR_PARTNER,
|
37 | 38 | PARAMETER_AUTOCOMMIT,
|
|
51 | 52 | from ..description import PLATFORM, PYTHON_VERSION, SNOWFLAKE_CONNECTOR_VERSION
|
52 | 53 | from ..errorcode import (
|
53 | 54 | ER_CONNECTION_IS_CLOSED,
|
| 55 | + ER_EXPERIMENTAL_AUTHENTICATION_NOT_SUPPORTED, |
54 | 56 | ER_FAILED_TO_CONNECT_TO_DB,
|
55 | 57 | ER_INVALID_VALUE,
|
56 | 58 | ER_INVALID_WIF_SETTINGS,
|
| 59 | + ER_NO_CLIENT_ID, |
57 | 60 | )
|
58 | 61 | from ..network import (
|
59 | 62 | DEFAULT_AUTHENTICATOR,
|
60 | 63 | EXTERNAL_BROWSER_AUTHENTICATOR,
|
61 | 64 | KEY_PAIR_AUTHENTICATOR,
|
62 | 65 | OAUTH_AUTHENTICATOR,
|
| 66 | + OAUTH_AUTHORIZATION_CODE, |
| 67 | + OAUTH_CLIENT_CREDENTIALS, |
63 | 68 | PROGRAMMATIC_ACCESS_TOKEN,
|
64 | 69 | REQUEST_ID,
|
65 | 70 | USR_PWD_MFA_AUTHENTICATOR,
|
|
84 | 89 | AuthByIdToken,
|
85 | 90 | AuthByKeyPair,
|
86 | 91 | AuthByOAuth,
|
| 92 | + AuthByOauthCode, |
| 93 | + AuthByOauthCredentials, |
87 | 94 | AuthByOkta,
|
88 | 95 | AuthByPAT,
|
89 | 96 | AuthByPlugin,
|
@@ -307,6 +314,56 @@ async def __open_connection(self):
|
307 | 314 | timeout=self.login_timeout,
|
308 | 315 | backoff_generator=self._backoff_generator,
|
309 | 316 | )
|
| 317 | + elif self._authenticator == OAUTH_AUTHORIZATION_CODE: |
| 318 | + self._check_experimental_authentication_flag() |
| 319 | + self._check_oauth_required_parameters() |
| 320 | + features = self.oauth_security_features |
| 321 | + if self._role and (self._oauth_scope == ""): |
| 322 | + # if role is known then let's inject it into scope |
| 323 | + self._oauth_scope = _OAUTH_DEFAULT_SCOPE.format(role=self._role) |
| 324 | + self.auth_class = AuthByOauthCode( |
| 325 | + application=self.application, |
| 326 | + client_id=self._oauth_client_id, |
| 327 | + client_secret=self._oauth_client_secret, |
| 328 | + authentication_url=self._oauth_authorization_url.format( |
| 329 | + host=self.host, port=self.port |
| 330 | + ), |
| 331 | + token_request_url=self._oauth_token_request_url.format( |
| 332 | + host=self.host, port=self.port |
| 333 | + ), |
| 334 | + redirect_uri=self._oauth_redirect_uri, |
| 335 | + scope=self._oauth_scope, |
| 336 | + pkce_enabled=features.pkce_enabled, |
| 337 | + token_cache=( |
| 338 | + auth.get_token_cache() |
| 339 | + if self._client_store_temporary_credential |
| 340 | + else None |
| 341 | + ), |
| 342 | + refresh_token_enabled=features.refresh_token_enabled, |
| 343 | + external_browser_timeout=self._external_browser_timeout, |
| 344 | + ) |
| 345 | + elif self._authenticator == OAUTH_CLIENT_CREDENTIALS: |
| 346 | + self._check_experimental_authentication_flag() |
| 347 | + self._check_oauth_required_parameters() |
| 348 | + features = self.oauth_security_features |
| 349 | + if self._role and (self._oauth_scope == ""): |
| 350 | + # if role is known then let's inject it into scope |
| 351 | + self._oauth_scope = _OAUTH_DEFAULT_SCOPE.format(role=self._role) |
| 352 | + self.auth_class = AuthByOauthCredentials( |
| 353 | + application=self.application, |
| 354 | + client_id=self._oauth_client_id, |
| 355 | + client_secret=self._oauth_client_secret, |
| 356 | + token_request_url=self._oauth_token_request_url.format( |
| 357 | + host=self.host, port=self.port |
| 358 | + ), |
| 359 | + scope=self._oauth_scope, |
| 360 | + token_cache=( |
| 361 | + auth.get_token_cache() |
| 362 | + if self._client_store_temporary_credential |
| 363 | + else None |
| 364 | + ), |
| 365 | + refresh_token_enabled=features.refresh_token_enabled, |
| 366 | + ) |
310 | 367 | elif self._authenticator == PROGRAMMATIC_ACCESS_TOKEN:
|
311 | 368 | self.auth_class = AuthByPAT(self._token)
|
312 | 369 | elif self._authenticator == USR_PWD_MFA_AUTHENTICATOR:
|
@@ -1052,3 +1109,37 @@ async def is_valid(self) -> bool:
|
1052 | 1109 | except Exception as e:
|
1053 | 1110 | logger.debug("session could not be validated due to exception: %s", e)
|
1054 | 1111 | return False
|
| 1112 | + |
| 1113 | + def _check_experimental_authentication_flag(self) -> None: |
| 1114 | + if os.getenv(ENV_VAR_EXPERIMENTAL_AUTHENTICATION, "false").lower() != "true": |
| 1115 | + Error.errorhandler_wrapper( |
| 1116 | + self, |
| 1117 | + None, |
| 1118 | + ProgrammingError, |
| 1119 | + { |
| 1120 | + "msg": f"Please set the '{ENV_VAR_EXPERIMENTAL_AUTHENTICATION}' environment variable true to use the '{self._authenticator}' authenticator.", |
| 1121 | + "errno": ER_EXPERIMENTAL_AUTHENTICATION_NOT_SUPPORTED, |
| 1122 | + }, |
| 1123 | + ) |
| 1124 | + |
| 1125 | + def _check_oauth_required_parameters(self) -> None: |
| 1126 | + if self._oauth_client_id is None: |
| 1127 | + Error.errorhandler_wrapper( |
| 1128 | + self, |
| 1129 | + None, |
| 1130 | + ProgrammingError, |
| 1131 | + { |
| 1132 | + "msg": "Oauth code flow requirement 'client_id' is empty", |
| 1133 | + "errno": ER_NO_CLIENT_ID, |
| 1134 | + }, |
| 1135 | + ) |
| 1136 | + if self._oauth_client_secret is None: |
| 1137 | + Error.errorhandler_wrapper( |
| 1138 | + self, |
| 1139 | + None, |
| 1140 | + ProgrammingError, |
| 1141 | + { |
| 1142 | + "msg": "Oauth code flow requirement 'client_secret' is empty", |
| 1143 | + "errno": ER_NO_CLIENT_ID, |
| 1144 | + }, |
| 1145 | + ) |
0 commit comments