From 22cd9c42bb5041bde5dc64532091fd8665b1d487 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:12:07 -0700 Subject: [PATCH 01/55] save --- cpp/lib/Authenticator.hpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 4c9dde6690..20bc4e63c5 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -87,6 +87,28 @@ namespace Client static std::vector SHA256(const std::vector &message); }; + class AuthenticatorOKTA : public IAuthenticator + { + public: + AuthenticatorOKTA(SF_CONNECT* conn); + + ~AuthenticatorOKTA(); + + void authenticate(); + + void updateDataMap(cJSON* dataMap); + + private: + SF_CONNECT *m_connection; + std::string m_samlResponse; + + /** + * Extract post back url from samel response. Input is in HTML format. + */ + std::string extractPostBackUrlFromSamlResponse(std::string html); + + }; + } // namespace Client } // namespace Snowflake #endif //PROJECT_AUTHENTICATOR_HPP From ed997445c722da64fd7da956a7c52319d83162b1 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:14:44 -0700 Subject: [PATCH 02/55] save --- CMakeLists.txt | 2 + cacert.pem | 25 ++++ cpp/lib/Authenticator.cpp | 274 +++++++++++++++++++++++++++++++++++- cpp/lib/Authenticator.hpp | 10 +- cpp/util/SFURL.cpp | 20 +++ include/snowflake/SFURL.hpp | 2 + include/snowflake/client.h | 15 +- include/snowflake/version.h | 6 +- lib/client.c | 6 + lib/connection.c | 11 ++ lib/connection.h | 5 + lib/http_perform.c | 14 +- tests/test_connect.c | 19 ++- 13 files changed, 388 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc63924798..8f803391a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,6 +160,8 @@ set (SOURCE_FILES_PUT_GET cpp/StorageClientFactory.hpp cpp/StorageClientFactory.cpp cpp/RemoteStorageRequestOutcome.hpp + cpp/entities.cpp + cpp/entities.hpp cpp/util/Base64.hpp cpp/util/Base64.cpp cpp/util/ByteArrayStreamBuf.cpp diff --git a/cacert.pem b/cacert.pem index 9551dfd830..500ced99eb 100644 --- a/cacert.pem +++ b/cacert.pem @@ -3449,3 +3449,28 @@ TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W HYMfRsCbvUOZ58SWLs5fyQ== -----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIID+TCCAuGgAwIBAgICAVcwDQYJKoZIhvcNAQELBQAwgZQxJTAjBgkqhkiG9w0B +CQEMFmNlcnRhZG1pbkBuZXRza29wZS5jb20xEjAQBgNVBAMMCWNlcnRhZG1pbjES +MBAGA1UECwwJY2VydGFkbWluMRYwFAYDVQQKDA1OZXRza29wZSBJbmMuMREwDwYD +VQQHDAhTYW4gSm9zZTELMAkGA1UECAwCQ0ExCzAJBgNVBAYMAlVTMB4XDTIyMDUx +MjIyNDg0MloXDTMyMDUwOTIyNDg0MlowgZQxJTAjBgkqhkiG9w0BCQEMFmNlcnRh +ZG1pbkBuZXRza29wZS5jb20xEjAQBgNVBAMMCWNlcnRhZG1pbjESMBAGA1UECwwJ +Y2VydGFkbWluMRYwFAYDVQQKDA1OZXRza29wZSBJbmMuMREwDwYDVQQHDAhTYW4g +Sm9zZTELMAkGA1UECAwCQ0ExCzAJBgNVBAYMAlVTMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAq9u+p3tH2OaXIai2hiWFFu81G3QH+lAW9H29BX/emqdK +U7xD0Wc52TOLALDZos3p3hTOeQMIaC5cCrqUbtE9V4gmtceUFJ/bD/lHlVtQZKl4 +HcsRAek8cmwIf3bWkeih9XjzbEYY7ffq5ifkT10OENqzrZO9RryDXQnBB6tALB/I +MY4JEFzK9Vr8eqLdKHEfCAjAsvU2HePOZXImrPEFcttgOsoGTsC98Jk2JKMlsme0 +mH136MPhKa54ZrFhNRx/jLogNB+EsWlDeWD78tjlNBTGuX5QAVcBeoijrlMUX4k7 +cf++fJgUdZSz2k75LEuBMn4pd6sPDsJ5dWppzCMx4QIDAQABo1MwUTAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQVt3Q8rBQqDyEKxxubOEQ84txBTDAfBgNVHSME +GDAWgBQVt3Q8rBQqDyEKxxubOEQ84txBTDANBgkqhkiG9w0BAQsFAAOCAQEAFUpG +UV6iM7N9GQpkW2bGdPr0aobsxAxlFeMS3I6dDrrlTvAqAxmMpxHa4X9ghUybHU8M +6PahBA4K4hJsO5fr6u5HBvvomd6Qad6Ks9DLmhuEBlrj4cRoWyfmGVG0OiObeG8a +QJqEmd3wv3ajn1VFif2s2JGVWpGE7/LJKQcNwbIlVycq++5kjF1sc+RpOCQCSq7G +LMpX5aOQr+KY7I7nCK01OgVDvhf3amRA5ZvgisSREMah+qqjm3u9jj3LMzpOzJkK +6U6Y+TDV1xaumxhTBk7BkbC5spBZfVv8+CA/pv/G8hRvrq++jSJvbWXvz8m5lFT0 +CivcxSKMW68x8vGHbQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index b37507b3b1..aa21f18ba5 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -12,10 +12,13 @@ #include "Authenticator.hpp" #include "../logger/SFLogger.hpp" #include "error.h" +#include "../cpp/entities.hpp" #include #include #include +#include +#include "curl_desc_pool.h" #include @@ -50,8 +53,9 @@ extern "C" { { return AUTH_JWT; } - - return AUTH_UNSUPPORTED; + + return AUTH_OKTA; + //return AUTH_UNSUPPORTED; } SF_STATUS STDCALL auth_initialize(SF_CONNECT * conn) @@ -69,6 +73,11 @@ extern "C" { conn->auth_object = static_cast( new Snowflake::Client::AuthenticatorJWT(conn)); } + if (AUTH_OKTA == auth_type) + { + conn->auth_object = static_cast( + new Snowflake::Client::AuthenticatorOKTA(conn)); + } } catch (...) { @@ -123,6 +132,12 @@ extern "C" { void auth_update_json_body(SF_CONNECT * conn, cJSON* body) { + cJSON* data = snowflake_cJSON_GetObjectItem(body, "data"); + if (!data) + { + data = snowflake_cJSON_CreateObject(); + snowflake_cJSON_AddItemToObject(body, "data", data); + } if (!conn || !conn->auth_object) { return; @@ -175,6 +190,10 @@ extern "C" { { delete static_cast(conn->auth_object); } + if (AUTH_OKTA == auth_type) + { + delete static_cast(conn->auth_object); + } } catch (...) { @@ -278,11 +297,6 @@ namespace Client void AuthenticatorJWT::updateDataMap(cJSON* dataMap) { cJSON* data = snowflake_cJSON_GetObjectItem(dataMap, "data"); - if (!data) - { - data = snowflake_cJSON_CreateObject(); - snowflake_cJSON_AddItemToObject(dataMap, "data", data); - } snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_JWT); @@ -344,5 +358,251 @@ namespace Client return coded; } + + AuthenticatorOKTA::AuthenticatorOKTA( + SF_CONNECT* connection) : m_connection(connection) + { + // nop + } + + AuthenticatorOKTA::~AuthenticatorOKTA() + { + // nop + } + + void AuthenticatorOKTA::authenticate() + { + // 1. get authenticator info + cJSON* respData = getIdpInfo(); + + char* tokenURLStr = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(respData, "tokenUrl")); + char* ssoURLStr = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(respData, "ssoUrl")); + + + // 2. verify ssoUrl and tokenUrl contains same prefix + if (!SFURL::urlHasSamePrefix(tokenURLStr, m_connection->authenticator)) + { + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "The specified authenticator is not supported, " + "authenticator=%s, token url=%s, sso url=%s", + m_connection->authenticator, tokenURLStr, ssoURLStr); + //SF_THROWGEN3_NO_INCIDENT("SFAuthenticatorVerificationFailed", + // authenticatorStr.c_str(), + // "***", "***"); + } + + void* curl_desc; + CURL* curl; + curl_desc = get_curl_desc_from_pool(tokenURLStr, m_connection->proxy, m_connection->no_proxy); + curl = get_curl_from_desc(curl_desc); + + // When renewTimeout < 0, postJson() throws RenewTimeoutException + // for each retry attempt so we can renew the one time token + unsigned retryTimeout = get_login_timeout(m_connection); + using namespace std::chrono; + const auto start = system_clock::now(); + unsigned long elapsedSeconds = 0; + unsigned retriedCount = get_login_retry_count(m_connection); + + // 3. get one time token from okta + cJSON* body = snowflake_cJSON_CreateObject(); + snowflake_cJSON_AddStringToObject(body, "username", m_connection->user); + snowflake_cJSON_AddStringToObject(body, "password", m_connection->password); + + char* s_body = snowflake_cJSON_Print(body); + + // headers for step 4 + // add header for accept format + SF_HEADER* header = sf_header_create(); + header->use_application_json_accept_type = SF_BOOLEAN_TRUE; + if (create_header(m_connection, header, &m_connection->error)) { + printf("Error"); + } + cJSON* resp = NULL; + + if (curl_post_call(m_connection, curl, tokenURLStr, header, s_body, &resp, + &m_connection->error, 0, get_login_retry_count(m_connection), retryTimeout, + 0, 0, false, + false)) + { + printf("%s", snowflake_cJSON_Print(resp)); + char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? + snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : + snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); + auto end = std::chrono::system_clock::now(); + elapsedSeconds = static_cast(duration_cast(end - start).count()); + if (elapsedSeconds >= retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retryTimeout, elapsedSeconds); + + //SF_THROWGEN1("OktaConnectionFailed", "timeout reached"); + } + + // 4. get SAML response + SFURL sso_url = SFURL::parse(ssoURLStr); + sso_url.addQueryParam("onetimetoken", one_time_token); + resp = NULL; + if (curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error)) + { + elapsedSeconds = static_cast(duration_cast(end - start).count()); + if (elapsedSeconds >= retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retryTimeout, elapsedSeconds); + + /* SF_THROWGEN1("OktaConnectionFailed", "timeout reached");*/ + } + m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); + printf("\n%s", m_samlResponse); + } + else + { + auto end = std::chrono::system_clock::now(); + elapsedSeconds = static_cast(duration_cast(end - start).count()); + if (elapsedSeconds >= retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retryTimeout, elapsedSeconds); + + //SF_THROWGEN1("OktaConnectionFailed", "timeout reached"); + } + + CXX_LOG_TRACE("sf", "Connection", "Connect", + "Retry on getting SAML response with one time token renewed for %d times " + "with updated retryTimeout = %d", + retriedCount, retryTimeout - elapsedSeconds); + } + // 5. Validate post_back_url matches Snowflake URL + std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); + std::string server_url = getServerURLSync().toString(); + + if ((!m_connection->disable_saml_url_check) && + (!SFURL::urlHasSamePrefix(post_back_url, server_url))) + { + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "The specified authenticator and destination URL in " + "Saml Assertion did not " + "match, expected=%s, post back=%s", + server_url.c_str(), + post_back_url.c_str()); + //SF_THROWGEN2_LOG_EXCEPTION("SFSamlResponseVerificationFailed", + // "***", + // "***", + // m_connection); + } + + } + else + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, response body=%s", + snowflake_cJSON_Print(resp)); + + // SF_THROWGEN1("OktaConnectionFailed", std::to_string(resp.code())); + + + } + } + + void AuthenticatorOKTA::updateDataMap(cJSON* dataMap) + { + cJSON* data = snowflake_cJSON_GetObjectItem(dataMap, "data"); + snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); + snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); + snowflake_cJSON_DeleteItemFromObject(data, "LOGIN_NAME"); + snowflake_cJSON_DeleteItemFromObject(data, "PASSWORD"); + snowflake_cJSON_DeleteItemFromObject(data, "EXT_AUTHN_DUO_METHOD"); + + snowflake_cJSON_AddStringToObject(data, "RAW_SAML_RESPONSE", m_samlResponse.c_str()); + } + + std::string AuthenticatorOKTA::extractPostBackUrlFromSamlResponse(std::string html) + { + std::size_t form_start = html.find("application); + snowflake_cJSON_AddStringToObject(dataMap, "CLIENT_APP_VERSION", m_connection->application_version); + snowflake_cJSON_AddStringToObject(dataMap, "ACCOUNT_NAME",m_connection->account); + snowflake_cJSON_AddStringToObject(dataMap, "AUTHENTICATOR",m_connection->authenticator); + snowflake_cJSON_AddStringToObject(dataMap, "LOGIN_NAME",m_connection->user); + snowflake_cJSON_AddStringToObject(dataMap, "PORT", m_connection->port); + snowflake_cJSON_AddStringToObject(dataMap, "PROTOCOL", m_connection->protocol); + + cJSON* authnData = snowflake_cJSON_CreateObject(); + cJSON *resp = NULL; + snowflake_cJSON_AddItemReferenceToObject(authnData, "data", dataMap); + + // add headers for account and authentication + SF_HEADER* httpExtraHeaders = sf_header_create(); + if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { + printf("failed"); + } + + bool injectCURLTimeout = false; + unsigned renew_timeout = 0; + unsigned retriedCount = 0; + sf_bool isNewRetry = true; + + char* s_body = snowflake_cJSON_Print(authnData); + + if (request(m_connection, &resp, connectURL, NULL, + 0, s_body, httpExtraHeaders, + POST_REQUEST_TYPE, &m_connection->error, SF_BOOLEAN_FALSE, + renew_timeout, get_login_retry_count(m_connection), get_login_timeout(m_connection), NULL, + NULL, NULL, SF_BOOLEAN_TRUE)) + { + printf("%s", snowflake_cJSON_Print(resp)); + return snowflake_cJSON_GetObjectItem(resp, "data"); + } + else { + cJSON* result = snowflake_cJSON_GetObjectItem(resp, "data"); + CXX_LOG_INFO("sf", "Connection", "getIdpInfo", + "Fail to get authenticator info, response body=%s\n", + snowflake_cJSON_Print(result)); + //SF_THROWGEN1("SFConnectionFailed", snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(result, "code"))); + } + } + + SFURL AuthenticatorOKTA::getServerURLSync() + { + SFURL url = SFURL().scheme(m_connection->protocol) + .host(m_connection->host) + .port(m_connection->port); + if (!m_connection->proxy_with_env) + { + // Set the proxy through curl option which won't affect other connections. + url.setProxy(m_proxySettings); + } + return url; + } + } // namespace Client } // namespace Snowflake diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 20bc4e63c5..0d704577bc 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -16,6 +16,8 @@ #include "snowflake/IBase64.hpp" #include "authenticator.h" #include "cJSON.h" +#include "snowflake/SFURL.hpp" + namespace Snowflake { @@ -99,14 +101,16 @@ namespace Client void updateDataMap(cJSON* dataMap); private: - SF_CONNECT *m_connection; + SF_CONNECT* m_connection; std::string m_samlResponse; + Snowflake::Client::Util::Proxy m_proxySettings; /** * Extract post back url from samel response. Input is in HTML format. - */ + */ std::string extractPostBackUrlFromSamlResponse(std::string html); - + SFURL getServerURLSync(); + cJSON* getIdpInfo(); }; } // namespace Client diff --git a/cpp/util/SFURL.cpp b/cpp/util/SFURL.cpp index 23b75f7273..9d0a15ff22 100644 --- a/cpp/util/SFURL.cpp +++ b/cpp/util/SFURL.cpp @@ -429,5 +429,25 @@ std::string SFURL::toString() m_cacheValid = true; return m_cacheURL; } + +bool SFURL::urlHasSamePrefix(std::string url1, std::string url2) +{ + SFURL parsed_url1 = parse(url1); + SFURL parsed_url2 = parse(url2); + + if (parsed_url1.port() == "" && parsed_url1.scheme() == "https") + { + parsed_url1.port("443"); + } + + if (parsed_url2.port() == "" && parsed_url2.scheme() == "https") + { + parsed_url2.port("443"); + } + + return parsed_url1.scheme() == parsed_url2.scheme() && + parsed_url1.host() == parsed_url2.host() && + parsed_url1.port() == parsed_url2.port(); +} } } diff --git a/include/snowflake/SFURL.hpp b/include/snowflake/SFURL.hpp index a61c764748..bf72ef3d57 100644 --- a/include/snowflake/SFURL.hpp +++ b/include/snowflake/SFURL.hpp @@ -422,6 +422,8 @@ class SFURL return m_proxy; } + static bool urlHasSamePrefix(std::string url1, std::string url2); + private: /** diff --git a/include/snowflake/client.h b/include/snowflake/client.h index abd332f5be..2c1ca4380c 100644 --- a/include/snowflake/client.h +++ b/include/snowflake/client.h @@ -18,7 +18,8 @@ extern "C" { /** * API Name */ -#define SF_API_NAME "C API" +#define SF_API_NAME "ODBC" +//#define SF_API_NAME "JAVASCIRPT" /** * SQLState code length @@ -41,6 +42,12 @@ extern "C" { */ #define SF_AUTHENTICATOR_EXTERNAL_BROWSER "externalbrowser" + /** +* Authenticator, Okta +* This definition will not be required if we implement all authentication. +*/ +#define SF_AUTHENTICATOR_OKTA "okta" + /** * UUID4 length */ @@ -262,11 +269,12 @@ typedef enum SF_ATTRIBUTE { SF_CON_MAX_VARCHAR_SIZE, SF_CON_MAX_BINARY_SIZE, SF_CON_MAX_VARIANT_SIZE, + SF_CON_DISABLE_SAML_URL_CHECK, SF_DIR_QUERY_URL, SF_DIR_QUERY_URL_PARAM, SF_DIR_QUERY_TOKEN, SF_RETRY_ON_CURLE_COULDNT_CONNECT_COUNT, - SF_QUERY_RESULT_TYPE + SF_QUERY_RESULT_TYPE, } SF_ATTRIBUTE; /** @@ -347,6 +355,7 @@ typedef struct SF_CONNECT { // Proxy char * proxy; char * no_proxy; + sf_bool proxy_with_env; // Query Context Cache // the flag of whether to disable qcc, false by default @@ -393,6 +402,8 @@ typedef struct SF_CONNECT { uint64 max_varchar_size; uint64 max_binary_size; uint64 max_variant_size; + + sf_bool disable_saml_url_check; } SF_CONNECT; /** diff --git a/include/snowflake/version.h b/include/snowflake/version.h index b21a93dd34..43c21b34ba 100644 --- a/include/snowflake/version.h +++ b/include/snowflake/version.h @@ -5,6 +5,10 @@ #ifndef SNOWFLAKE_CLIENT_VERSION_H #define SNOWFLAKE_CLIENT_VERSION_H -#define SF_API_VERSION "1.0.14" +//#define SF_API_VERSION "1.0.14" +// TODO: temporarily change to run test +#define SF_API_VERSION "3.0.1" +//#define SF_API_VERSION "1.13.1" + #endif /* SNOWFLAKE_CLIENT_VERSION_H */ diff --git a/lib/client.c b/lib/client.c index 90eb215469..48783e2b50 100644 --- a/lib/client.c +++ b/lib/client.c @@ -1140,6 +1140,9 @@ SF_STATUS STDCALL snowflake_set_attribute( case SF_CON_INCLUDE_RETRY_REASON: sf->include_retry_reason = value ? *((sf_bool *)value) : SF_BOOLEAN_TRUE; break; + case SF_CON_DISABLE_SAML_URL_CHECK: + sf->disable_saml_url_check = value ? *((sf_bool*)value) : SF_BOOLEAN_FALSE; + break; default: SET_SNOWFLAKE_ERROR(&sf->error, SF_STATUS_ERROR_BAD_ATTRIBUTE_TYPE, "Invalid attribute type", @@ -1283,6 +1286,9 @@ SF_STATUS STDCALL snowflake_get_attribute( case SF_CON_MAX_VARIANT_SIZE: *value = &sf->max_variant_size; break; + case SF_CON_DISABLE_SAML_URL_CHECK: + *value = &sf->disable_saml_url_check; + break; default: SET_SNOWFLAKE_ERROR(&sf->error, SF_STATUS_ERROR_BAD_ATTRIBUTE_TYPE, "Invalid attribute type", diff --git a/lib/connection.c b/lib/connection.c index 5176607223..07a629d6b1 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1340,3 +1340,14 @@ int8 get_login_retry_count(SF_CONNECT *sf) { return (int8)get_less_one(sf->retry_on_connect_count, sf->retry_count); } + +sf_bool is_one_time_token_request(cJSON* resp) +{ + return snowflake_cJSON_HasObjectItem(resp, "cookieToken") || snowflake_cJSON_HasObjectItem(resp, "sessionToken"); +} + +sf_bool is_saml_response(char* response) +{ + char* doctype = ""; + return strncmp(response, doctype, strlen(doctype)) == 0; +} diff --git a/lib/connection.h b/lib/connection.h index d27cf9d6b9..05362084d2 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -618,6 +618,11 @@ int8 get_login_retry_count(SF_CONNECT *sf); */ int64 get_retry_timeout(SF_CONNECT *sf); +sf_bool is_one_time_token_request(cJSON* resp); + +sf_bool is_saml_response(char* response); + + #ifdef __cplusplus } #endif diff --git a/lib/http_perform.c b/lib/http_perform.c index 292427b38b..c90d0bc697 100644 --- a/lib/http_perform.c +++ b/lib/http_perform.c @@ -484,9 +484,21 @@ sf_bool STDCALL http_perform(CURL *curl, } snowflake_cJSON_Delete(*json); *json = NULL; - *json = snowflake_cJSON_Parse(buffer.buffer); + if (is_saml_response(buffer.buffer)) + { + *json = snowflake_cJSON_CreateObject(); + snowflake_cJSON_AddRawToObject(*json, "data", buffer.buffer); + snowflake_cJSON_AddNullToObject(*json, "code"); + } + else + { + *json = snowflake_cJSON_Parse(buffer.buffer); + } if (*json) { ret = SF_BOOLEAN_TRUE; + if (is_one_time_token_request(*json)) { + snowflake_cJSON_AddNullToObject(*json, "code"); + } } else { SET_SNOWFLAKE_ERROR(error, SF_STATUS_ERROR_BAD_JSON, "Unable to parse JSON text response.", diff --git a/tests/test_connect.c b/tests/test_connect.c index 2b67e40e4b..c206dd5c10 100644 --- a/tests/test_connect.c +++ b/tests/test_connect.c @@ -33,9 +33,14 @@ void test_connect_with_minimum_parameters(void **unused) { SF_CONNECT *sf = snowflake_init(); snowflake_set_attribute(sf, SF_CON_ACCOUNT, getenv("SNOWFLAKE_TEST_ACCOUNT")); - snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_USER")); + //snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_USER")); + //snowflake_set_attribute(sf, SF_CON_PASSWORD, + // getenv("SNOWFLAKE_TEST_PASSWORD")); + snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_OKTA_USER")); snowflake_set_attribute(sf, SF_CON_PASSWORD, - getenv("SNOWFLAKE_TEST_PASSWORD")); + getenv("SNOWFLAKE_TEST_OKTA_PASSWORD")); + snowflake_set_attribute(sf, SF_CON_AUTHENTICATOR, + getenv("SNOWFLAKE_TEST_AUTHENTICATOR")); char *host, *port, *protocol; host = getenv("SNOWFLAKE_TEST_HOST"); if (host) { @@ -50,7 +55,7 @@ void test_connect_with_minimum_parameters(void **unused) { snowflake_set_attribute(sf, SF_CON_PROTOCOL, protocol); } - SF_STATUS status = snowflake_connect(sf); + SF_STATUS status = snowflake_connect(sf); if (status != SF_STATUS_SUCCESS) { dump_error(&(sf->error)); } @@ -235,13 +240,13 @@ void test_connect_with_proxy(void **unused) { int main(void) { initialize_test(SF_BOOLEAN_FALSE); const struct CMUnitTest tests[] = { - cmocka_unit_test(test_null_sf_connect), - cmocka_unit_test(test_no_connection_parameters), + //cmocka_unit_test(test_null_sf_connect), + //cmocka_unit_test(test_no_connection_parameters), cmocka_unit_test(test_connect_with_minimum_parameters), - cmocka_unit_test(test_connect_with_full_parameters), +/* cmocka_unit_test(test_connect_with_full_parameters), cmocka_unit_test(test_connect_with_ocsp_cache_server_off), cmocka_unit_test(test_connect_with_ocsp_cache_server_on), - cmocka_unit_test(test_connect_with_proxy), + cmocka_unit_test(test_connect_with_proxy),*/ }; int ret = cmocka_run_group_tests(tests, NULL, NULL); snowflake_global_term(); From 37bca3e04d43442423983d2d21211f787630a9fc Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:38:13 -0700 Subject: [PATCH 03/55] implemented the okta authentication --- cpp/lib/Authenticator.cpp | 218 +++++++----------- cpp/lib/Authenticator.hpp | 1 - include/snowflake/Exceptions.hpp | 53 +++-- lib/connection.c | 61 +++++ lib/connection.h | 1 + tests/CMakeLists.txt | 2 +- ...st_mfa_connect.c => test_manual_connect.c} | 40 +++- tests/test_unit_sfurl.cpp | 9 + 8 files changed, 228 insertions(+), 157 deletions(-) rename tests/{test_mfa_connect.c => test_manual_connect.c} (73%) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index aa21f18ba5..a4d2f345ce 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -19,6 +19,7 @@ #include #include #include "curl_desc_pool.h" +#include "snowflake/Exceptions.hpp" #include @@ -39,6 +40,12 @@ throw Snowflake::Client::Jwt::JwtException(msg); \ } +#define OKTA_THROW(err) \ +{ \ + throw Snowflake::Client::Exception::OktaException(err); \ +} + + // wrapper functions for C extern "C" { @@ -373,12 +380,15 @@ namespace Client void AuthenticatorOKTA::authenticate() { // 1. get authenticator info - cJSON* respData = getIdpInfo(); - + cJSON* respData = NULL; + if (!getIdpInfo(m_connection, &respData)) + { + OKTA_THROW(&m_connection->error); + } + SF_ERROR_STRUCT *err = &m_connection->error; char* tokenURLStr = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(respData, "tokenUrl")); char* ssoURLStr = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(respData, "ssoUrl")); - // 2. verify ssoUrl and tokenUrl contains same prefix if (!SFURL::urlHasSamePrefix(tokenURLStr, m_connection->authenticator)) { @@ -386,9 +396,8 @@ namespace Client "The specified authenticator is not supported, " "authenticator=%s, token url=%s, sso url=%s", m_connection->authenticator, tokenURLStr, ssoURLStr); - //SF_THROWGEN3_NO_INCIDENT("SFAuthenticatorVerificationFailed", - // authenticatorStr.c_str(), - // "***", "***"); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); + OKTA_THROW(err); } void* curl_desc; @@ -396,11 +405,11 @@ namespace Client curl_desc = get_curl_desc_from_pool(tokenURLStr, m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); - // When renewTimeout < 0, postJson() throws RenewTimeoutException + // When renewTimeout < 0, throws Exception // for each retry attempt so we can renew the one time token unsigned retryTimeout = get_login_timeout(m_connection); using namespace std::chrono; - const auto start = system_clock::now(); + time_t start = time(NULL); unsigned long elapsedSeconds = 0; unsigned retriedCount = get_login_retry_count(m_connection); @@ -415,98 +424,89 @@ namespace Client // add header for accept format SF_HEADER* header = sf_header_create(); header->use_application_json_accept_type = SF_BOOLEAN_TRUE; - if (create_header(m_connection, header, &m_connection->error)) { - printf("Error"); + if (!create_header(m_connection, header, &m_connection->error)) { + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "Failed to created the header for the okta authentication", SF_SQLSTATE_GENERAL_ERROR); + OKTA_THROW(err); } cJSON* resp = NULL; - if (curl_post_call(m_connection, curl, tokenURLStr, header, s_body, &resp, + if (!curl_post_call(m_connection, curl, tokenURLStr, header, s_body, &resp, &m_connection->error, 0, get_login_retry_count(m_connection), retryTimeout, 0, 0, false, false)) { - printf("%s", snowflake_cJSON_Print(resp)); - char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? - snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : - snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); - auto end = std::chrono::system_clock::now(); - elapsedSeconds = static_cast(duration_cast(end - start).count()); + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, response body=%s", + snowflake_cJSON_Print(resp)); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); + OKTA_THROW(err); + } + + char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? + snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : + snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); + elapsedSeconds = time(NULL) - start; + if (elapsedSeconds >= retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retryTimeout, elapsedSeconds); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + OKTA_THROW(err); + } + + // 4. get SAML response + SFURL sso_url = SFURL::parse(ssoURLStr); + sso_url.addQueryParam("onetimetoken", one_time_token); + resp = NULL; + if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error)) + { + elapsedSeconds = time(NULL) - start; if (elapsedSeconds >= retryTimeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", retryTimeout, elapsedSeconds); - - //SF_THROWGEN1("OktaConnectionFailed", "timeout reached"); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + OKTA_THROW(err); } - // 4. get SAML response - SFURL sso_url = SFURL::parse(ssoURLStr); - sso_url.addQueryParam("onetimetoken", one_time_token); - resp = NULL; - if (curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error)) - { - elapsedSeconds = static_cast(duration_cast(end - start).count()); - if (elapsedSeconds >= retryTimeout) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retryTimeout, elapsedSeconds); - - /* SF_THROWGEN1("OktaConnectionFailed", "timeout reached");*/ - } - m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); - printf("\n%s", m_samlResponse); - } - else - { - auto end = std::chrono::system_clock::now(); - elapsedSeconds = static_cast(duration_cast(end - start).count()); - if (elapsedSeconds >= retryTimeout) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retryTimeout, elapsedSeconds); - - //SF_THROWGEN1("OktaConnectionFailed", "timeout reached"); - } - - CXX_LOG_TRACE("sf", "Connection", "Connect", - "Retry on getting SAML response with one time token renewed for %d times " - "with updated retryTimeout = %d", - retriedCount, retryTimeout - elapsedSeconds); - } - // 5. Validate post_back_url matches Snowflake URL - std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); - std::string server_url = getServerURLSync().toString(); - - if ((!m_connection->disable_saml_url_check) && - (!SFURL::urlHasSamePrefix(post_back_url, server_url))) - { - CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "The specified authenticator and destination URL in " - "Saml Assertion did not " - "match, expected=%s, post back=%s", - server_url.c_str(), - post_back_url.c_str()); - //SF_THROWGEN2_LOG_EXCEPTION("SFSamlResponseVerificationFailed", - // "***", - // "***", - // m_connection); - } - + CXX_LOG_TRACE("sf", "Connection", "Connect", + "Retry on getting SAML response with one time token renewed for %d times " + "with updated retryTimeout = %d", + retriedCount, retryTimeout - elapsedSeconds); } - else + elapsedSeconds = time(NULL) - start; + if (elapsedSeconds >= retryTimeout) { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, response body=%s", - snowflake_cJSON_Print(resp)); - - // SF_THROWGEN1("OktaConnectionFailed", std::to_string(resp.code())); + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retryTimeout, elapsedSeconds); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + OKTA_THROW(err); + } + m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); + // 5. Validate post_back_url matches Snowflake URL + std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); + std::string server_url = getServerURLSync().toString(); + if ((!m_connection->disable_saml_url_check) && + (!SFURL::urlHasSamePrefix(post_back_url, server_url))) + { + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "The specified authenticator and destination URL in " + "Saml Assertion did not " + "match, expected=%s, post back=%s", + server_url.c_str(), + post_back_url.c_str()); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + OKTA_THROW(err); } - } + sf_header_destroy(header); + free_curl_desc(curl_desc); + snowflake_cJSON_Delete(body); + } void AuthenticatorOKTA::updateDataMap(cJSON* dataMap) { @@ -523,12 +523,11 @@ namespace Client std::string AuthenticatorOKTA::extractPostBackUrlFromSamlResponse(std::string html) { std::size_t form_start = html.find("application); - snowflake_cJSON_AddStringToObject(dataMap, "CLIENT_APP_VERSION", m_connection->application_version); - snowflake_cJSON_AddStringToObject(dataMap, "ACCOUNT_NAME",m_connection->account); - snowflake_cJSON_AddStringToObject(dataMap, "AUTHENTICATOR",m_connection->authenticator); - snowflake_cJSON_AddStringToObject(dataMap, "LOGIN_NAME",m_connection->user); - snowflake_cJSON_AddStringToObject(dataMap, "PORT", m_connection->port); - snowflake_cJSON_AddStringToObject(dataMap, "PROTOCOL", m_connection->protocol); - - cJSON* authnData = snowflake_cJSON_CreateObject(); - cJSON *resp = NULL; - snowflake_cJSON_AddItemReferenceToObject(authnData, "data", dataMap); - - // add headers for account and authentication - SF_HEADER* httpExtraHeaders = sf_header_create(); - if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { - printf("failed"); - } - - bool injectCURLTimeout = false; - unsigned renew_timeout = 0; - unsigned retriedCount = 0; - sf_bool isNewRetry = true; - - char* s_body = snowflake_cJSON_Print(authnData); - - if (request(m_connection, &resp, connectURL, NULL, - 0, s_body, httpExtraHeaders, - POST_REQUEST_TYPE, &m_connection->error, SF_BOOLEAN_FALSE, - renew_timeout, get_login_retry_count(m_connection), get_login_timeout(m_connection), NULL, - NULL, NULL, SF_BOOLEAN_TRUE)) - { - printf("%s", snowflake_cJSON_Print(resp)); - return snowflake_cJSON_GetObjectItem(resp, "data"); - } - else { - cJSON* result = snowflake_cJSON_GetObjectItem(resp, "data"); - CXX_LOG_INFO("sf", "Connection", "getIdpInfo", - "Fail to get authenticator info, response body=%s\n", - snowflake_cJSON_Print(result)); - //SF_THROWGEN1("SFConnectionFailed", snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(result, "code"))); - } - } - SFURL AuthenticatorOKTA::getServerURLSync() { SFURL url = SFURL().scheme(m_connection->protocol) @@ -603,6 +552,5 @@ namespace Client } return url; } - } // namespace Client } // namespace Snowflake diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 0d704577bc..bfddb8ede8 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -110,7 +110,6 @@ namespace Client */ std::string extractPostBackUrlFromSamlResponse(std::string html); SFURL getServerURLSync(); - cJSON* getIdpInfo(); }; } // namespace Client diff --git a/include/snowflake/Exceptions.hpp b/include/snowflake/Exceptions.hpp index ea32f0a6b0..69863c40d1 100644 --- a/include/snowflake/Exceptions.hpp +++ b/include/snowflake/Exceptions.hpp @@ -8,31 +8,50 @@ #include #include "client.h" -class SnowflakeException: public std::exception { -public: - SnowflakeException(SF_ERROR_STRUCT *error); +namespace Snowflake +{ + namespace Client + { + namespace Exception + { + class SnowflakeException : public std::exception { + public: + SnowflakeException(SF_ERROR_STRUCT* error); - const char * what() const throw(); + const char* what() const throw(); - SF_STATUS code(); + SF_STATUS code(); - const char *sqlstate(); + const char* sqlstate(); - const char *msg(); + const char* msg(); - const char *sfqid(); + const char* sfqid(); - const char *file(); + const char* file(); - int line(); + int line(); -protected: - SF_ERROR_STRUCT *error; -}; + protected: + SF_ERROR_STRUCT* error; + }; -class GeneralException: public SnowflakeException { -public: - GeneralException(SF_ERROR_STRUCT *error) : SnowflakeException(error) {}; -}; + class GeneralException : public SnowflakeException { + public: + GeneralException(SF_ERROR_STRUCT* error) : SnowflakeException(error) {}; + }; + struct OktaException : public std::exception + { + OktaException(SF_ERROR_STRUCT* error) : message_(error->msg) {} + const char* what() const noexcept + { + return message_.c_str(); + } + + std::string message_; + }; + } + } +} #endif //SNOWFLAKECLIENT_EXCEPTIONS_HPP diff --git a/lib/connection.c b/lib/connection.c index 07a629d6b1..88c62c191d 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1351,3 +1351,64 @@ sf_bool is_saml_response(char* response) char* doctype = ""; return strncmp(response, doctype, strlen(doctype)) == 0; } + +sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) +{ + sf_bool ret = SF_BOOLEAN_FALSE; + cJSON* dataMap = snowflake_cJSON_CreateObject(); + // connection URL + const char* connectURL = "/session/authenticator-request"; + SF_ERROR_STRUCT* err = &sf->error; + + // login info as a json post body + snowflake_cJSON_AddStringToObject(dataMap, "CLIENT_APP_ID", "ODBC"); + snowflake_cJSON_AddStringToObject(dataMap, "CLIENT_APP_VERSION", "3.4.1"); + snowflake_cJSON_AddStringToObject(dataMap, "ACCOUNT_NAME", sf->account); + snowflake_cJSON_AddStringToObject(dataMap, "AUTHENTICATOR", sf->authenticator); + snowflake_cJSON_AddStringToObject(dataMap, "LOGIN_NAME", sf->user); + snowflake_cJSON_AddStringToObject(dataMap, "PORT", sf->port); + snowflake_cJSON_AddStringToObject(dataMap, "PROTOCOL", sf->protocol); + + cJSON* authnData = snowflake_cJSON_CreateObject(); + cJSON* resp = NULL; + snowflake_cJSON_AddItemReferenceToObject(authnData, "data", dataMap); + + // add headers for account and authentication + SF_HEADER* httpExtraHeaders = sf_header_create(); + httpExtraHeaders->use_application_json_accept_type = TRUE; + if (!create_header(sf, httpExtraHeaders, &sf->error)) { + log_trace("sf", "AuthenticatorOKTA", + "getIdpInfo", + "Failed to create the header for the request to get the token URL and the SSO URL"); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + ret = SF_BOOLEAN_FALSE; + goto cleanup; + } + + unsigned renew_timeout = 0; + unsigned retried_count = 0; + char* s_body = snowflake_cJSON_Print(authnData); + + if (!request(sf, &resp, connectURL, NULL, + 0, s_body, httpExtraHeaders, + POST_REQUEST_TYPE, &sf->error, SF_BOOLEAN_FALSE, + renew_timeout, get_login_retry_count(sf), get_login_timeout(sf), NULL, + retried_count, NULL, SF_BOOLEAN_TRUE)) + { + log_info("sf", "Connection", "getIdpInfo", + "Fail to get authenticator info, response body=%s\n", + snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data"))); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); + ret = SF_BOOLEAN_FALSE; + goto cleanup; + } + *json = snowflake_cJSON_GetObjectItem(resp, "data"); + ret = SF_BOOLEAN_TRUE; + +cleanup: + sf_header_destroy(httpExtraHeaders); + snowflake_cJSON_Delete(authnData); + + return ret; +} + diff --git a/lib/connection.h b/lib/connection.h index 05362084d2..fc0abe89eb 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -622,6 +622,7 @@ sf_bool is_one_time_token_request(cJSON* resp); sf_bool is_saml_response(char* response); +sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json); #ifdef __cplusplus } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 20b09d62eb..7e8c394d54 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,7 +47,7 @@ SET(TESTS_C # test_lob # test_stats # MFA connection is only able to run testing manually. - test_mfa_connect + test_manual_connect ) SET(TESTS_CXX diff --git a/tests/test_mfa_connect.c b/tests/test_manual_connect.c similarity index 73% rename from tests/test_mfa_connect.c rename to tests/test_manual_connect.c index fbc69e1f1d..d23a2fb35b 100644 --- a/tests/test_mfa_connect.c +++ b/tests/test_manual_connect.c @@ -117,14 +117,48 @@ void test_connect_with_duo_passcodeInPassword(void** unused) snowflake_term(sf); } +void test_okta_connect(void** unused) { + SF_CONNECT* sf = snowflake_init(); + sf_bool disable_saml_url_check = SF_BOOLEAN_TRUE; + sf->disable_saml_url_check = &disable_saml_url_check; + snowflake_set_attribute(sf, SF_CON_ACCOUNT, + getenv("SNOWFLAKE_TEST_ACCOUNT")); + snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_OKTA_USERNAME")); + snowflake_set_attribute(sf, SF_CON_PASSWORD, + getenv("SNOWFLAKE_TEST_OKTA_PASSWORD")); + snowflake_set_attribute(sf, SF_CON_AUTHENTICATOR, + getenv("SNOWFLAKE_TEST_AUTHENTICATOR")); + char* host, * port, * protocol; + host = getenv("SNOWFLAKE_TEST_HOST"); + if (host) { + snowflake_set_attribute(sf, SF_CON_HOST, host); + } + port = getenv("SNOWFLAKE_TEST_PORT"); + if (port) { + snowflake_set_attribute(sf, SF_CON_PORT, port); + } + protocol = getenv("SNOWFLAKE_TEST_PROTOCOL"); + if (protocol) { + snowflake_set_attribute(sf, SF_CON_PROTOCOL, protocol); + } + + SF_STATUS status = snowflake_connect(sf); + if (status != SF_STATUS_SUCCESS) { + dump_error(&(sf->error)); + } + assert_int_equal(status, SF_STATUS_SUCCESS); + snowflake_term(sf); +} + int main(void) { initialize_test(SF_BOOLEAN_FALSE); const struct CMUnitTest tests[] = { - cmocka_unit_test(test_connect_with_duo_push), - cmocka_unit_test(test_connect_with_duo_passcode), + //cmocka_unit_test(test_connect_with_duo_push), + //cmocka_unit_test(test_connect_with_duo_passcode), //Need to run this testing separately. - //cmocka_unit_test(test_connect_with_duo_passcodeInPassword), + //cmocka_unit_test(test_connect_with_duo_passcodeInPassword) + cmocka_unit_test(test_okta_connect), }; int ret = cmocka_run_group_tests(tests, NULL, NULL); snowflake_global_term(); diff --git a/tests/test_unit_sfurl.cpp b/tests/test_unit_sfurl.cpp index 762d3f7280..4fc9e8d781 100644 --- a/tests/test_unit_sfurl.cpp +++ b/tests/test_unit_sfurl.cpp @@ -140,6 +140,14 @@ void test_error_parse(void ** unused) REQUIRE_THROWS(SFURL::parse("http://github.com:80ad")); } +void test_url_has_same_prefix(void** unused) +{ + char* url1 = "https://okta.snowflake.com"; + char* url2 = "http://snowflake.com/request_path"; + + assert_true(SFURL::urlHasSamePrefix(url1, url2)); +} + static int gr_setup(void **unused) { initialize_test(SF_BOOLEAN_FALSE); @@ -152,6 +160,7 @@ int main(void) { cmocka_unit_test(test_parse_authority), cmocka_unit_test(test_construct), cmocka_unit_test(test_error_parse), + cmocka_unit_test(test_url_has_same_prefix), }; int ret = cmocka_run_group_tests(tests, gr_setup, NULL); return ret; From b76617c35de7313d9f79c3df62a4499510e028bd Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:40:19 -0700 Subject: [PATCH 04/55] comment out the connection manual testing --- tests/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7e8c394d54..f8c1b6bd1b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,8 +46,8 @@ SET(TESTS_C # will enable lob test when the change on server side will be published # test_lob # test_stats -# MFA connection is only able to run testing manually. - test_manual_connect +# MFA and Okta connections are only able to run testing manually. +# test_manual_connect ) SET(TESTS_CXX From 62048543aa66b374c005044aa9ff16ec2d647f3f Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:47:25 -0700 Subject: [PATCH 05/55] added comments and updated codes --- cacert.pem | 25 ------------- cpp/lib/Authenticator.cpp | 60 ++++++++++++++------------------ cpp/lib/Authenticator.hpp | 1 - include/snowflake/Exceptions.hpp | 6 ++-- include/snowflake/SFURL.hpp | 7 ++++ include/snowflake/client.h | 9 +---- lib/connection.c | 1 + lib/connection.h | 9 +++++ tests/test_connect.c | 19 ++++------ tests/test_manual_connect.c | 2 +- 10 files changed, 57 insertions(+), 82 deletions(-) diff --git a/cacert.pem b/cacert.pem index 500ced99eb..4c22ba6f72 100644 --- a/cacert.pem +++ b/cacert.pem @@ -3448,29 +3448,4 @@ TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj 1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W HYMfRsCbvUOZ58SWLs5fyQ== ------END CERTIFICATE----- - ------BEGIN CERTIFICATE----- -MIID+TCCAuGgAwIBAgICAVcwDQYJKoZIhvcNAQELBQAwgZQxJTAjBgkqhkiG9w0B -CQEMFmNlcnRhZG1pbkBuZXRza29wZS5jb20xEjAQBgNVBAMMCWNlcnRhZG1pbjES -MBAGA1UECwwJY2VydGFkbWluMRYwFAYDVQQKDA1OZXRza29wZSBJbmMuMREwDwYD -VQQHDAhTYW4gSm9zZTELMAkGA1UECAwCQ0ExCzAJBgNVBAYMAlVTMB4XDTIyMDUx -MjIyNDg0MloXDTMyMDUwOTIyNDg0MlowgZQxJTAjBgkqhkiG9w0BCQEMFmNlcnRh -ZG1pbkBuZXRza29wZS5jb20xEjAQBgNVBAMMCWNlcnRhZG1pbjESMBAGA1UECwwJ -Y2VydGFkbWluMRYwFAYDVQQKDA1OZXRza29wZSBJbmMuMREwDwYDVQQHDAhTYW4g -Sm9zZTELMAkGA1UECAwCQ0ExCzAJBgNVBAYMAlVTMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAq9u+p3tH2OaXIai2hiWFFu81G3QH+lAW9H29BX/emqdK -U7xD0Wc52TOLALDZos3p3hTOeQMIaC5cCrqUbtE9V4gmtceUFJ/bD/lHlVtQZKl4 -HcsRAek8cmwIf3bWkeih9XjzbEYY7ffq5ifkT10OENqzrZO9RryDXQnBB6tALB/I -MY4JEFzK9Vr8eqLdKHEfCAjAsvU2HePOZXImrPEFcttgOsoGTsC98Jk2JKMlsme0 -mH136MPhKa54ZrFhNRx/jLogNB+EsWlDeWD78tjlNBTGuX5QAVcBeoijrlMUX4k7 -cf++fJgUdZSz2k75LEuBMn4pd6sPDsJ5dWppzCMx4QIDAQABo1MwUTAPBgNVHRMB -Af8EBTADAQH/MB0GA1UdDgQWBBQVt3Q8rBQqDyEKxxubOEQ84txBTDAfBgNVHSME -GDAWgBQVt3Q8rBQqDyEKxxubOEQ84txBTDANBgkqhkiG9w0BAQsFAAOCAQEAFUpG -UV6iM7N9GQpkW2bGdPr0aobsxAxlFeMS3I6dDrrlTvAqAxmMpxHa4X9ghUybHU8M -6PahBA4K4hJsO5fr6u5HBvvomd6Qad6Ks9DLmhuEBlrj4cRoWyfmGVG0OiObeG8a -QJqEmd3wv3ajn1VFif2s2JGVWpGE7/LJKQcNwbIlVycq++5kjF1sc+RpOCQCSq7G -LMpX5aOQr+KY7I7nCK01OgVDvhf3amRA5ZvgisSREMah+qqjm3u9jj3LMzpOzJkK -6U6Y+TDV1xaumxhTBk7BkbC5spBZfVv8+CA/pv/G8hRvrq++jSJvbWXvz8m5lFT0 -CivcxSKMW68x8vGHbQ== -----END CERTIFICATE----- \ No newline at end of file diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index a4d2f345ce..3660d08468 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -35,17 +35,11 @@ #define strcasecmp _stricmp #endif -#define JWT_THROW(msg) \ +#define AUTH_THROW(msg) \ { \ - throw Snowflake::Client::Jwt::JwtException(msg); \ + throw Snowflake::Client::Exception::AuthException(msg); \ } -#define OKTA_THROW(err) \ -{ \ - throw Snowflake::Client::Exception::OktaException(err); \ -} - - // wrapper functions for C extern "C" { @@ -62,7 +56,6 @@ extern "C" { } return AUTH_OKTA; - //return AUTH_UNSUPPORTED; } SF_STATUS STDCALL auth_initialize(SF_CONNECT * conn) @@ -128,9 +121,11 @@ extern "C" { } catch (...) { - SET_SNOWFLAKE_ERROR(&conn->error, SF_STATUS_ERROR_GENERAL, - "authentication failed", - SF_SQLSTATE_GENERAL_ERROR); + if (!&conn->error) { + SET_SNOWFLAKE_ERROR(&conn->error, SF_STATUS_ERROR_GENERAL, + "authentication failed", + SF_SQLSTATE_GENERAL_ERROR); + } return SF_STATUS_ERROR_GENERAL; } @@ -230,7 +225,7 @@ namespace Client if (sf_fopen(&file, privateKeyFile.c_str(), "r") == nullptr) { CXX_LOG_ERROR("Failed to open private key file. Errno: %d", errno); - JWT_THROW("Failed to open private key file"); + AUTH_THROW("Failed to open private key file"); } m_privKey = PEM_read_PrivateKey(file, nullptr, nullptr, (void *)passcode.c_str()); @@ -238,7 +233,7 @@ namespace Client if (m_privKey == nullptr) { CXX_LOG_ERROR("Loading private key from %s failed", privateKeyFile.c_str()); - JWT_THROW("Marshaling private key failed"); + AUTH_THROW("Marshaling private key failed"); } } @@ -318,7 +313,7 @@ namespace Client if (size < 0) { CXX_LOG_ERROR("Fail to extract public key"); - JWT_THROW("Public Key extract failed"); + AUTH_THROW("Public Key extract failed"); } std::vector pubKeyBytes(out, out + size); OPENSSL_free(out); @@ -337,19 +332,19 @@ namespace Client if (mdctx == nullptr) { CXX_LOG_ERROR("EVP context create failed."); - JWT_THROW("EVP context create failed"); + AUTH_THROW("EVP context create failed"); } if (1 != EVP_DigestInit_ex(mdctx.get(), EVP_sha256(), nullptr)) { CXX_LOG_ERROR("Digest Init failed."); - JWT_THROW("Digest Init failed"); + AUTH_THROW("Digest Init failed"); } if (1 != EVP_DigestUpdate(mdctx.get(), message.data(), message.size())) { CXX_LOG_ERROR("Digest update failed."); - JWT_THROW("Digest update failed"); + AUTH_THROW("Digest update failed"); } std::vector coded(EVP_MD_size(EVP_sha256())); @@ -358,7 +353,7 @@ namespace Client if (1 != EVP_DigestFinal_ex(mdctx.get(), (unsigned char *)coded.data(), &code_size)) { CXX_LOG_ERROR("Digest final failed."); - JWT_THROW("Digest final failed"); + AUTH_THROW("Digest final failed"); } coded.resize(code_size); @@ -383,8 +378,9 @@ namespace Client cJSON* respData = NULL; if (!getIdpInfo(m_connection, &respData)) { - OKTA_THROW(&m_connection->error); + AUTH_THROW(&m_connection->error); } + SF_ERROR_STRUCT *err = &m_connection->error; char* tokenURLStr = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(respData, "tokenUrl")); char* ssoURLStr = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(respData, "ssoUrl")); @@ -397,7 +393,7 @@ namespace Client "authenticator=%s, token url=%s, sso url=%s", m_connection->authenticator, tokenURLStr, ssoURLStr); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); - OKTA_THROW(err); + AUTH_THROW(err->msg); } void* curl_desc; @@ -426,7 +422,7 @@ namespace Client header->use_application_json_accept_type = SF_BOOLEAN_TRUE; if (!create_header(m_connection, header, &m_connection->error)) { SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "Failed to created the header for the okta authentication", SF_SQLSTATE_GENERAL_ERROR); - OKTA_THROW(err); + AUTH_THROW(err); } cJSON* resp = NULL; @@ -439,7 +435,7 @@ namespace Client "Fail to get SAML response, response body=%s", snowflake_cJSON_Print(resp)); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); - OKTA_THROW(err); + AUTH_THROW(err); } char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? @@ -452,7 +448,7 @@ namespace Client "Fail to get SAML response, timeout reached: %d, elapsed time: %d", retryTimeout, elapsedSeconds); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - OKTA_THROW(err); + AUTH_THROW(err); } // 4. get SAML response @@ -468,7 +464,7 @@ namespace Client "Fail to get SAML response, timeout reached: %d, elapsed time: %d", retryTimeout, elapsedSeconds); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - OKTA_THROW(err); + AUTH_THROW(err); } CXX_LOG_TRACE("sf", "Connection", "Connect", @@ -476,6 +472,7 @@ namespace Client "with updated retryTimeout = %d", retriedCount, retryTimeout - elapsedSeconds); } + elapsedSeconds = time(NULL) - start; if (elapsedSeconds >= retryTimeout) { @@ -483,10 +480,10 @@ namespace Client "Fail to get SAML response, timeout reached: %d, elapsed time: %d", retryTimeout, elapsedSeconds); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - OKTA_THROW(err); + AUTH_THROW(err); } - m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); + m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); // 5. Validate post_back_url matches Snowflake URL std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); std::string server_url = getServerURLSync().toString(); @@ -501,8 +498,9 @@ namespace Client server_url.c_str(), post_back_url.c_str()); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - OKTA_THROW(err); + AUTH_THROW(err); } + sf_header_destroy(header); free_curl_desc(curl_desc); snowflake_cJSON_Delete(body); @@ -545,11 +543,7 @@ namespace Client SFURL url = SFURL().scheme(m_connection->protocol) .host(m_connection->host) .port(m_connection->port); - if (!m_connection->proxy_with_env) - { - // Set the proxy through curl option which won't affect other connections. - url.setProxy(m_proxySettings); - } + return url; } } // namespace Client diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index bfddb8ede8..1a7976ac4f 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -18,7 +18,6 @@ #include "cJSON.h" #include "snowflake/SFURL.hpp" - namespace Snowflake { namespace Client diff --git a/include/snowflake/Exceptions.hpp b/include/snowflake/Exceptions.hpp index 69863c40d1..08b527897e 100644 --- a/include/snowflake/Exceptions.hpp +++ b/include/snowflake/Exceptions.hpp @@ -41,9 +41,11 @@ namespace Snowflake GeneralException(SF_ERROR_STRUCT* error) : SnowflakeException(error) {}; }; - struct OktaException : public std::exception + struct AuthException : public std::exception { - OktaException(SF_ERROR_STRUCT* error) : message_(error->msg) {} + AuthException(SF_ERROR_STRUCT* error) : message_(error->msg) {} + AuthException(const std::string& message) : message_(message) {} + const char* what() const noexcept { return message_.c_str(); diff --git a/include/snowflake/SFURL.hpp b/include/snowflake/SFURL.hpp index bf72ef3d57..9f81dcb0fd 100644 --- a/include/snowflake/SFURL.hpp +++ b/include/snowflake/SFURL.hpp @@ -422,6 +422,13 @@ class SFURL return m_proxy; } + /** + * Verify that if two urls has same prefix (protocl + host + port) + * @param url1 + * @param url2 + * + * @return true if same prefix otherwise false + */ static bool urlHasSamePrefix(std::string url1, std::string url2); private: diff --git a/include/snowflake/client.h b/include/snowflake/client.h index 2c1ca4380c..88dd76df01 100644 --- a/include/snowflake/client.h +++ b/include/snowflake/client.h @@ -18,8 +18,7 @@ extern "C" { /** * API Name */ -#define SF_API_NAME "ODBC" -//#define SF_API_NAME "JAVASCIRPT" +#define SF_API_NAME "C API" /** * SQLState code length @@ -42,12 +41,6 @@ extern "C" { */ #define SF_AUTHENTICATOR_EXTERNAL_BROWSER "externalbrowser" - /** -* Authenticator, Okta -* This definition will not be required if we implement all authentication. -*/ -#define SF_AUTHENTICATOR_OKTA "okta" - /** * UUID4 length */ diff --git a/lib/connection.c b/lib/connection.c index dcdda17b61..bb4fa5a4ef 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1357,6 +1357,7 @@ sf_bool is_saml_response(char* response) return strncmp(response, doctype, strlen(doctype)) == 0; } + sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) { sf_bool ret = SF_BOOLEAN_FALSE; diff --git a/lib/connection.h b/lib/connection.h index aa8c8ec1ca..66c2ed0542 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -623,10 +623,19 @@ int64 get_retry_timeout(SF_CONNECT *sf); */ uint64 sf_get_current_time_millis(); +/* +* a function to check that this request is whether the one time token request. +*/ sf_bool is_one_time_token_request(cJSON* resp); +/* +* a function to check that this request is whether the response includes the SAML response. +*/ sf_bool is_saml_response(char* response); +/* +* Get IdpInfo for OKTA and SAML 2.0 application +*/ sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json); #ifdef __cplusplus diff --git a/tests/test_connect.c b/tests/test_connect.c index c206dd5c10..2b67e40e4b 100644 --- a/tests/test_connect.c +++ b/tests/test_connect.c @@ -33,14 +33,9 @@ void test_connect_with_minimum_parameters(void **unused) { SF_CONNECT *sf = snowflake_init(); snowflake_set_attribute(sf, SF_CON_ACCOUNT, getenv("SNOWFLAKE_TEST_ACCOUNT")); - //snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_USER")); - //snowflake_set_attribute(sf, SF_CON_PASSWORD, - // getenv("SNOWFLAKE_TEST_PASSWORD")); - snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_OKTA_USER")); + snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_USER")); snowflake_set_attribute(sf, SF_CON_PASSWORD, - getenv("SNOWFLAKE_TEST_OKTA_PASSWORD")); - snowflake_set_attribute(sf, SF_CON_AUTHENTICATOR, - getenv("SNOWFLAKE_TEST_AUTHENTICATOR")); + getenv("SNOWFLAKE_TEST_PASSWORD")); char *host, *port, *protocol; host = getenv("SNOWFLAKE_TEST_HOST"); if (host) { @@ -55,7 +50,7 @@ void test_connect_with_minimum_parameters(void **unused) { snowflake_set_attribute(sf, SF_CON_PROTOCOL, protocol); } - SF_STATUS status = snowflake_connect(sf); + SF_STATUS status = snowflake_connect(sf); if (status != SF_STATUS_SUCCESS) { dump_error(&(sf->error)); } @@ -240,13 +235,13 @@ void test_connect_with_proxy(void **unused) { int main(void) { initialize_test(SF_BOOLEAN_FALSE); const struct CMUnitTest tests[] = { - //cmocka_unit_test(test_null_sf_connect), - //cmocka_unit_test(test_no_connection_parameters), + cmocka_unit_test(test_null_sf_connect), + cmocka_unit_test(test_no_connection_parameters), cmocka_unit_test(test_connect_with_minimum_parameters), -/* cmocka_unit_test(test_connect_with_full_parameters), + cmocka_unit_test(test_connect_with_full_parameters), cmocka_unit_test(test_connect_with_ocsp_cache_server_off), cmocka_unit_test(test_connect_with_ocsp_cache_server_on), - cmocka_unit_test(test_connect_with_proxy),*/ + cmocka_unit_test(test_connect_with_proxy), }; int ret = cmocka_run_group_tests(tests, NULL, NULL); snowflake_global_term(); diff --git a/tests/test_manual_connect.c b/tests/test_manual_connect.c index d23a2fb35b..51baa98755 100644 --- a/tests/test_manual_connect.c +++ b/tests/test_manual_connect.c @@ -157,7 +157,7 @@ int main(void) //cmocka_unit_test(test_connect_with_duo_push), //cmocka_unit_test(test_connect_with_duo_passcode), //Need to run this testing separately. - //cmocka_unit_test(test_connect_with_duo_passcodeInPassword) + //cmocka_unit_test(test_connect_with_duo_passcodeInPassword), cmocka_unit_test(test_okta_connect), }; int ret = cmocka_run_group_tests(tests, NULL, NULL); From 73468ecf699b011bb8f47cf29109539f5fd639f1 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:04:32 -0700 Subject: [PATCH 06/55] add entities files --- cpp/entities.cpp | 416 +++++++++++++++++++++++++++++++++++++++++++++++ cpp/entities.hpp | 28 ++++ 2 files changed, 444 insertions(+) create mode 100644 cpp/entities.cpp create mode 100644 cpp/entities.hpp diff --git a/cpp/entities.cpp b/cpp/entities.cpp new file mode 100644 index 0000000000..494e409911 --- /dev/null +++ b/cpp/entities.cpp @@ -0,0 +1,416 @@ +/** + * Copyright 2012, 2016 Christoph Gärtner + * Distributed under the Boost Software License, Version 1.0 + */ + +#include "entities.hpp" + +#include +#include +#include +#include "snowflake/SF_CRTFunctionSafe.h" + +#define UNICODE_MAX 0x10FFFFul + +namespace Snowflake +{ + namespace Client + { + + static const char* const NAMED_ENTITIES[][2] = { + {"AElig;", "Æ"}, + {"Aacute;", "Á"}, + {"Acirc;", "Â"}, + {"Agrave;", "À"}, + {"Alpha;", "Α"}, + {"Aring;", "Å"}, + {"Atilde;", "Ã"}, + {"Auml;", "Ä"}, + {"Beta;", "Β"}, + {"Ccedil;", "Ç"}, + {"Chi;", "Χ"}, + {"Dagger;", "‡"}, + {"Delta;", "Δ"}, + {"ETH;", "Ð"}, + {"Eacute;", "É"}, + {"Ecirc;", "Ê"}, + {"Egrave;", "È"}, + {"Epsilon;", "Ε"}, + {"Eta;", "Η"}, + {"Euml;", "Ë"}, + {"Gamma;", "Γ"}, + {"Iacute;", "Í"}, + {"Icirc;", "Î"}, + {"Igrave;", "Ì"}, + {"Iota;", "Ι"}, + {"Iuml;", "Ï"}, + {"Kappa;", "Κ"}, + {"Lambda;", "Λ"}, + {"Mu;", "Μ"}, + {"Ntilde;", "Ñ"}, + {"Nu;", "Ν"}, + {"OElig;", "Œ"}, + {"Oacute;", "Ó"}, + {"Ocirc;", "Ô"}, + {"Ograve;", "Ò"}, + {"Omega;", "Ω"}, + {"Omicron;", "Ο"}, + {"Oslash;", "Ø"}, + {"Otilde;", "Õ"}, + {"Ouml;", "Ö"}, + {"Phi;", "Φ"}, + {"Pi;", "Π"}, + {"Prime;", "″"}, + {"Psi;", "Ψ"}, + {"Rho;", "Ρ"}, + {"Scaron;", "Š"}, + {"Sigma;", "Σ"}, + {"THORN;", "Þ"}, + {"Tau;", "Τ"}, + {"Theta;", "Θ"}, + {"Uacute;", "Ú"}, + {"Ucirc;", "Û"}, + {"Ugrave;", "Ù"}, + {"Upsilon;", "Υ"}, + {"Uuml;", "Ü"}, + {"Xi;", "Ξ"}, + {"Yacute;", "Ý"}, + {"Yuml;", "Ÿ"}, + {"Zeta;", "Ζ"}, + {"aacute;", "á"}, + {"acirc;", "â"}, + {"acute;", "´"}, + {"aelig;", "æ"}, + {"agrave;", "à"}, + {"alefsym;", "ℵ"}, + {"alpha;", "α"}, + {"amp;", "&"}, + {"and;", "∧"}, + {"ang;", "∠"}, + {"apos;", "'"}, + {"aring;", "å"}, + {"asymp;", "≈"}, + {"atilde;", "ã"}, + {"auml;", "ä"}, + {"bdquo;", "„"}, + {"beta;", "β"}, + {"brvbar;", "¦"}, + {"bull;", "•"}, + {"cap;", "∩"}, + {"ccedil;", "ç"}, + {"cedil;", "¸"}, + {"cent;", "¢"}, + {"chi;", "χ"}, + {"circ;", "ˆ"}, + {"clubs;", "♣"}, + {"cong;", "≅"}, + {"copy;", "©"}, + {"crarr;", "↵"}, + {"cup;", "∪"}, + {"curren;", "¤"}, + {"dArr;", "⇓"}, + {"dagger;", "†"}, + {"darr;", "↓"}, + {"deg;", "°"}, + {"delta;", "δ"}, + {"diams;", "♦"}, + {"divide;", "÷"}, + {"eacute;", "é"}, + {"ecirc;", "ê"}, + {"egrave;", "è"}, + {"empty;", "∅"}, + {"emsp;", "\xE2\x80\x83"}, + {"ensp;", "\xE2\x80\x82"}, + {"epsilon;", "ε"}, + {"equiv;", "≡"}, + {"eta;", "η"}, + {"eth;", "ð"}, + {"euml;", "ë"}, + {"euro;", "€"}, + {"exist;", "∃"}, + {"fnof;", "ƒ"}, + {"forall;", "∀"}, + {"frac12;", "½"}, + {"frac14;", "¼"}, + {"frac34;", "¾"}, + {"frasl;", "⁄"}, + {"gamma;", "γ"}, + {"ge;", "≥"}, + {"gt;", ">"}, + {"hArr;", "⇔"}, + {"harr;", "↔"}, + {"hearts;", "♥"}, + {"hellip;", "…"}, + {"iacute;", "í"}, + {"icirc;", "î"}, + {"iexcl;", "¡"}, + {"igrave;", "ì"}, + {"image;", "ℑ"}, + {"infin;", "∞"}, + {"int;", "∫"}, + {"iota;", "ι"}, + {"iquest;", "¿"}, + {"isin;", "∈"}, + {"iuml;", "ï"}, + {"kappa;", "κ"}, + {"lArr;", "⇐"}, + {"lambda;", "λ"}, + {"lang;", "〈"}, + {"laquo;", "«"}, + {"larr;", "←"}, + {"lceil;", "⌈"}, + {"ldquo;", "“"}, + {"le;", "≤"}, + {"lfloor;", "⌊"}, + {"lowast;", "∗"}, + {"loz;", "◊"}, + {"lrm;", "\xE2\x80\x8E"}, + {"lsaquo;", "‹"}, + {"lsquo;", "‘"}, + {"lt;", "<"}, + {"macr;", "¯"}, + {"mdash;", "—"}, + {"micro;", "µ"}, + {"middot;", "·"}, + {"minus;", "−"}, + {"mu;", "μ"}, + {"nabla;", "∇"}, + {"nbsp;", "\xC2\xA0"}, + {"ndash;", "–"}, + {"ne;", "≠"}, + {"ni;", "∋"}, + {"not;", "¬"}, + {"notin;", "∉"}, + {"nsub;", "⊄"}, + {"ntilde;", "ñ"}, + {"nu;", "ν"}, + {"oacute;", "ó"}, + {"ocirc;", "ô"}, + {"oelig;", "œ"}, + {"ograve;", "ò"}, + {"oline;", "‾"}, + {"omega;", "ω"}, + {"omicron;", "ο"}, + {"oplus;", "⊕"}, + {"or;", "∨"}, + {"ordf;", "ª"}, + {"ordm;", "º"}, + {"oslash;", "ø"}, + {"otilde;", "õ"}, + {"otimes;", "⊗"}, + {"ouml;", "ö"}, + {"para;", "¶"}, + {"part;", "∂"}, + {"permil;", "‰"}, + {"perp;", "⊥"}, + {"phi;", "φ"}, + {"pi;", "π"}, + {"piv;", "ϖ"}, + {"plusmn;", "±"}, + {"pound;", "£"}, + {"prime;", "′"}, + {"prod;", "∏"}, + {"prop;", "∝"}, + {"psi;", "ψ"}, + {"quot;", "\""}, + {"rArr;", "⇒"}, + {"radic;", "√"}, + {"rang;", "〉"}, + {"raquo;", "»"}, + {"rarr;", "→"}, + {"rceil;", "⌉"}, + {"rdquo;", "”"}, + {"real;", "ℜ"}, + {"reg;", "®"}, + {"rfloor;", "⌋"}, + {"rho;", "ρ"}, + {"rlm;", "\xE2\x80\x8F"}, + {"rsaquo;", "›"}, + {"rsquo;", "’"}, + {"sbquo;", "‚"}, + {"scaron;", "š"}, + {"sdot;", "⋅"}, + {"sect;", "§"}, + {"shy;", "\xC2\xAD"}, + {"sigma;", "σ"}, + {"sigmaf;", "ς"}, + {"sim;", "∼"}, + {"spades;", "♠"}, + {"sub;", "⊂"}, + {"sube;", "⊆"}, + {"sum;", "∑"}, + {"sup1;", "¹"}, + {"sup2;", "²"}, + {"sup3;", "³"}, + {"sup;", "⊃"}, + {"supe;", "⊇"}, + {"szlig;", "ß"}, + {"tau;", "τ"}, + {"there4;", "∴"}, + {"theta;", "θ"}, + {"thetasym;", "ϑ"}, + {"thinsp;", "\xE2\x80\x89"}, + {"thorn;", "þ"}, + {"tilde;", "˜"}, + {"times;", "×"}, + {"trade;", "™"}, + {"uArr;", "⇑"}, + {"uacute;", "ú"}, + {"uarr;", "↑"}, + {"ucirc;", "û"}, + {"ugrave;", "ù"}, + {"uml;", "¨"}, + {"upsih;", "ϒ"}, + {"upsilon;", "υ"}, + {"uuml;", "ü"}, + {"weierp;", "℘"}, + {"xi;", "ξ"}, + {"yacute;", "ý"}, + {"yen;", "¥"}, + {"yuml;", "ÿ"}, + {"zeta;", "ζ"}, + {"zwj;", "\xE2\x80\x8D"}, + {"zwnj;", "\xE2\x80\x8C"} + }; + + static int cmp(const void* key, const void* value) + { + return strncmp((const char*)key, *(const char* const*)value, + strlen(*(const char* const*)value)); + } + +#if defined(_WIN32) || defined(_WIN64) + static int cmp_s(void* pvlocale, const void* key, const void* value) + { + //UNUSED(pvlocale); + return cmp(key, value); + } +#endif + + static const char* get_named_entity(const char* name) + { +#if defined(_WIN32) || defined(_WIN64) + const char* const* entity = (const char* const*)bsearch_s(name, + NAMED_ENTITIES, + sizeof NAMED_ENTITIES / sizeof * NAMED_ENTITIES, + sizeof * NAMED_ENTITIES, cmp_s, + NULL); +#else + const char* const* entity = (const char* const*)bsearch(name, + NAMED_ENTITIES, + sizeof NAMED_ENTITIES / sizeof * NAMED_ENTITIES, + sizeof * NAMED_ENTITIES, cmp); +#endif + + return entity ? entity[1] : NULL; + } + + static size_t putc_utf8(unsigned long cp, char* buffer) + { + unsigned char* bytes = (unsigned char*)buffer; + + if (cp <= 0x007Ful) + { + bytes[0] = (unsigned char)cp; + return 1; + } + + if (cp <= 0x07FFul) + { + bytes[1] = (unsigned char)((2 << 6) | (cp & 0x3F)); + bytes[0] = (unsigned char)((6 << 5) | (cp >> 6)); + return 2; + } + + if (cp <= 0xFFFFul) + { + bytes[2] = (unsigned char)((2 << 6) | (cp & 0x3F)); + bytes[1] = (unsigned char)((2 << 6) | ((cp >> 6) & 0x3F)); + bytes[0] = (unsigned char)((14 << 4) | (cp >> 12)); + return 3; + } + + if (cp <= 0x10FFFFul) + { + bytes[3] = (unsigned char)((2 << 6) | (cp & 0x3F)); + bytes[2] = (unsigned char)((2 << 6) | ((cp >> 6) & 0x3F)); + bytes[1] = (unsigned char)((2 << 6) | ((cp >> 12) & 0x3F)); + bytes[0] = (unsigned char)((30 << 3) | (cp >> 18)); + return 4; + } + + return 0; + } + + static bool parse_entity( + const char* current, char** to, const char** from) + { + const char* end = strchr(current, ';'); + if (!end) return 0; + + if (current[1] == '#') + { + char* tail = NULL; + int errno_save = errno; + bool hex = current[2] == 'x' || current[2] == 'X'; + + errno = 0; + unsigned long cp = strtoul( + current + (hex ? 3 : 2), &tail, hex ? 16 : 10); + + bool fail = errno || tail != end || cp > UNICODE_MAX; + errno = errno_save; + if (fail) return 0; + + *to += putc_utf8(cp, *to); + *from = end + 1; + + return 1; + } + else + { + const char* entity = get_named_entity(¤t[1]); + if (!entity) return 0; + + size_t len = strlen(entity); + sf_memcpy(*to, len, entity, len); + + *to += len; + *from = end + 1; + + return 1; + } + } + + + + size_t decode_html_entities_utf8(char* dest, const char* src) + { + if (!src) src = dest; + + char* to = dest; + const char* from = src; + + for (const char* current; (current = strchr(from, '&'));) + { + memmove(to, from, (size_t)(current - from)); + to += current - from; + + if (parse_entity(current, &to, &from)) + continue; + + from = current; + *to++ = *from++; + } + + size_t remaining = strlen(from); + + memmove(to, from, remaining); + to += remaining; + *to = 0; + + return (size_t)(to - dest); + } + } +} + diff --git a/cpp/entities.hpp b/cpp/entities.hpp new file mode 100644 index 0000000000..86ab3734b0 --- /dev/null +++ b/cpp/entities.hpp @@ -0,0 +1,28 @@ +/** + * Copyright 2012 Christoph Gärtner + * Distributed under the Boost Software License, Version 1.0 + */ + +#ifndef DECODE_HTML_ENTITIES_UTF8_ +#define DECODE_HTML_ENTITIES_UTF8_ + +#include +#include + +namespace Snowflake +{ + namespace Client + { + size_t decode_html_entities_utf8(char* dest, const char* src); + /* Takes input from and decodes into , which should be a buffer + large enough to hold characters. + + If is , input will be taken from , decoding + the entities in-place. + + The function returns the length of the decoded string. + */ + } +} + +#endif From 555cbfb667064f34291471bab953e930de376b35 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:21:36 -0700 Subject: [PATCH 07/55] fix --- lib/connection.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/connection.c b/lib/connection.c index bb4fa5a4ef..2a8e391665 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1381,7 +1381,7 @@ sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) // add headers for account and authentication SF_HEADER* httpExtraHeaders = sf_header_create(); - httpExtraHeaders->use_application_json_accept_type = TRUE; + httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; if (!create_header(sf, httpExtraHeaders, &sf->error)) { log_trace("sf", "AuthenticatorOKTA", "getIdpInfo", @@ -1391,8 +1391,8 @@ sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) goto cleanup; } - unsigned renew_timeout = 0; - unsigned retried_count = 0; + int64 renew_timeout = 0; + int8 retried_count = 0; char* s_body = snowflake_cJSON_Print(authnData); if (!request(sf, &resp, connectURL, NULL, From 4be9c7ff6efb2b78f71db392154f3e7a7b407439 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:07:22 -0700 Subject: [PATCH 08/55] fix --- cpp/lib/Authenticator.cpp | 68 +++++++++++++++++++++------------------ lib/connection.c | 8 +++-- tests/CMakeLists.txt | 2 +- tests/test_unit_sfurl.cpp | 2 +- 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 3660d08468..e8d0deb233 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -375,39 +375,40 @@ namespace Client void AuthenticatorOKTA::authenticate() { // 1. get authenticator info - cJSON* respData = NULL; - if (!getIdpInfo(m_connection, &respData)) + cJSON* resp_data = NULL; + if (!getIdpInfo(m_connection, &resp_data)) { AUTH_THROW(&m_connection->error); } SF_ERROR_STRUCT *err = &m_connection->error; - char* tokenURLStr = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(respData, "tokenUrl")); - char* ssoURLStr = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(respData, "ssoUrl")); + char* token_url_str = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp_data, "tokenUrl")); + char* sso_url_str = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp_data, "ssoUrl")); + std::string post_back_url; + std::string server_url; // 2. verify ssoUrl and tokenUrl contains same prefix - if (!SFURL::urlHasSamePrefix(tokenURLStr, m_connection->authenticator)) + if (!SFURL::urlHasSamePrefix(token_url_str, m_connection->authenticator)) { CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "The specified authenticator is not supported, " "authenticator=%s, token url=%s, sso url=%s", - m_connection->authenticator, tokenURLStr, ssoURLStr); + m_connection->authenticator, token_url_str, sso_url_str); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); AUTH_THROW(err->msg); } void* curl_desc; CURL* curl; - curl_desc = get_curl_desc_from_pool(tokenURLStr, m_connection->proxy, m_connection->no_proxy); + curl_desc = get_curl_desc_from_pool(token_url_str, m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); // When renewTimeout < 0, throws Exception // for each retry attempt so we can renew the one time token - unsigned retryTimeout = get_login_timeout(m_connection); - using namespace std::chrono; + int64 retry_timeout = get_login_timeout(m_connection); time_t start = time(NULL); - unsigned long elapsedSeconds = 0; - unsigned retriedCount = get_login_retry_count(m_connection); + int64 elapsed_time = 0; + int8* retried_count = &m_connection->retry_count; // 3. get one time token from okta cJSON* body = snowflake_cJSON_CreateObject(); @@ -426,10 +427,9 @@ namespace Client } cJSON* resp = NULL; - if (!curl_post_call(m_connection, curl, tokenURLStr, header, s_body, &resp, - &m_connection->error, 0, get_login_retry_count(m_connection), retryTimeout, - 0, 0, false, - false)) + if (!curl_post_call(m_connection, curl, token_url_str, header, s_body, &resp, + &m_connection->error, 0, get_login_retry_count(m_connection), retry_timeout, + &elapsed_time, retried_count, 0, false)) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, response body=%s", @@ -441,52 +441,52 @@ namespace Client char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); - elapsedSeconds = time(NULL) - start; - if (elapsedSeconds >= retryTimeout) + elapsed_time = time(NULL) - start; + if (elapsed_time >= retry_timeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retryTimeout, elapsedSeconds); + retried_count, elapsed_time); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); AUTH_THROW(err); } // 4. get SAML response - SFURL sso_url = SFURL::parse(ssoURLStr); + SFURL sso_url = SFURL::parse(sso_url_str); sso_url.addQueryParam("onetimetoken", one_time_token); resp = NULL; if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error)) { - elapsedSeconds = time(NULL) - start; - if (elapsedSeconds >= retryTimeout) + elapsed_time = time(NULL) - start; + if (elapsed_time >= retry_timeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retryTimeout, elapsedSeconds); + retried_count, elapsed_time); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); + goto cleanup; } CXX_LOG_TRACE("sf", "Connection", "Connect", "Retry on getting SAML response with one time token renewed for %d times " "with updated retryTimeout = %d", - retriedCount, retryTimeout - elapsedSeconds); + retried_count, retry_timeout - elapsed_time); } - elapsedSeconds = time(NULL) - start; - if (elapsedSeconds >= retryTimeout) + elapsed_time = time(NULL) - start; + if (elapsed_time >= retry_timeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retryTimeout, elapsedSeconds); + retry_timeout, elapsed_time); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); + goto cleanup; } m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); // 5. Validate post_back_url matches Snowflake URL - std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); - std::string server_url = getServerURLSync().toString(); + post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); + server_url = getServerURLSync().toString(); if ((!m_connection->disable_saml_url_check) && (!SFURL::urlHasSamePrefix(post_back_url, server_url))) @@ -498,12 +498,18 @@ namespace Client server_url.c_str(), post_back_url.c_str()); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); + goto cleanup; } + //deduct the elapsed time from the max of the login timeout. + m_connection->retry_timeout -= elapsed_time; + cleanup: sf_header_destroy(header); free_curl_desc(curl_desc); snowflake_cJSON_Delete(body); + if (err) { + AUTH_THROW(err); + } } void AuthenticatorOKTA::updateDataMap(cJSON* dataMap) diff --git a/lib/connection.c b/lib/connection.c index 2a8e391665..3257751b29 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1367,6 +1367,7 @@ sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) SF_ERROR_STRUCT* err = &sf->error; // login info as a json post body + //Currently, the server is not enabled Okta authentication with C API. For testing purpose, I used the ODBC info. snowflake_cJSON_AddStringToObject(dataMap, "CLIENT_APP_ID", "ODBC"); snowflake_cJSON_AddStringToObject(dataMap, "CLIENT_APP_VERSION", "3.4.1"); snowflake_cJSON_AddStringToObject(dataMap, "ACCOUNT_NAME", sf->account); @@ -1392,14 +1393,15 @@ sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) } int64 renew_timeout = 0; - int8 retried_count = 0; + int8* retried_count = &sf->retry_count; char* s_body = snowflake_cJSON_Print(authnData); + time_t start = time(NULL); if (!request(sf, &resp, connectURL, NULL, 0, s_body, httpExtraHeaders, POST_REQUEST_TYPE, &sf->error, SF_BOOLEAN_FALSE, renew_timeout, get_login_retry_count(sf), get_login_timeout(sf), NULL, - retried_count, NULL, SF_BOOLEAN_TRUE)) + &retried_count, NULL, SF_BOOLEAN_TRUE)) { log_info("sf", "Connection", "getIdpInfo", "Fail to get authenticator info, response body=%s\n", @@ -1408,6 +1410,8 @@ sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) ret = SF_BOOLEAN_FALSE; goto cleanup; } + //deduct elasped time from the retry timeout + sf->retry_timeout -= time(NULL) - start; *json = snowflake_cJSON_GetObjectItem(resp, "data"); ret = SF_BOOLEAN_TRUE; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f8c1b6bd1b..6ea4bd63c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,7 +47,7 @@ SET(TESTS_C # test_lob # test_stats # MFA and Okta connections are only able to run testing manually. -# test_manual_connect +# test_manual_connect ) SET(TESTS_CXX diff --git a/tests/test_unit_sfurl.cpp b/tests/test_unit_sfurl.cpp index 4fc9e8d781..1ed675c5c8 100644 --- a/tests/test_unit_sfurl.cpp +++ b/tests/test_unit_sfurl.cpp @@ -143,7 +143,7 @@ void test_error_parse(void ** unused) void test_url_has_same_prefix(void** unused) { char* url1 = "https://okta.snowflake.com"; - char* url2 = "http://snowflake.com/request_path"; + char* url2 = "https://okta.snowflake.com/request_path"; assert_true(SFURL::urlHasSamePrefix(url1, url2)); } From bf2ca7d64dc72b1dcd9b2f9b25edb0d376ce6520 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 19:17:41 -0700 Subject: [PATCH 09/55] fix --- cpp/lib/Authenticator.cpp | 6 +----- lib/connection.c | 7 ++++--- tests/CMakeLists.txt | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index e8d0deb233..28977bd59e 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -406,7 +406,6 @@ namespace Client // When renewTimeout < 0, throws Exception // for each retry attempt so we can renew the one time token int64 retry_timeout = get_login_timeout(m_connection); - time_t start = time(NULL); int64 elapsed_time = 0; int8* retried_count = &m_connection->retry_count; @@ -441,7 +440,6 @@ namespace Client char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); - elapsed_time = time(NULL) - start; if (elapsed_time >= retry_timeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", @@ -457,7 +455,6 @@ namespace Client resp = NULL; if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error)) { - elapsed_time = time(NULL) - start; if (elapsed_time >= retry_timeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", @@ -473,7 +470,6 @@ namespace Client retried_count, retry_timeout - elapsed_time); } - elapsed_time = time(NULL) - start; if (elapsed_time >= retry_timeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", @@ -507,7 +503,7 @@ namespace Client sf_header_destroy(header); free_curl_desc(curl_desc); snowflake_cJSON_Delete(body); - if (err) { + if (err->error_code != SF_STATUS_SUCCESS) { AUTH_THROW(err); } } diff --git a/lib/connection.c b/lib/connection.c index 3257751b29..057c60ffcd 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1394,14 +1394,15 @@ sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) int64 renew_timeout = 0; int8* retried_count = &sf->retry_count; + int64 elasped_time = 0; char* s_body = snowflake_cJSON_Print(authnData); time_t start = time(NULL); if (!request(sf, &resp, connectURL, NULL, 0, s_body, httpExtraHeaders, POST_REQUEST_TYPE, &sf->error, SF_BOOLEAN_FALSE, - renew_timeout, get_login_retry_count(sf), get_login_timeout(sf), NULL, - &retried_count, NULL, SF_BOOLEAN_TRUE)) + renew_timeout, get_login_retry_count(sf), get_login_timeout(sf), &elasped_time, + retried_count, NULL, SF_BOOLEAN_TRUE)) { log_info("sf", "Connection", "getIdpInfo", "Fail to get authenticator info, response body=%s\n", @@ -1411,7 +1412,7 @@ sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) goto cleanup; } //deduct elasped time from the retry timeout - sf->retry_timeout -= time(NULL) - start; + sf->retry_timeout -= elasped_time; *json = snowflake_cJSON_GetObjectItem(resp, "data"); ret = SF_BOOLEAN_TRUE; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6ea4bd63c3..7ca2022309 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,7 +47,7 @@ SET(TESTS_C # test_lob # test_stats # MFA and Okta connections are only able to run testing manually. -# test_manual_connect + test_manual_connect ) SET(TESTS_CXX From fd5caa0be9618f7187340bcdbd0d3cfea3644c2c Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 19:40:23 -0700 Subject: [PATCH 10/55] fix testing cases --- tests/CMakeLists.txt | 2 +- tests/test_unit_sfurl.cpp | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7ca2022309..6ea4bd63c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,7 +47,7 @@ SET(TESTS_C # test_lob # test_stats # MFA and Okta connections are only able to run testing manually. - test_manual_connect +# test_manual_connect ) SET(TESTS_CXX diff --git a/tests/test_unit_sfurl.cpp b/tests/test_unit_sfurl.cpp index 1ed675c5c8..cf234a2f85 100644 --- a/tests/test_unit_sfurl.cpp +++ b/tests/test_unit_sfurl.cpp @@ -142,10 +142,7 @@ void test_error_parse(void ** unused) void test_url_has_same_prefix(void** unused) { - char* url1 = "https://okta.snowflake.com"; - char* url2 = "https://okta.snowflake.com/request_path"; - - assert_true(SFURL::urlHasSamePrefix(url1, url2)); + assert_true(SFURL::urlHasSamePrefix("https://okta.snowflake.com", "https://okta.snowflake.com/request_path")); } static int gr_setup(void **unused) From 4271b2cbb5f88d79d303f90bdd774c491ac47398 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 20:10:36 -0700 Subject: [PATCH 11/55] fixed the error on mac --- cpp/lib/Authenticator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 28977bd59e..373e5ef4cd 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -121,7 +121,7 @@ extern "C" { } catch (...) { - if (!&conn->error) { + if (getAuthenticatorType(conn->authenticator) == AUTH_JWT) { SET_SNOWFLAKE_ERROR(&conn->error, SF_STATUS_ERROR_GENERAL, "authentication failed", SF_SQLSTATE_GENERAL_ERROR); From fd576835395272d05ca048ca5700f6ecdb4dabfd Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Tue, 8 Oct 2024 20:55:03 -0700 Subject: [PATCH 12/55] removed unnecessary changes --- cacert.pem | 2 +- tests/test_manual_connect.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cacert.pem b/cacert.pem index 4c22ba6f72..9551dfd830 100644 --- a/cacert.pem +++ b/cacert.pem @@ -3448,4 +3448,4 @@ TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj 1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W HYMfRsCbvUOZ58SWLs5fyQ== ------END CERTIFICATE----- \ No newline at end of file +-----END CERTIFICATE----- diff --git a/tests/test_manual_connect.c b/tests/test_manual_connect.c index 51baa98755..f09e406a63 100644 --- a/tests/test_manual_connect.c +++ b/tests/test_manual_connect.c @@ -154,8 +154,8 @@ int main(void) { initialize_test(SF_BOOLEAN_FALSE); const struct CMUnitTest tests[] = { - //cmocka_unit_test(test_connect_with_duo_push), - //cmocka_unit_test(test_connect_with_duo_passcode), + cmocka_unit_test(test_connect_with_duo_push), + cmocka_unit_test(test_connect_with_duo_passcode), //Need to run this testing separately. //cmocka_unit_test(test_connect_with_duo_passcodeInPassword), cmocka_unit_test(test_okta_connect), From d8680d00b69644a1ec0680e9657ba703af53d051 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:54:51 -0700 Subject: [PATCH 13/55] added retry parameters in the curl_get_call and applied the retry strategy --- cpp/lib/Authenticator.cpp | 187 ++++++++++++++++++++---------------- lib/connection.c | 14 ++- lib/connection.h | 2 +- lib/http_perform.c | 4 +- tests/test_manual_connect.c | 2 +- 5 files changed, 116 insertions(+), 93 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 373e5ef4cd..b02c4ae4ef 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -35,11 +35,17 @@ #define strcasecmp _stricmp #endif -#define AUTH_THROW(msg) \ -{ \ +#define AUTH_THROW(msg) \ +{ \ throw Snowflake::Client::Exception::AuthException(msg); \ } +#define JWT_THROW(err, msg) \ +{ \ + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "Failed to created the header for the okta authentication", SF_SQLSTATE_GENERAL_ERROR); \ + AUTH_THROW(err); \ +} + // wrapper functions for C extern "C" { @@ -250,31 +256,37 @@ namespace Client { privKeyFilePwd = conn->priv_key_file_pwd; } - loadPrivateKey(privKeyFile, privKeyFilePwd); - m_timeOut = conn->jwt_timeout; - m_renewTimeout = conn->jwt_cnxn_wait_time; - - // Prepare header - std::shared_ptr
header {Header::buildHeader()}; - header->setAlgorithm(Snowflake::Client::Jwt::AlgorithmType::RS256); - m_jwt->setHeader(std::move(header)); - - // Prepare claim set - std::shared_ptr claimSet {ClaimSet::buildClaimSet()}; - - std::string account = conn->account; - std::string user = conn->user; - for (char &c : account) c = std::toupper(c); - for (char &c : user) c = std::toupper(c); - - // issuer - std::string subject = account + '.'; - subject += user; - claimSet->addClaim("sub", subject); - - std::string issuer = subject + ".SHA256:" + extractPublicKey(m_privKey); - claimSet->addClaim("iss", issuer); - m_jwt->setClaimSet(std::move(claimSet)); + try { + loadPrivateKey(privKeyFile, privKeyFilePwd); + m_timeOut = conn->jwt_timeout; + m_renewTimeout = conn->jwt_cnxn_wait_time; + + // Prepare header + std::shared_ptr
header{ Header::buildHeader() }; + header->setAlgorithm(Snowflake::Client::Jwt::AlgorithmType::RS256); + m_jwt->setHeader(std::move(header)); + + // Prepare claim set + std::shared_ptr claimSet{ ClaimSet::buildClaimSet() }; + + std::string account = conn->account; + std::string user = conn->user; + for (char& c : account) c = std::toupper(c); + for (char& c : user) c = std::toupper(c); + + // issuer + std::string subject = account + '.'; + subject += user; + claimSet->addClaim("sub", subject); + + std::string issuer = subject + ".SHA256:" + extractPublicKey(m_privKey); + claimSet->addClaim("iss", issuer); + m_jwt->setClaimSet(std::move(claimSet)); + } + catch (Exception::AuthException e) { + SET_SNOWFLAKE_ERROR(&conn->error, SF_STATUS_ERROR_GENERAL, e.message_.c_str(), SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW("JWT Authentication failed"); + } } AuthenticatorJWT::~AuthenticatorJWT() @@ -381,7 +393,7 @@ namespace Client AUTH_THROW(&m_connection->error); } - SF_ERROR_STRUCT *err = &m_connection->error; + SF_ERROR_STRUCT* err = &m_connection->error; char* token_url_str = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp_data, "tokenUrl")); char* sso_url_str = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp_data, "ssoUrl")); std::string post_back_url; @@ -426,35 +438,22 @@ namespace Client } cJSON* resp = NULL; - if (!curl_post_call(m_connection, curl, token_url_str, header, s_body, &resp, - &m_connection->error, 0, get_login_retry_count(m_connection), retry_timeout, - &elapsed_time, retried_count, 0, false)) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, response body=%s", - snowflake_cJSON_Print(resp)); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); - } - - char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? - snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : - snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); - if (elapsed_time >= retry_timeout) + while (true) { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retried_count, elapsed_time); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); - } + if (!curl_post_call(m_connection, curl, token_url_str, header, s_body, &resp, + &m_connection->error, 0, get_login_retry_count(m_connection), retry_timeout, + &elapsed_time, retried_count, 0, false)) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, response body=%s", + snowflake_cJSON_Print(resp)); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); + goto cleanup; + } - // 4. get SAML response - SFURL sso_url = SFURL::parse(sso_url_str); - sso_url.addQueryParam("onetimetoken", one_time_token); - resp = NULL; - if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error)) - { + char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? + snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : + snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); if (elapsed_time >= retry_timeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", @@ -464,40 +463,58 @@ namespace Client goto cleanup; } - CXX_LOG_TRACE("sf", "Connection", "Connect", - "Retry on getting SAML response with one time token renewed for %d times " - "with updated retryTimeout = %d", - retried_count, retry_timeout - elapsed_time); - } + // 4. get SAML response + SFURL sso_url = SFURL::parse(sso_url_str); + sso_url.addQueryParam("onetimetoken", one_time_token); + resp = NULL; + if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error, -1, get_login_retry_count(m_connection), retry_timeout, &elapsed_time, retried_count)) + { + if (elapsed_time >= retry_timeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retried_count, elapsed_time); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + goto cleanup; + } + + CXX_LOG_TRACE("sf", "Connection", "Connect", + "Retry on getting SAML response with one time token renewed for %d times " + "with updated retryTimeout = %d", + retried_count, retry_timeout - elapsed_time); + continue; + } - if (elapsed_time >= retry_timeout) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retry_timeout, elapsed_time); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - goto cleanup; + if (elapsed_time >= retry_timeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retry_timeout, elapsed_time); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + goto cleanup; + } + break; } - m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); - // 5. Validate post_back_url matches Snowflake URL - post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); - server_url = getServerURLSync().toString(); + m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); + // 5. Validate post_back_url matches Snowflake URL + post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); + server_url = getServerURLSync().toString(); - if ((!m_connection->disable_saml_url_check) && - (!SFURL::urlHasSamePrefix(post_back_url, server_url))) - { - CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "The specified authenticator and destination URL in " - "Saml Assertion did not " - "match, expected=%s, post back=%s", - server_url.c_str(), - post_back_url.c_str()); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - goto cleanup; - } - //deduct the elapsed time from the max of the login timeout. - m_connection->retry_timeout -= elapsed_time; + if ((!m_connection->disable_saml_url_check) && + (!SFURL::urlHasSamePrefix(post_back_url, server_url))) + { + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "The specified authenticator and destination URL in " + "Saml Assertion did not " + "match, expected=%s, post back=%s", + server_url.c_str(), + post_back_url.c_str()); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "SFSamlResponseVerificationFailed", SF_SQLSTATE_GENERAL_ERROR); + goto cleanup; + } + //update retry info. + m_connection->retry_timeout -= elapsed_time; cleanup: sf_header_destroy(header); diff --git a/lib/connection.c b/lib/connection.c index 057c60ffcd..e96d71d3c3 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -470,7 +470,12 @@ sf_bool STDCALL curl_get_call(SF_CONNECT *sf, char *url, SF_HEADER *header, cJSON **json, - SF_ERROR_STRUCT *error) { + SF_ERROR_STRUCT *error, + int64 renew_timeout, + int8 retry_max_count, + int64 retry_timeout, + int64* elapsed_time, + int8* retried_count){ SF_JSON_ERROR json_error; const char *error_msg; char query_code[QUERYCODE_LEN]; @@ -486,7 +491,7 @@ sf_bool STDCALL curl_get_call(SF_CONNECT *sf, get_retry_timeout(sf), SF_BOOLEAN_FALSE, error, sf->insecure_mode, sf->retry_on_curle_couldnt_connect_count, - 0, sf->retry_count, NULL, NULL, NULL, SF_BOOLEAN_FALSE, + renew_timeout, retry_max_count, elapsed_time, retried_count, NULL, SF_BOOLEAN_FALSE, sf->proxy, sf->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE) || !*json) { // Error is set in the perform function @@ -518,7 +523,7 @@ sf_bool STDCALL curl_get_call(SF_CONNECT *sf, if (!create_header(sf, new_header, error)) { break; } - if (!curl_get_call(sf, curl, url, new_header, json, error)) { + if (!curl_get_call(sf, curl, url, new_header, json, error, retry_max_count, renew_timeout, retry_timeout, elapsed_time, retried_count)) { // Error is set in curl call break; } @@ -953,7 +958,8 @@ sf_bool STDCALL request(SF_CONNECT *sf, elapsed_time, retried_count, is_renew, renew_injection); } else if (request_type == GET_REQUEST_TYPE) { - ret = curl_get_call(sf, curl, encoded_url, my_header, json, error); + ret = curl_get_call(sf, curl, encoded_url, my_header, json, error, + renew_timeout, retry_max_count, retry_timeout, elapsed_time, retried_count); } else { SET_SNOWFLAKE_ERROR(error, SF_STATUS_ERROR_BAD_REQUEST, "An unknown request type was passed to the request function", diff --git a/lib/connection.h b/lib/connection.h index 66c2ed0542..bde025c436 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -264,7 +264,7 @@ sf_bool STDCALL curl_post_call(SF_CONNECT *sf, CURL *curl, char *url, SF_HEADER * @return Success/failure status of get call. 1 = Success; 0 = Failure */ sf_bool STDCALL curl_get_call(SF_CONNECT *sf, CURL *curl, char *url, SF_HEADER *header, cJSON **json, - SF_ERROR_STRUCT *error); + SF_ERROR_STRUCT *error, int64 renew_timeout, int8 retry_max_count, int64 retry_timeout, int64* elapsed_time, int8* retried_count); /** * Used to determine the sleep time during the next backoff caused by request failure. diff --git a/lib/http_perform.c b/lib/http_perform.c index 5b302262da..517843e78d 100644 --- a/lib/http_perform.c +++ b/lib/http_perform.c @@ -456,8 +456,8 @@ sf_bool STDCALL http_perform(CURL *curl, // When renew timeout is reached, stop retry and return to the caller // to renew request - if ((retry) && (renew_timeout > 0) && - ((time(NULL) - elapsedRetryTime) >= renew_timeout)) { + if (((retry) && (renew_timeout > 0) && + ((time(NULL) - elapsedRetryTime) >= renew_timeout)) || renew_timeout < 0) { retry = SF_BOOLEAN_FALSE; if (elapsed_time) { *elapsed_time += (time(NULL) - elapsedRetryTime); diff --git a/tests/test_manual_connect.c b/tests/test_manual_connect.c index f09e406a63..1b5c358244 100644 --- a/tests/test_manual_connect.c +++ b/tests/test_manual_connect.c @@ -157,7 +157,7 @@ int main(void) cmocka_unit_test(test_connect_with_duo_push), cmocka_unit_test(test_connect_with_duo_passcode), //Need to run this testing separately. - //cmocka_unit_test(test_connect_with_duo_passcodeInPassword), + cmocka_unit_test(test_connect_with_duo_passcodeInPassword), cmocka_unit_test(test_okta_connect), }; int ret = cmocka_run_group_tests(tests, NULL, NULL); From 48aae9fa2f48c6e231f25c25d2e55d4b55f597e8 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:41:46 -0700 Subject: [PATCH 14/55] add one more condition for the retry strategy --- cpp/lib/Authenticator.cpp | 8 +++++--- lib/connection.h | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index b02c4ae4ef..8b70057f4d 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -418,6 +418,8 @@ namespace Client // When renewTimeout < 0, throws Exception // for each retry attempt so we can renew the one time token int64 retry_timeout = get_login_timeout(m_connection); + int64 retry_max_count = get_login_retry_count(m_connection); + int64 elapsed_time = 0; int8* retried_count = &m_connection->retry_count; @@ -441,7 +443,7 @@ namespace Client while (true) { if (!curl_post_call(m_connection, curl, token_url_str, header, s_body, &resp, - &m_connection->error, 0, get_login_retry_count(m_connection), retry_timeout, + &m_connection->error, 0, retry_max_count, retry_timeout, &elapsed_time, retried_count, 0, false)) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", @@ -467,9 +469,9 @@ namespace Client SFURL sso_url = SFURL::parse(sso_url_str); sso_url.addQueryParam("onetimetoken", one_time_token); resp = NULL; - if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error, -1, get_login_retry_count(m_connection), retry_timeout, &elapsed_time, retried_count)) + if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error, -1, retry_max_count, retry_timeout, &elapsed_time, retried_count)) { - if (elapsed_time >= retry_timeout) + if (elapsed_time >= retry_timeout || &retried_count < retry_max_count) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", diff --git a/lib/connection.h b/lib/connection.h index bde025c436..7bdee81ada 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -233,6 +233,9 @@ sf_bool STDCALL create_header(SF_CONNECT *sf, SF_HEADER *header, SF_ERROR_STRUCT * reached and the caller can renew the credentials and * then go back to the retry by calling curl_post_call() again. * 0 means no renew timeout needed. + * For Okta Authentication, whenever the authentication failed, the connector + * should update the onetime token. In this case, the renew timeout < 0, which means + * the request should be renewed for each request. * @param retry_max_count The max number of retry attempts. 0 means no limit. * @param retry_timeout The timeout for retry. Will stop retry when it's exceeded. 0 means no limit. * @param elapsed_time The in/out paramter to record the elapsed time before @@ -261,6 +264,22 @@ sf_bool STDCALL curl_post_call(SF_CONNECT *sf, CURL *curl, char *url, SF_HEADER * @param header Header passed to cURL for use in the request * @param json Reference to a cJSON pointer that is used to store the JSON response upon a successful request * @param error Reference to the Snowflake Error object to set an error if one occurs + * @param renew_timeout For key pair authentication. Credentials could expire + * during the connection retry. Set renew timeout in such + * case so http_perform will return when renew_timeout is + * reached and the caller can renew the credentials and + * then go back to the retry by calling curl_post_call() again. + * 0 means no renew timeout needed. + * For Okta Authentication, whenever the authentication failed, the connector + * should update the onetime token. In this case, the renew timeout < 0, which means + * the request should be renewed for each request. + * @param retry_max_count The max number of retry attempts. 0 means no limit. + * @param retry_timeout The timeout for retry. Will stop retry when it's exceeded. 0 means no limit. + * @param elapsed_time The in/out paramter to record the elapsed time before + * curl_post_call() returned due to renew timeout last time + * @param retried_count The in/out paramter to record the number of retry attempts + * has been done before http_perform() returned due to renew + * timeout last time. * @return Success/failure status of get call. 1 = Success; 0 = Failure */ sf_bool STDCALL curl_get_call(SF_CONNECT *sf, CURL *curl, char *url, SF_HEADER *header, cJSON **json, From 9245aae9647dd4790bcb051e052fe4f462d262bb Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:43:15 -0700 Subject: [PATCH 15/55] removed the error in catch --- cpp/lib/Authenticator.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 8b70057f4d..cc8163b67c 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -127,11 +127,6 @@ extern "C" { } catch (...) { - if (getAuthenticatorType(conn->authenticator) == AUTH_JWT) { - SET_SNOWFLAKE_ERROR(&conn->error, SF_STATUS_ERROR_GENERAL, - "authentication failed", - SF_SQLSTATE_GENERAL_ERROR); - } return SF_STATUS_ERROR_GENERAL; } @@ -471,7 +466,7 @@ namespace Client resp = NULL; if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error, -1, retry_max_count, retry_timeout, &elapsed_time, retried_count)) { - if (elapsed_time >= retry_timeout || &retried_count < retry_max_count) + if (elapsed_time >= retry_timeout || *retried_count >= retry_max_count) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", From 40077ca4f3d248732dc75dcd23c85dd65edbc528 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:02:24 -0700 Subject: [PATCH 16/55] removed unnecessary changes --- cpp/lib/Authenticator.hpp | 1 - include/snowflake/client.h | 1 - include/snowflake/version.h | 6 +----- tests/CMakeLists.txt | 2 +- tests/test_manual_connect.c | 6 +++--- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 1a7976ac4f..46bbcd8e91 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -102,7 +102,6 @@ namespace Client private: SF_CONNECT* m_connection; std::string m_samlResponse; - Snowflake::Client::Util::Proxy m_proxySettings; /** * Extract post back url from samel response. Input is in HTML format. diff --git a/include/snowflake/client.h b/include/snowflake/client.h index 88dd76df01..f181e4a1b5 100644 --- a/include/snowflake/client.h +++ b/include/snowflake/client.h @@ -348,7 +348,6 @@ typedef struct SF_CONNECT { // Proxy char * proxy; char * no_proxy; - sf_bool proxy_with_env; // Query Context Cache // the flag of whether to disable qcc, false by default diff --git a/include/snowflake/version.h b/include/snowflake/version.h index 43c21b34ba..b21a93dd34 100644 --- a/include/snowflake/version.h +++ b/include/snowflake/version.h @@ -5,10 +5,6 @@ #ifndef SNOWFLAKE_CLIENT_VERSION_H #define SNOWFLAKE_CLIENT_VERSION_H -//#define SF_API_VERSION "1.0.14" -// TODO: temporarily change to run test -#define SF_API_VERSION "3.0.1" -//#define SF_API_VERSION "1.13.1" - +#define SF_API_VERSION "1.0.14" #endif /* SNOWFLAKE_CLIENT_VERSION_H */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6ea4bd63c3..7ca2022309 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,7 +47,7 @@ SET(TESTS_C # test_lob # test_stats # MFA and Okta connections are only able to run testing manually. -# test_manual_connect + test_manual_connect ) SET(TESTS_CXX diff --git a/tests/test_manual_connect.c b/tests/test_manual_connect.c index 1b5c358244..51baa98755 100644 --- a/tests/test_manual_connect.c +++ b/tests/test_manual_connect.c @@ -154,10 +154,10 @@ int main(void) { initialize_test(SF_BOOLEAN_FALSE); const struct CMUnitTest tests[] = { - cmocka_unit_test(test_connect_with_duo_push), - cmocka_unit_test(test_connect_with_duo_passcode), + //cmocka_unit_test(test_connect_with_duo_push), + //cmocka_unit_test(test_connect_with_duo_passcode), //Need to run this testing separately. - cmocka_unit_test(test_connect_with_duo_passcodeInPassword), + //cmocka_unit_test(test_connect_with_duo_passcodeInPassword), cmocka_unit_test(test_okta_connect), }; int ret = cmocka_run_group_tests(tests, NULL, NULL); From 2c56894e8962fd0cb00e96d733f2e9ffe1c73fc6 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:03:03 -0700 Subject: [PATCH 17/55] fix --- tests/test_manual_connect.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_manual_connect.c b/tests/test_manual_connect.c index 51baa98755..f09e406a63 100644 --- a/tests/test_manual_connect.c +++ b/tests/test_manual_connect.c @@ -154,8 +154,8 @@ int main(void) { initialize_test(SF_BOOLEAN_FALSE); const struct CMUnitTest tests[] = { - //cmocka_unit_test(test_connect_with_duo_push), - //cmocka_unit_test(test_connect_with_duo_passcode), + cmocka_unit_test(test_connect_with_duo_push), + cmocka_unit_test(test_connect_with_duo_passcode), //Need to run this testing separately. //cmocka_unit_test(test_connect_with_duo_passcodeInPassword), cmocka_unit_test(test_okta_connect), From 6e58935e995354897af14bf48670a42b0c589536 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:05:50 -0700 Subject: [PATCH 18/55] comment out manual test --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7ca2022309..6ea4bd63c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,7 +47,7 @@ SET(TESTS_C # test_lob # test_stats # MFA and Okta connections are only able to run testing manually. - test_manual_connect +# test_manual_connect ) SET(TESTS_CXX From ef7d51acf6acb1480bab127aa2477b3d65f207fe Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:09:22 -0700 Subject: [PATCH 19/55] add retried_count increment on authentication level --- cpp/lib/Authenticator.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index cc8163b67c..db6b609385 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -455,7 +455,7 @@ namespace Client { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retried_count, elapsed_time); + *retried_count, elapsed_time); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); goto cleanup; } @@ -474,11 +474,12 @@ namespace Client SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); goto cleanup; } - + //Need to increase retried_count on the authentication level + (*retried_count)++; CXX_LOG_TRACE("sf", "Connection", "Connect", "Retry on getting SAML response with one time token renewed for %d times " "with updated retryTimeout = %d", - retried_count, retry_timeout - elapsed_time); + *retried_count, retry_timeout - elapsed_time); continue; } From 27e8e472cea65eb747abba49a4fa45d2253fbe55 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:41:48 -0700 Subject: [PATCH 20/55] refactored the renew timeout condition --- CMakeLists.txt | 4 +- cpp/entities.cpp | 416 -------------------------------------- cpp/entities.hpp | 28 --- cpp/lib/Authenticator.cpp | 2 +- lib/http_perform.c | 5 +- 5 files changed, 6 insertions(+), 449 deletions(-) delete mode 100644 cpp/entities.cpp delete mode 100644 cpp/entities.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fbb66547c..7c1be2279c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,8 +155,6 @@ set (SOURCE_FILES_PUT_GET cpp/StorageClientFactory.hpp cpp/StorageClientFactory.cpp cpp/RemoteStorageRequestOutcome.hpp - cpp/entities.cpp - cpp/entities.hpp cpp/util/Base64.hpp cpp/util/Base64.cpp cpp/util/ByteArrayStreamBuf.cpp @@ -166,6 +164,7 @@ set (SOURCE_FILES_PUT_GET cpp/util/Proxy.cpp cpp/util/ThreadPool.hpp cpp/util/SnowflakeCommon.hpp + cpp/util/entities.cpp cpp/crypto/CryptoTypes.hpp cpp/crypto/Cryptor.hpp cpp/crypto/CipherContext.hpp @@ -201,6 +200,7 @@ set (SOURCE_FILES_PUT_GET include/snowflake/IJwt.hpp include/snowflake/IBase64.hpp include/snowflake/Proxy.hpp + include/snowflake/entities.hpp ) set(SOURCE_FILES_CPP_WRAPPER diff --git a/cpp/entities.cpp b/cpp/entities.cpp deleted file mode 100644 index 494e409911..0000000000 --- a/cpp/entities.cpp +++ /dev/null @@ -1,416 +0,0 @@ -/** - * Copyright 2012, 2016 Christoph Gärtner - * Distributed under the Boost Software License, Version 1.0 - */ - -#include "entities.hpp" - -#include -#include -#include -#include "snowflake/SF_CRTFunctionSafe.h" - -#define UNICODE_MAX 0x10FFFFul - -namespace Snowflake -{ - namespace Client - { - - static const char* const NAMED_ENTITIES[][2] = { - {"AElig;", "Æ"}, - {"Aacute;", "Á"}, - {"Acirc;", "Â"}, - {"Agrave;", "À"}, - {"Alpha;", "Α"}, - {"Aring;", "Å"}, - {"Atilde;", "Ã"}, - {"Auml;", "Ä"}, - {"Beta;", "Β"}, - {"Ccedil;", "Ç"}, - {"Chi;", "Χ"}, - {"Dagger;", "‡"}, - {"Delta;", "Δ"}, - {"ETH;", "Ð"}, - {"Eacute;", "É"}, - {"Ecirc;", "Ê"}, - {"Egrave;", "È"}, - {"Epsilon;", "Ε"}, - {"Eta;", "Η"}, - {"Euml;", "Ë"}, - {"Gamma;", "Γ"}, - {"Iacute;", "Í"}, - {"Icirc;", "Î"}, - {"Igrave;", "Ì"}, - {"Iota;", "Ι"}, - {"Iuml;", "Ï"}, - {"Kappa;", "Κ"}, - {"Lambda;", "Λ"}, - {"Mu;", "Μ"}, - {"Ntilde;", "Ñ"}, - {"Nu;", "Ν"}, - {"OElig;", "Œ"}, - {"Oacute;", "Ó"}, - {"Ocirc;", "Ô"}, - {"Ograve;", "Ò"}, - {"Omega;", "Ω"}, - {"Omicron;", "Ο"}, - {"Oslash;", "Ø"}, - {"Otilde;", "Õ"}, - {"Ouml;", "Ö"}, - {"Phi;", "Φ"}, - {"Pi;", "Π"}, - {"Prime;", "″"}, - {"Psi;", "Ψ"}, - {"Rho;", "Ρ"}, - {"Scaron;", "Š"}, - {"Sigma;", "Σ"}, - {"THORN;", "Þ"}, - {"Tau;", "Τ"}, - {"Theta;", "Θ"}, - {"Uacute;", "Ú"}, - {"Ucirc;", "Û"}, - {"Ugrave;", "Ù"}, - {"Upsilon;", "Υ"}, - {"Uuml;", "Ü"}, - {"Xi;", "Ξ"}, - {"Yacute;", "Ý"}, - {"Yuml;", "Ÿ"}, - {"Zeta;", "Ζ"}, - {"aacute;", "á"}, - {"acirc;", "â"}, - {"acute;", "´"}, - {"aelig;", "æ"}, - {"agrave;", "à"}, - {"alefsym;", "ℵ"}, - {"alpha;", "α"}, - {"amp;", "&"}, - {"and;", "∧"}, - {"ang;", "∠"}, - {"apos;", "'"}, - {"aring;", "å"}, - {"asymp;", "≈"}, - {"atilde;", "ã"}, - {"auml;", "ä"}, - {"bdquo;", "„"}, - {"beta;", "β"}, - {"brvbar;", "¦"}, - {"bull;", "•"}, - {"cap;", "∩"}, - {"ccedil;", "ç"}, - {"cedil;", "¸"}, - {"cent;", "¢"}, - {"chi;", "χ"}, - {"circ;", "ˆ"}, - {"clubs;", "♣"}, - {"cong;", "≅"}, - {"copy;", "©"}, - {"crarr;", "↵"}, - {"cup;", "∪"}, - {"curren;", "¤"}, - {"dArr;", "⇓"}, - {"dagger;", "†"}, - {"darr;", "↓"}, - {"deg;", "°"}, - {"delta;", "δ"}, - {"diams;", "♦"}, - {"divide;", "÷"}, - {"eacute;", "é"}, - {"ecirc;", "ê"}, - {"egrave;", "è"}, - {"empty;", "∅"}, - {"emsp;", "\xE2\x80\x83"}, - {"ensp;", "\xE2\x80\x82"}, - {"epsilon;", "ε"}, - {"equiv;", "≡"}, - {"eta;", "η"}, - {"eth;", "ð"}, - {"euml;", "ë"}, - {"euro;", "€"}, - {"exist;", "∃"}, - {"fnof;", "ƒ"}, - {"forall;", "∀"}, - {"frac12;", "½"}, - {"frac14;", "¼"}, - {"frac34;", "¾"}, - {"frasl;", "⁄"}, - {"gamma;", "γ"}, - {"ge;", "≥"}, - {"gt;", ">"}, - {"hArr;", "⇔"}, - {"harr;", "↔"}, - {"hearts;", "♥"}, - {"hellip;", "…"}, - {"iacute;", "í"}, - {"icirc;", "î"}, - {"iexcl;", "¡"}, - {"igrave;", "ì"}, - {"image;", "ℑ"}, - {"infin;", "∞"}, - {"int;", "∫"}, - {"iota;", "ι"}, - {"iquest;", "¿"}, - {"isin;", "∈"}, - {"iuml;", "ï"}, - {"kappa;", "κ"}, - {"lArr;", "⇐"}, - {"lambda;", "λ"}, - {"lang;", "〈"}, - {"laquo;", "«"}, - {"larr;", "←"}, - {"lceil;", "⌈"}, - {"ldquo;", "“"}, - {"le;", "≤"}, - {"lfloor;", "⌊"}, - {"lowast;", "∗"}, - {"loz;", "◊"}, - {"lrm;", "\xE2\x80\x8E"}, - {"lsaquo;", "‹"}, - {"lsquo;", "‘"}, - {"lt;", "<"}, - {"macr;", "¯"}, - {"mdash;", "—"}, - {"micro;", "µ"}, - {"middot;", "·"}, - {"minus;", "−"}, - {"mu;", "μ"}, - {"nabla;", "∇"}, - {"nbsp;", "\xC2\xA0"}, - {"ndash;", "–"}, - {"ne;", "≠"}, - {"ni;", "∋"}, - {"not;", "¬"}, - {"notin;", "∉"}, - {"nsub;", "⊄"}, - {"ntilde;", "ñ"}, - {"nu;", "ν"}, - {"oacute;", "ó"}, - {"ocirc;", "ô"}, - {"oelig;", "œ"}, - {"ograve;", "ò"}, - {"oline;", "‾"}, - {"omega;", "ω"}, - {"omicron;", "ο"}, - {"oplus;", "⊕"}, - {"or;", "∨"}, - {"ordf;", "ª"}, - {"ordm;", "º"}, - {"oslash;", "ø"}, - {"otilde;", "õ"}, - {"otimes;", "⊗"}, - {"ouml;", "ö"}, - {"para;", "¶"}, - {"part;", "∂"}, - {"permil;", "‰"}, - {"perp;", "⊥"}, - {"phi;", "φ"}, - {"pi;", "π"}, - {"piv;", "ϖ"}, - {"plusmn;", "±"}, - {"pound;", "£"}, - {"prime;", "′"}, - {"prod;", "∏"}, - {"prop;", "∝"}, - {"psi;", "ψ"}, - {"quot;", "\""}, - {"rArr;", "⇒"}, - {"radic;", "√"}, - {"rang;", "〉"}, - {"raquo;", "»"}, - {"rarr;", "→"}, - {"rceil;", "⌉"}, - {"rdquo;", "”"}, - {"real;", "ℜ"}, - {"reg;", "®"}, - {"rfloor;", "⌋"}, - {"rho;", "ρ"}, - {"rlm;", "\xE2\x80\x8F"}, - {"rsaquo;", "›"}, - {"rsquo;", "’"}, - {"sbquo;", "‚"}, - {"scaron;", "š"}, - {"sdot;", "⋅"}, - {"sect;", "§"}, - {"shy;", "\xC2\xAD"}, - {"sigma;", "σ"}, - {"sigmaf;", "ς"}, - {"sim;", "∼"}, - {"spades;", "♠"}, - {"sub;", "⊂"}, - {"sube;", "⊆"}, - {"sum;", "∑"}, - {"sup1;", "¹"}, - {"sup2;", "²"}, - {"sup3;", "³"}, - {"sup;", "⊃"}, - {"supe;", "⊇"}, - {"szlig;", "ß"}, - {"tau;", "τ"}, - {"there4;", "∴"}, - {"theta;", "θ"}, - {"thetasym;", "ϑ"}, - {"thinsp;", "\xE2\x80\x89"}, - {"thorn;", "þ"}, - {"tilde;", "˜"}, - {"times;", "×"}, - {"trade;", "™"}, - {"uArr;", "⇑"}, - {"uacute;", "ú"}, - {"uarr;", "↑"}, - {"ucirc;", "û"}, - {"ugrave;", "ù"}, - {"uml;", "¨"}, - {"upsih;", "ϒ"}, - {"upsilon;", "υ"}, - {"uuml;", "ü"}, - {"weierp;", "℘"}, - {"xi;", "ξ"}, - {"yacute;", "ý"}, - {"yen;", "¥"}, - {"yuml;", "ÿ"}, - {"zeta;", "ζ"}, - {"zwj;", "\xE2\x80\x8D"}, - {"zwnj;", "\xE2\x80\x8C"} - }; - - static int cmp(const void* key, const void* value) - { - return strncmp((const char*)key, *(const char* const*)value, - strlen(*(const char* const*)value)); - } - -#if defined(_WIN32) || defined(_WIN64) - static int cmp_s(void* pvlocale, const void* key, const void* value) - { - //UNUSED(pvlocale); - return cmp(key, value); - } -#endif - - static const char* get_named_entity(const char* name) - { -#if defined(_WIN32) || defined(_WIN64) - const char* const* entity = (const char* const*)bsearch_s(name, - NAMED_ENTITIES, - sizeof NAMED_ENTITIES / sizeof * NAMED_ENTITIES, - sizeof * NAMED_ENTITIES, cmp_s, - NULL); -#else - const char* const* entity = (const char* const*)bsearch(name, - NAMED_ENTITIES, - sizeof NAMED_ENTITIES / sizeof * NAMED_ENTITIES, - sizeof * NAMED_ENTITIES, cmp); -#endif - - return entity ? entity[1] : NULL; - } - - static size_t putc_utf8(unsigned long cp, char* buffer) - { - unsigned char* bytes = (unsigned char*)buffer; - - if (cp <= 0x007Ful) - { - bytes[0] = (unsigned char)cp; - return 1; - } - - if (cp <= 0x07FFul) - { - bytes[1] = (unsigned char)((2 << 6) | (cp & 0x3F)); - bytes[0] = (unsigned char)((6 << 5) | (cp >> 6)); - return 2; - } - - if (cp <= 0xFFFFul) - { - bytes[2] = (unsigned char)((2 << 6) | (cp & 0x3F)); - bytes[1] = (unsigned char)((2 << 6) | ((cp >> 6) & 0x3F)); - bytes[0] = (unsigned char)((14 << 4) | (cp >> 12)); - return 3; - } - - if (cp <= 0x10FFFFul) - { - bytes[3] = (unsigned char)((2 << 6) | (cp & 0x3F)); - bytes[2] = (unsigned char)((2 << 6) | ((cp >> 6) & 0x3F)); - bytes[1] = (unsigned char)((2 << 6) | ((cp >> 12) & 0x3F)); - bytes[0] = (unsigned char)((30 << 3) | (cp >> 18)); - return 4; - } - - return 0; - } - - static bool parse_entity( - const char* current, char** to, const char** from) - { - const char* end = strchr(current, ';'); - if (!end) return 0; - - if (current[1] == '#') - { - char* tail = NULL; - int errno_save = errno; - bool hex = current[2] == 'x' || current[2] == 'X'; - - errno = 0; - unsigned long cp = strtoul( - current + (hex ? 3 : 2), &tail, hex ? 16 : 10); - - bool fail = errno || tail != end || cp > UNICODE_MAX; - errno = errno_save; - if (fail) return 0; - - *to += putc_utf8(cp, *to); - *from = end + 1; - - return 1; - } - else - { - const char* entity = get_named_entity(¤t[1]); - if (!entity) return 0; - - size_t len = strlen(entity); - sf_memcpy(*to, len, entity, len); - - *to += len; - *from = end + 1; - - return 1; - } - } - - - - size_t decode_html_entities_utf8(char* dest, const char* src) - { - if (!src) src = dest; - - char* to = dest; - const char* from = src; - - for (const char* current; (current = strchr(from, '&'));) - { - memmove(to, from, (size_t)(current - from)); - to += current - from; - - if (parse_entity(current, &to, &from)) - continue; - - from = current; - *to++ = *from++; - } - - size_t remaining = strlen(from); - - memmove(to, from, remaining); - to += remaining; - *to = 0; - - return (size_t)(to - dest); - } - } -} - diff --git a/cpp/entities.hpp b/cpp/entities.hpp deleted file mode 100644 index 86ab3734b0..0000000000 --- a/cpp/entities.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2012 Christoph Gärtner - * Distributed under the Boost Software License, Version 1.0 - */ - -#ifndef DECODE_HTML_ENTITIES_UTF8_ -#define DECODE_HTML_ENTITIES_UTF8_ - -#include -#include - -namespace Snowflake -{ - namespace Client - { - size_t decode_html_entities_utf8(char* dest, const char* src); - /* Takes input from and decodes into , which should be a buffer - large enough to hold characters. - - If is , input will be taken from , decoding - the entities in-place. - - The function returns the length of the decoded string. - */ - } -} - -#endif diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index db6b609385..4cf21cd0bc 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -12,7 +12,7 @@ #include "Authenticator.hpp" #include "../logger/SFLogger.hpp" #include "error.h" -#include "../cpp/entities.hpp" +#include "../include/snowflake/entities.hpp" #include #include diff --git a/lib/http_perform.c b/lib/http_perform.c index 81391a32f0..2c4f5eab7e 100644 --- a/lib/http_perform.c +++ b/lib/http_perform.c @@ -454,8 +454,9 @@ sf_bool STDCALL http_perform(CURL *curl, // When renew timeout is reached, stop retry and return to the caller // to renew request - if (((retry) && (renew_timeout > 0) && - ((time(NULL) - elapsedRetryTime) >= renew_timeout)) || renew_timeout < 0) { + sf_bool renew_timeout_reached = retry && (renew_timeout > 0) && ((time(NULL) - elapsedRetryTime) >= renew_timeout); + sf_bool renew_timeout_disabled = retry && renew_timeout < 0; + if (renew_timeout_reached || renew_timeout_disabled) { retry = SF_BOOLEAN_FALSE; if (elapsed_time) { *elapsed_time += (time(NULL) - elapsedRetryTime); From 9659d7c10e79fa9d16bfd00786c044d92644aef5 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:45:16 -0700 Subject: [PATCH 21/55] added entities --- cpp/util/entities.cpp | 415 +++++++++++++++++++++++++++++++++ include/snowflake/entities.hpp | 28 +++ 2 files changed, 443 insertions(+) create mode 100644 cpp/util/entities.cpp create mode 100644 include/snowflake/entities.hpp diff --git a/cpp/util/entities.cpp b/cpp/util/entities.cpp new file mode 100644 index 0000000000..0373e54e89 --- /dev/null +++ b/cpp/util/entities.cpp @@ -0,0 +1,415 @@ +/** + * Copyright 2012, 2016 Christoph Gärtner + * Distributed under the Boost Software License, Version 1.0 + */ + +#include "../include/snowflake/entities.hpp" +#include +#include +#include +#include "snowflake/SF_CRTFunctionSafe.h" + +#define UNICODE_MAX 0x10FFFFul + +namespace Snowflake +{ + namespace Client + { + + static const char* const NAMED_ENTITIES[][2] = { + {"AElig;", "Æ"}, + {"Aacute;", "Á"}, + {"Acirc;", "Â"}, + {"Agrave;", "À"}, + {"Alpha;", "Α"}, + {"Aring;", "Å"}, + {"Atilde;", "Ã"}, + {"Auml;", "Ä"}, + {"Beta;", "Β"}, + {"Ccedil;", "Ç"}, + {"Chi;", "Χ"}, + {"Dagger;", "‡"}, + {"Delta;", "Δ"}, + {"ETH;", "Ð"}, + {"Eacute;", "É"}, + {"Ecirc;", "Ê"}, + {"Egrave;", "È"}, + {"Epsilon;", "Ε"}, + {"Eta;", "Η"}, + {"Euml;", "Ë"}, + {"Gamma;", "Γ"}, + {"Iacute;", "Í"}, + {"Icirc;", "Î"}, + {"Igrave;", "Ì"}, + {"Iota;", "Ι"}, + {"Iuml;", "Ï"}, + {"Kappa;", "Κ"}, + {"Lambda;", "Λ"}, + {"Mu;", "Μ"}, + {"Ntilde;", "Ñ"}, + {"Nu;", "Ν"}, + {"OElig;", "Œ"}, + {"Oacute;", "Ó"}, + {"Ocirc;", "Ô"}, + {"Ograve;", "Ò"}, + {"Omega;", "Ω"}, + {"Omicron;", "Ο"}, + {"Oslash;", "Ø"}, + {"Otilde;", "Õ"}, + {"Ouml;", "Ö"}, + {"Phi;", "Φ"}, + {"Pi;", "Π"}, + {"Prime;", "″"}, + {"Psi;", "Ψ"}, + {"Rho;", "Ρ"}, + {"Scaron;", "Š"}, + {"Sigma;", "Σ"}, + {"THORN;", "Þ"}, + {"Tau;", "Τ"}, + {"Theta;", "Θ"}, + {"Uacute;", "Ú"}, + {"Ucirc;", "Û"}, + {"Ugrave;", "Ù"}, + {"Upsilon;", "Υ"}, + {"Uuml;", "Ü"}, + {"Xi;", "Ξ"}, + {"Yacute;", "Ý"}, + {"Yuml;", "Ÿ"}, + {"Zeta;", "Ζ"}, + {"aacute;", "á"}, + {"acirc;", "â"}, + {"acute;", "´"}, + {"aelig;", "æ"}, + {"agrave;", "à"}, + {"alefsym;", "ℵ"}, + {"alpha;", "α"}, + {"amp;", "&"}, + {"and;", "∧"}, + {"ang;", "∠"}, + {"apos;", "'"}, + {"aring;", "å"}, + {"asymp;", "≈"}, + {"atilde;", "ã"}, + {"auml;", "ä"}, + {"bdquo;", "„"}, + {"beta;", "β"}, + {"brvbar;", "¦"}, + {"bull;", "•"}, + {"cap;", "∩"}, + {"ccedil;", "ç"}, + {"cedil;", "¸"}, + {"cent;", "¢"}, + {"chi;", "χ"}, + {"circ;", "ˆ"}, + {"clubs;", "♣"}, + {"cong;", "≅"}, + {"copy;", "©"}, + {"crarr;", "↵"}, + {"cup;", "∪"}, + {"curren;", "¤"}, + {"dArr;", "⇓"}, + {"dagger;", "†"}, + {"darr;", "↓"}, + {"deg;", "°"}, + {"delta;", "δ"}, + {"diams;", "♦"}, + {"divide;", "÷"}, + {"eacute;", "é"}, + {"ecirc;", "ê"}, + {"egrave;", "è"}, + {"empty;", "∅"}, + {"emsp;", "\xE2\x80\x83"}, + {"ensp;", "\xE2\x80\x82"}, + {"epsilon;", "ε"}, + {"equiv;", "≡"}, + {"eta;", "η"}, + {"eth;", "ð"}, + {"euml;", "ë"}, + {"euro;", "€"}, + {"exist;", "∃"}, + {"fnof;", "ƒ"}, + {"forall;", "∀"}, + {"frac12;", "½"}, + {"frac14;", "¼"}, + {"frac34;", "¾"}, + {"frasl;", "⁄"}, + {"gamma;", "γ"}, + {"ge;", "≥"}, + {"gt;", ">"}, + {"hArr;", "⇔"}, + {"harr;", "↔"}, + {"hearts;", "♥"}, + {"hellip;", "…"}, + {"iacute;", "í"}, + {"icirc;", "î"}, + {"iexcl;", "¡"}, + {"igrave;", "ì"}, + {"image;", "ℑ"}, + {"infin;", "∞"}, + {"int;", "∫"}, + {"iota;", "ι"}, + {"iquest;", "¿"}, + {"isin;", "∈"}, + {"iuml;", "ï"}, + {"kappa;", "κ"}, + {"lArr;", "⇐"}, + {"lambda;", "λ"}, + {"lang;", "〈"}, + {"laquo;", "«"}, + {"larr;", "←"}, + {"lceil;", "⌈"}, + {"ldquo;", "“"}, + {"le;", "≤"}, + {"lfloor;", "⌊"}, + {"lowast;", "∗"}, + {"loz;", "◊"}, + {"lrm;", "\xE2\x80\x8E"}, + {"lsaquo;", "‹"}, + {"lsquo;", "‘"}, + {"lt;", "<"}, + {"macr;", "¯"}, + {"mdash;", "—"}, + {"micro;", "µ"}, + {"middot;", "·"}, + {"minus;", "−"}, + {"mu;", "μ"}, + {"nabla;", "∇"}, + {"nbsp;", "\xC2\xA0"}, + {"ndash;", "–"}, + {"ne;", "≠"}, + {"ni;", "∋"}, + {"not;", "¬"}, + {"notin;", "∉"}, + {"nsub;", "⊄"}, + {"ntilde;", "ñ"}, + {"nu;", "ν"}, + {"oacute;", "ó"}, + {"ocirc;", "ô"}, + {"oelig;", "œ"}, + {"ograve;", "ò"}, + {"oline;", "‾"}, + {"omega;", "ω"}, + {"omicron;", "ο"}, + {"oplus;", "⊕"}, + {"or;", "∨"}, + {"ordf;", "ª"}, + {"ordm;", "º"}, + {"oslash;", "ø"}, + {"otilde;", "õ"}, + {"otimes;", "⊗"}, + {"ouml;", "ö"}, + {"para;", "¶"}, + {"part;", "∂"}, + {"permil;", "‰"}, + {"perp;", "⊥"}, + {"phi;", "φ"}, + {"pi;", "π"}, + {"piv;", "ϖ"}, + {"plusmn;", "±"}, + {"pound;", "£"}, + {"prime;", "′"}, + {"prod;", "∏"}, + {"prop;", "∝"}, + {"psi;", "ψ"}, + {"quot;", "\""}, + {"rArr;", "⇒"}, + {"radic;", "√"}, + {"rang;", "〉"}, + {"raquo;", "»"}, + {"rarr;", "→"}, + {"rceil;", "⌉"}, + {"rdquo;", "”"}, + {"real;", "ℜ"}, + {"reg;", "®"}, + {"rfloor;", "⌋"}, + {"rho;", "ρ"}, + {"rlm;", "\xE2\x80\x8F"}, + {"rsaquo;", "›"}, + {"rsquo;", "’"}, + {"sbquo;", "‚"}, + {"scaron;", "š"}, + {"sdot;", "⋅"}, + {"sect;", "§"}, + {"shy;", "\xC2\xAD"}, + {"sigma;", "σ"}, + {"sigmaf;", "ς"}, + {"sim;", "∼"}, + {"spades;", "♠"}, + {"sub;", "⊂"}, + {"sube;", "⊆"}, + {"sum;", "∑"}, + {"sup1;", "¹"}, + {"sup2;", "²"}, + {"sup3;", "³"}, + {"sup;", "⊃"}, + {"supe;", "⊇"}, + {"szlig;", "ß"}, + {"tau;", "τ"}, + {"there4;", "∴"}, + {"theta;", "θ"}, + {"thetasym;", "ϑ"}, + {"thinsp;", "\xE2\x80\x89"}, + {"thorn;", "þ"}, + {"tilde;", "˜"}, + {"times;", "×"}, + {"trade;", "™"}, + {"uArr;", "⇑"}, + {"uacute;", "ú"}, + {"uarr;", "↑"}, + {"ucirc;", "û"}, + {"ugrave;", "ù"}, + {"uml;", "¨"}, + {"upsih;", "ϒ"}, + {"upsilon;", "υ"}, + {"uuml;", "ü"}, + {"weierp;", "℘"}, + {"xi;", "ξ"}, + {"yacute;", "ý"}, + {"yen;", "¥"}, + {"yuml;", "ÿ"}, + {"zeta;", "ζ"}, + {"zwj;", "\xE2\x80\x8D"}, + {"zwnj;", "\xE2\x80\x8C"} + }; + + static int cmp(const void* key, const void* value) + { + return strncmp((const char*)key, *(const char* const*)value, + strlen(*(const char* const*)value)); + } + +#if defined(_WIN32) || defined(_WIN64) + static int cmp_s(void* pvlocale, const void* key, const void* value) + { + //UNUSED(pvlocale); + return cmp(key, value); + } +#endif + + static const char* get_named_entity(const char* name) + { +#if defined(_WIN32) || defined(_WIN64) + const char* const* entity = (const char* const*)bsearch_s(name, + NAMED_ENTITIES, + sizeof NAMED_ENTITIES / sizeof * NAMED_ENTITIES, + sizeof * NAMED_ENTITIES, cmp_s, + NULL); +#else + const char* const* entity = (const char* const*)bsearch(name, + NAMED_ENTITIES, + sizeof NAMED_ENTITIES / sizeof * NAMED_ENTITIES, + sizeof * NAMED_ENTITIES, cmp); +#endif + + return entity ? entity[1] : NULL; + } + + static size_t putc_utf8(unsigned long cp, char* buffer) + { + unsigned char* bytes = (unsigned char*)buffer; + + if (cp <= 0x007Ful) + { + bytes[0] = (unsigned char)cp; + return 1; + } + + if (cp <= 0x07FFul) + { + bytes[1] = (unsigned char)((2 << 6) | (cp & 0x3F)); + bytes[0] = (unsigned char)((6 << 5) | (cp >> 6)); + return 2; + } + + if (cp <= 0xFFFFul) + { + bytes[2] = (unsigned char)((2 << 6) | (cp & 0x3F)); + bytes[1] = (unsigned char)((2 << 6) | ((cp >> 6) & 0x3F)); + bytes[0] = (unsigned char)((14 << 4) | (cp >> 12)); + return 3; + } + + if (cp <= 0x10FFFFul) + { + bytes[3] = (unsigned char)((2 << 6) | (cp & 0x3F)); + bytes[2] = (unsigned char)((2 << 6) | ((cp >> 6) & 0x3F)); + bytes[1] = (unsigned char)((2 << 6) | ((cp >> 12) & 0x3F)); + bytes[0] = (unsigned char)((30 << 3) | (cp >> 18)); + return 4; + } + + return 0; + } + + static bool parse_entity( + const char* current, char** to, const char** from) + { + const char* end = strchr(current, ';'); + if (!end) return 0; + + if (current[1] == '#') + { + char* tail = NULL; + int errno_save = errno; + bool hex = current[2] == 'x' || current[2] == 'X'; + + errno = 0; + unsigned long cp = strtoul( + current + (hex ? 3 : 2), &tail, hex ? 16 : 10); + + bool fail = errno || tail != end || cp > UNICODE_MAX; + errno = errno_save; + if (fail) return 0; + + *to += putc_utf8(cp, *to); + *from = end + 1; + + return 1; + } + else + { + const char* entity = get_named_entity(¤t[1]); + if (!entity) return 0; + + size_t len = strlen(entity); + sf_memcpy(*to, len, entity, len); + + *to += len; + *from = end + 1; + + return 1; + } + } + + + + size_t decode_html_entities_utf8(char* dest, const char* src) + { + if (!src) src = dest; + + char* to = dest; + const char* from = src; + + for (const char* current; (current = strchr(from, '&'));) + { + memmove(to, from, (size_t)(current - from)); + to += current - from; + + if (parse_entity(current, &to, &from)) + continue; + + from = current; + *to++ = *from++; + } + + size_t remaining = strlen(from); + + memmove(to, from, remaining); + to += remaining; + *to = 0; + + return (size_t)(to - dest); + } + } +} + diff --git a/include/snowflake/entities.hpp b/include/snowflake/entities.hpp new file mode 100644 index 0000000000..86ab3734b0 --- /dev/null +++ b/include/snowflake/entities.hpp @@ -0,0 +1,28 @@ +/** + * Copyright 2012 Christoph Gärtner + * Distributed under the Boost Software License, Version 1.0 + */ + +#ifndef DECODE_HTML_ENTITIES_UTF8_ +#define DECODE_HTML_ENTITIES_UTF8_ + +#include +#include + +namespace Snowflake +{ + namespace Client + { + size_t decode_html_entities_utf8(char* dest, const char* src); + /* Takes input from and decodes into , which should be a buffer + large enough to hold characters. + + If is , input will be taken from , decoding + the entities in-place. + + The function returns the length of the decoded string. + */ + } +} + +#endif From 467a7b88a3c96ed4c24aa1d58f6033ac727d7a2e Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:35:02 -0700 Subject: [PATCH 22/55] fix error when pull the master branch --- include/snowflake/client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/snowflake/client.h b/include/snowflake/client.h index 7b13d51774..e996d62e1c 100644 --- a/include/snowflake/client.h +++ b/include/snowflake/client.h @@ -263,7 +263,7 @@ typedef enum SF_ATTRIBUTE { SF_CON_MAX_BINARY_SIZE, SF_CON_MAX_VARIANT_SIZE, SF_CON_DISABLE_SAML_URL_CHECK, - SF_CON_OCSP_FAIL_OPE + SF_CON_OCSP_FAIL_OPEN, SF_DIR_QUERY_URL, SF_DIR_QUERY_URL_PARAM, SF_DIR_QUERY_TOKEN, From cc6c84c6cd807b37e2b205fc35df54fa79a944dd Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:26:58 -0700 Subject: [PATCH 23/55] fix error --- tests/test_manual_connect.c | 107 ++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 60 deletions(-) diff --git a/tests/test_manual_connect.c b/tests/test_manual_connect.c index 42458a1b02..f89870cbb8 100644 --- a/tests/test_manual_connect.c +++ b/tests/test_manual_connect.c @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018-2024 Snowflake Computing, Inc. All rights reserved. - */ +* Copyright (c) 2018-2024 Snowflake Computing, Inc. All rights reserved. +*/ #include "utils/test_setup.h" @@ -13,8 +13,7 @@ void test_oauth_connect(void **unused) snowflake_set_attribute(sf, SF_CON_ACCOUNT, getenv("SNOWFLAKE_TEST_ACCOUNT")); snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_USER")); - snowflake_set_attribute(sf, SF_CON_PASSWORD, - getenv("SNOWFLAKE_TEST_PASSWORD")); + char *host, *port, *protocol, *role; host = getenv("SNOWFLAKE_TEST_HOST"); if (host) @@ -27,7 +26,7 @@ void test_oauth_connect(void **unused) snowflake_set_attribute(sf, SF_CON_PORT, port); } protocol = getenv("SNOWFLAKE_TEST_PROTOCOL"); - if (protocol) + if (protocol) { snowflake_set_attribute(sf, SF_CON_PROTOCOL, protocol); } @@ -41,67 +40,56 @@ void test_oauth_connect(void **unused) snowflake_set_attribute(sf, SF_CON_OAUTH_TOKEN, token); SF_STATUS status = snowflake_connect(sf); - if (status != SF_STATUS_SUCCESS) - { + if (status != SF_STATUS_SUCCESS) { dump_error(&(sf->error)); } assert_int_equal(status, SF_STATUS_SUCCESS); snowflake_term(sf); } -void test_mfa_connect_with_duo_push(void **unused) +void test_mfa_connect_with_duo_push(void** unused) { - SF_CONNECT *sf = snowflake_init(); - snowflake_set_attribute(sf, SF_CON_ACCOUNT, - getenv("SNOWFLAKE_TEST_ACCOUNT")); - snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_USER")); - snowflake_set_attribute(sf, SF_CON_PASSWORD, - getenv("SNOWFLAKE_TEST_PASSWORD")); - char *host, *port, *protocol; - host = getenv("SNOWFLAKE_TEST_HOST"); - if (host) - { - snowflake_set_attribute(sf, SF_CON_HOST, host); - } - port = getenv("SNOWFLAKE_TEST_PORT"); - if (port) - { - snowflake_set_attribute(sf, SF_CON_PORT, port); - } - protocol = getenv("SNOWFLAKE_TEST_PROTOCOL"); - if (protocol) - { - snowflake_set_attribute(sf, SF_CON_PROTOCOL, protocol); - } - role = getenv("SNOWFLAKE_TEST_ROLE"); - if (role) - { - snowflake_set_attribute(sf, SF_CON_ROLE, role); - } - char* token = ""; - snowflake_set_attribute(sf, SF_CON_AUTHENTICATOR, "oauth"); - snowflake_set_attribute(sf, SF_CON_OAUTH_TOKEN, token); - - SF_STATUS status = snowflake_connect(sf); - if (status != SF_STATUS_SUCCESS) - { - dump_error(&(sf->error)); - } - assert_int_equal(status, SF_STATUS_SUCCESS); - snowflake_term(sf); -} + SF_CONNECT* sf = snowflake_init(); + snowflake_set_attribute(sf, SF_CON_ACCOUNT, + getenv("SNOWFLAKE_TEST_ACCOUNT")); + snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_USER")); + snowflake_set_attribute(sf, SF_CON_PASSWORD, + getenv("SNOWFLAKE_TEST_PASSWORD")); + char* host, * port, * protocol; + host = getenv("SNOWFLAKE_TEST_HOST"); + if (host) + { + snowflake_set_attribute(sf, SF_CON_HOST, host); + } + port = getenv("SNOWFLAKE_TEST_PORT"); + if (port) + { + snowflake_set_attribute(sf, SF_CON_PORT, port); + } + protocol = getenv("SNOWFLAKE_TEST_PROTOCOL"); + if (protocol) + { + snowflake_set_attribute(sf, SF_CON_PROTOCOL, protocol); + } + SF_STATUS status = snowflake_connect(sf); + if (status != SF_STATUS_SUCCESS) + { + dump_error(&(sf->error)); + } + assert_int_equal(status, SF_STATUS_SUCCESS); + snowflake_term(sf); +} -void test_mfa_connect_with_duo_passcode(void **unused) +void test_mfa_connect_with_duo_passcode(void** unused) { - SF_CONNECT *sf = snowflake_init(); + SF_CONNECT* sf = snowflake_init(); snowflake_set_attribute(sf, SF_CON_ACCOUNT, - getenv("SNOWFLAKE_TEST_ACCOUNT")); + getenv("SNOWFLAKE_TEST_ACCOUNT")); snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_USER")); snowflake_set_attribute(sf, SF_CON_PASSWORD, - getenv("SNOWFLAKE_TEST_PASSWORD")); - char *host, *port, *protocol, *passcode; - + getenv("SNOWFLAKE_TEST_PASSWORD")); + char* host, * port, * protocol, * passcode; host = getenv("SNOWFLAKE_TEST_HOST"); if (host) { @@ -135,7 +123,7 @@ void test_mfa_connect_with_duo_passcode(void **unused) snowflake_term(sf); } -void test_mfa_connect_with_duo_passcode(void** unused) +void test_mfa_connect_with_duo_passcodeInPassword(void** unused) { SF_CONNECT* sf = snowflake_init(); snowflake_set_attribute(sf, SF_CON_ACCOUNT, @@ -147,7 +135,6 @@ void test_mfa_connect_with_duo_passcode(void** unused) snowflake_set_attribute(sf, SF_CON_PASSCODE_IN_PASSWORD, &passcode_in_password); char* host, * port, * protocol; - host = getenv("SNOWFLAKE_TEST_HOST"); if (host) { @@ -173,7 +160,7 @@ void test_mfa_connect_with_duo_passcode(void** unused) snowflake_term(sf); } -void test_mfa_connect_with_duo_passcodeInPassword(void** unused) { +void test_okta_connect(void** unused) { SF_CONNECT* sf = snowflake_init(); sf_bool disable_saml_url_check = SF_BOOLEAN_TRUE; sf->disable_saml_url_check = &disable_saml_url_check; @@ -195,14 +182,10 @@ void test_mfa_connect_with_duo_passcodeInPassword(void** unused) { } protocol = getenv("SNOWFLAKE_TEST_PROTOCOL"); if (protocol) { - snowflake_set_attribute(sf, SF_CON_PROTOCOL, protocol); } - SF_STATUS status = snowflake_connect(sf); - if (status != SF_STATUS_SUCCESS) { - dump_error(&(sf->error)); } assert_int_equal(status, SF_STATUS_SUCCESS); @@ -236,6 +219,10 @@ int main(void) tests[0].name = "test_mfa_connect_with_duo_passcodeInPassword"; tests[0].test_func = test_mfa_connect_with_duo_passcodeInPassword; } + else if (strcmp(manual_test, "test_okta_connect") == 0) { + tests[0].name = "test_okta_connect"; + tests[0].test_func = test_okta_connect; + } else { printf("No matching test found for: %s\n", manual_test); } From 82c95ff19e91d0e3613515b045f9df80645e2f29 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:58:44 -0800 Subject: [PATCH 24/55] fix --- CMakeLists.txt | 5 +++++ cpp/lib/Authenticator.cpp | 45 +++++++++++++++++++++------------------ cpp/lib/Authenticator.hpp | 15 ++++++++----- lib/util.h | 21 ++++++++++++++++++ 4 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 lib/util.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c1be2279c..86a61f512e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,7 @@ set(SOURCE_FILES lib/basic_types.c lib/error.h lib/error.c + lib/util.h lib/client_int.h lib/chunk_downloader.h lib/chunk_downloader.c @@ -237,6 +238,7 @@ set(SOURCE_FILES_CPP_WRAPPER cpp/lib/ResultSetJson.hpp cpp/lib/Authenticator.cpp cpp/lib/Authenticator.hpp + cpp/lib/util.cpp cpp/jwt/jwtWrapper.cpp cpp/util/SnowflakeCommon.cpp cpp/util/SFURL.cpp @@ -408,6 +410,7 @@ if (LINUX) deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/azure/include deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/cmocka/include deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/uuid/include + deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/picojson/include include lib) endif() @@ -423,6 +426,7 @@ if (APPLE) deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/aws/include deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/azure/include deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/cmocka/include + deps-build/${PLATFORM}/${CMAKE_BUILD_TYPE}/picojson/include include lib) endif() @@ -437,6 +441,7 @@ if (WIN32) deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/aws/include deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/azure/include deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/cmocka/include + deps-build/${PLATFORM}/${VSDIR}/${CMAKE_BUILD_TYPE}/picojson/include include lib) if (CMAKE_SIZEOF_VOID_P EQUAL 8) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index fe727b12a2..2b7cdef97a 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -20,6 +20,8 @@ #include #include "curl_desc_pool.h" #include "snowflake/Exceptions.hpp" +#include "cJSON.h" +#include "../cpp/jwt/Util.hpp" #include @@ -162,8 +164,12 @@ extern "C" { try { - static_cast(conn->auth_object)-> - updateDataMap(body); + jsonObject_t picoBody; + cJSON* data = snowflake_cJSON_GetObjectItem(body, "data"); + cJSONtoPicoJson(data, picoBody); + static_cast(conn->auth_object)-> + updateDataMap(picoBody); + picoJsonTocJson(picoBody, &body); } catch (...) { @@ -182,8 +188,12 @@ extern "C" { try { - static_cast(conn->auth_object)-> - renewDataMap(body); + jsonObject_t picoBody; + cJSON* data = snowflake_cJSON_GetObjectItem(body, "data"); + cJSONtoPicoJson(data, picoBody); + static_cast(conn->auth_object)-> + renewDataMap(picoBody); + picoJsonTocJson(picoBody, &body); } catch (...) { @@ -219,7 +229,6 @@ extern "C" { return; } - } // extern "C" namespace Snowflake @@ -227,7 +236,7 @@ namespace Snowflake namespace Client { - void IAuthenticator::renewDataMap(cJSON *dataMap) + void IAuthenticator::renewDataMap(jsonObject_t& dataMap) { authenticate(); updateDataMap(dataMap); @@ -317,13 +326,10 @@ namespace Client claimSet->addClaim("exp", (long)seconds.count() + m_timeOut); } - void AuthenticatorJWT::updateDataMap(cJSON* dataMap) + void AuthenticatorJWT::updateDataMap(jsonObject_t &dataMap) { - cJSON* data = snowflake_cJSON_GetObjectItem(dataMap, "data"); - snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); - snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); - snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_JWT); - snowflake_cJSON_AddStringToObject(data, "TOKEN", m_jwt->serialize(m_privKey).c_str()); + dataMap["AUTHENTICATOR"] = picojson::value(SF_AUTHENTICATOR_JWT); + dataMap["TOKEN"] = picojson::value(m_jwt->serialize(m_privKey)); } std::string AuthenticatorJWT::extractPublicKey(EVP_PKEY *privKey) @@ -537,16 +543,13 @@ namespace Client } } - void AuthenticatorOKTA::updateDataMap(cJSON* dataMap) + void AuthenticatorOKTA::updateDataMap(jsonObject_t& dataMap) { - cJSON* data = snowflake_cJSON_GetObjectItem(dataMap, "data"); - snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); - snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); - snowflake_cJSON_DeleteItemFromObject(data, "LOGIN_NAME"); - snowflake_cJSON_DeleteItemFromObject(data, "PASSWORD"); - snowflake_cJSON_DeleteItemFromObject(data, "EXT_AUTHN_DUO_METHOD"); - - snowflake_cJSON_AddStringToObject(data, "RAW_SAML_RESPONSE", m_samlResponse.c_str()); + dataMap.erase("LOGIN_NAME"); + dataMap.erase("PASSWORD"); + dataMap.erase("EXT_AUTHN_DUO_METHOD"); + + dataMap["RAW_SAML_RESPONSE"] = picojson::value(m_samlResponse); } std::string AuthenticatorOKTA::extractPostBackUrlFromSamlResponse(std::string html) diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 46bbcd8e91..781b315aff 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -15,8 +15,11 @@ #include "snowflake/IJwt.hpp" #include "snowflake/IBase64.hpp" #include "authenticator.h" -#include "cJSON.h" +#include "picojson.h" #include "snowflake/SFURL.hpp" +#include "../../lib/util.h" + + namespace Snowflake { @@ -37,7 +40,7 @@ namespace Client virtual void authenticate()=0; - virtual void updateDataMap(cJSON * dataMap)=0; + virtual void updateDataMap(jsonObject_t& dataMap)=0; // Retrieve authenticator renew timeout, return 0 if not available. // When the authenticator renew timeout is available, the connection should @@ -50,7 +53,7 @@ namespace Client // Renew the autentication and update datamap. // The default behavior is to call authenticate() and updateDataMap(). - virtual void renewDataMap(cJSON * dataMap); + virtual void renewDataMap(jsonObject_t& dataMap); protected: int64 m_renewTimeout; @@ -68,7 +71,9 @@ namespace Client void authenticate(); - void updateDataMap(cJSON* dataMap); + void updateDataMap(jsonObject_t &dataMap); + + private: void loadPrivateKey(const std::string &privateKeyFile, const std::string &passcode); @@ -97,7 +102,7 @@ namespace Client void authenticate(); - void updateDataMap(cJSON* dataMap); + void updateDataMap(jsonObject_t &dataMap); private: SF_CONNECT* m_connection; diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 0000000000..7f54a0da46 --- /dev/null +++ b/lib/util.h @@ -0,0 +1,21 @@ +#ifndef SNOWFLAKE_UTIL_H +#define SNOWFLAKE_UTIL_H + +#include "picojson.h" +#include "cJSON.h" + +#ifdef __cplusplus +extern "C" { +#endif + typedef picojson::value jsonValue_t; + typedef std::map jsonObject_t; + typedef std::vector jsonArray_t; + + void cJSONtoPicoJson(cJSON* cjson, jsonObject_t& picojson); + void picoJsonTocJson(jsonObject_t &picojson, cJSON** cjson); + +#ifdef __cplusplus +} +#endif + +#endif //SNOWFLAKE_UTIL_H \ No newline at end of file From b26e37896e350c0e6827ec56bd315c63cd586643 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:21:27 -0800 Subject: [PATCH 25/55] add util.cpp --- cpp/lib/util.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 cpp/lib/util.cpp diff --git a/cpp/lib/util.cpp b/cpp/lib/util.cpp new file mode 100644 index 0000000000..88e18ebc7d --- /dev/null +++ b/cpp/lib/util.cpp @@ -0,0 +1,26 @@ +#include "../../lib/util.h" + +namespace Snowflake +{ +namespace Client +{ + // wrapper functions for C + extern "C" { + void cJSONtoPicoJson(cJSON* cjson, jsonObject_t& picojson) + { + jsonValue_t v; + const char* dataStr = snowflake_cJSON_Print(cjson); + picojson::parse(v, dataStr); + picojson = v.get(); + } + + void picoJsonTocJson(jsonObject_t& picojson, cJSON** cjson) + { + std::string body_str = picojson::value(picojson).serialize(); + cJSON* new_body = snowflake_cJSON_Parse(body_str.c_str()); + snowflake_cJSON_DeleteItemFromObject(*cjson, "data"); + snowflake_cJSON_AddItemToObject(*cjson, "data", new_body); + } + } +} +} \ No newline at end of file From d8deac1d78b6d2b2bf443a45a80a2dfc27cbd5c6 Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:16:51 -0800 Subject: [PATCH 26/55] fix --- tests/test_manual_connect.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_manual_connect.c b/tests/test_manual_connect.c index f89870cbb8..c34f872828 100644 --- a/tests/test_manual_connect.c +++ b/tests/test_manual_connect.c @@ -162,8 +162,6 @@ void test_mfa_connect_with_duo_passcodeInPassword(void** unused) void test_okta_connect(void** unused) { SF_CONNECT* sf = snowflake_init(); - sf_bool disable_saml_url_check = SF_BOOLEAN_TRUE; - sf->disable_saml_url_check = &disable_saml_url_check; snowflake_set_attribute(sf, SF_CON_ACCOUNT, getenv("SNOWFLAKE_TEST_ACCOUNT")); snowflake_set_attribute(sf, SF_CON_USER, getenv("SNOWFLAKE_TEST_OKTA_USERNAME")); From d928094e92a0f4c8076fc7b16b68a586540c0502 Mon Sep 17 00:00:00 2001 From: John Yun Date: Wed, 6 Nov 2024 16:21:56 -0800 Subject: [PATCH 27/55] refactored --- cpp/lib/Authenticator.cpp | 197 ++++++++++++++++++++++---------------- cpp/lib/Authenticator.hpp | 15 ++- cpp/lib/util.cpp | 9 ++ lib/util.h | 1 + 4 files changed, 136 insertions(+), 86 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 2b7cdef97a..63e020ecdd 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -148,12 +148,12 @@ extern "C" { data = snowflake_cJSON_CreateObject(); snowflake_cJSON_AddItemToObject(body, "data", data); } - + + snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); + snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); if (AUTH_OAUTH == getAuthenticatorType(conn->authenticator)) { - snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); - snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); snowflake_cJSON_AddStringToObject(data, "TOKEN", conn->oauth_token); } @@ -399,36 +399,23 @@ namespace Client // nop } - void AuthenticatorOKTA::authenticate() + void AuthenticatorOKTA::getIdp(jsonObject_t& respData) { - // 1. get authenticator info cJSON* resp_data = NULL; if (!getIdpInfo(m_connection, &resp_data)) { AUTH_THROW(&m_connection->error); } + cJSONtoPicoJson(resp_data, respData); + } - SF_ERROR_STRUCT* err = &m_connection->error; - char* token_url_str = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp_data, "tokenUrl")); - char* sso_url_str = snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp_data, "ssoUrl")); - std::string post_back_url; - std::string server_url; - - // 2. verify ssoUrl and tokenUrl contains same prefix - if (!SFURL::urlHasSamePrefix(token_url_str, m_connection->authenticator)) - { - CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "The specified authenticator is not supported, " - "authenticator=%s, token url=%s, sso url=%s", - m_connection->authenticator, token_url_str, sso_url_str); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err->msg); - } - + void AuthenticatorOKTA::getOneTimeToken(jsonObject_t& respData) + { void* curl_desc; CURL* curl; - curl_desc = get_curl_desc_from_pool(token_url_str, m_connection->proxy, m_connection->no_proxy); + curl_desc = get_curl_desc_from_pool(tokenURLStr.c_str(), m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); + SF_ERROR_STRUCT* err = &m_connection->error; // When renewTimeout < 0, throws Exception // for each retry attempt so we can renew the one time token @@ -438,13 +425,6 @@ namespace Client int64 elapsed_time = 0; int8* retried_count = &m_connection->retry_count; - // 3. get one time token from okta - cJSON* body = snowflake_cJSON_CreateObject(); - snowflake_cJSON_AddStringToObject(body, "username", m_connection->user); - snowflake_cJSON_AddStringToObject(body, "password", m_connection->password); - - char* s_body = snowflake_cJSON_Print(body); - // headers for step 4 // add header for accept format SF_HEADER* header = sf_header_create(); @@ -454,11 +434,9 @@ namespace Client AUTH_THROW(err); } cJSON* resp = NULL; - - while (true) - { - if (!curl_post_call(m_connection, curl, token_url_str, header, s_body, &resp, - &m_connection->error, 0, retry_max_count, retry_timeout, + std::string s_body = picojson::value(respData).serialize(); + if (!curl_post_call(m_connection, curl, (char*)tokenURLStr.c_str(), header, (char*)s_body.c_str(), &resp, + err, 0, retry_max_count, retry_timeout, &elapsed_time, retried_count, 0, false)) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", @@ -468,7 +446,7 @@ namespace Client goto cleanup; } - char* one_time_token = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? + onetimeToken = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); if (elapsed_time >= retry_timeout) @@ -479,76 +457,125 @@ namespace Client SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); goto cleanup; } + + //update retry info. + m_connection->retry_timeout -= elapsed_time; - // 4. get SAML response - SFURL sso_url = SFURL::parse(sso_url_str); - sso_url.addQueryParam("onetimetoken", one_time_token); - resp = NULL; - if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, &m_connection->error, -1, retry_max_count, retry_timeout, &elapsed_time, retried_count)) - { - if (elapsed_time >= retry_timeout || *retried_count >= retry_max_count) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retried_count, elapsed_time); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - goto cleanup; - } - //Need to increase retried_count on the authentication level - (*retried_count)++; - CXX_LOG_TRACE("sf", "Connection", "Connect", - "Retry on getting SAML response with one time token renewed for %d times " - "with updated retryTimeout = %d", - *retried_count, retry_timeout - elapsed_time); - continue; - } + cleanup: + sf_header_destroy(header); + snowflake_cJSON_Delete(resp); + free_curl_desc(curl_desc); + if (err->error_code != SF_STATUS_SUCCESS) { + AUTH_THROW(err); + } + } - if (elapsed_time >= retry_timeout) + bool AuthenticatorOKTA::getSAMLResponse() + { + bool isRetry = false; + int64 retry_timeout = get_login_timeout(m_connection); + int64 retry_max_count = get_login_retry_count(m_connection); + int64 elapsed_time = 0; + int8* retried_count = &m_connection->retry_count; + + void* curl_desc; + CURL* curl; + curl_desc = get_curl_desc_from_pool(ssoURLStr.c_str(), m_connection->proxy, m_connection->no_proxy); + curl = get_curl_from_desc(curl_desc); + SF_ERROR_STRUCT* err = &m_connection->error; + SFURL sso_url = SFURL::parse(ssoURLStr); + sso_url.addQueryParam("onetimetoken", onetimeToken); + cJSON* resp = NULL; + if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, err, -1, retry_max_count, retry_timeout, &elapsed_time, retried_count)) + { + if (elapsed_time >= retry_timeout || *retried_count >= retry_max_count) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retry_timeout, elapsed_time); + retried_count, elapsed_time); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); goto cleanup; } - break; + //Need to increase retried_count on the authentication level + (*retried_count)++; + CXX_LOG_TRACE("sf", "Connection", "Connect", + "Retry on getting SAML response with one time token renewed for %d times " + "with updated retryTimeout = %d", + *retried_count, retry_timeout - elapsed_time); + isRetry = true; + goto cleanup; + } + //update retry info. + m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); + + cleanup: + m_connection->retry_timeout -= elapsed_time; + free_curl_desc(curl_desc); + snowflake_cJSON_Delete(resp); + if (err->error_code != SF_STATUS_SUCCESS) { + AUTH_THROW(err); + } + return isRetry; + } + + void AuthenticatorOKTA::authenticate() + { + // 1. get authenticator info + jsonObject_t dataMap; + getIdp(dataMap); + + SF_ERROR_STRUCT* err = &m_connection->error; + tokenURLStr = dataMap["tokenUrl"].get(); + ssoURLStr = dataMap["ssoUrl"].get(); + + // 2. verify ssoUrl and tokenUrl contains same prefix + if (!SFURL::urlHasSamePrefix(tokenURLStr, m_connection->authenticator)) + { + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "The specified authenticator is not supported, " + "authenticator=%s, token url=%s, sso url=%s", + m_connection->authenticator, tokenURLStr, ssoURLStr); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err->msg); } - m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); - // 5. Validate post_back_url matches Snowflake URL - post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); - server_url = getServerURLSync().toString(); - if ((!m_connection->disable_saml_url_check) && - (!SFURL::urlHasSamePrefix(post_back_url, server_url))) + // 3. get one time token from okta + dataMap.clear(); + dataMap["username"] = picojson::value(m_connection->user); + dataMap["password"] = picojson::value(m_connection->password); + while (true) + { + getOneTimeToken(dataMap); + + // 4. get SAML response + if (!getSAMLResponse()) { - CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "The specified authenticator and destination URL in " - "Saml Assertion did not " - "match, expected=%s, post back=%s", - server_url.c_str(), - post_back_url.c_str()); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "SFSamlResponseVerificationFailed", SF_SQLSTATE_GENERAL_ERROR); - goto cleanup; + break; } - //update retry info. - m_connection->retry_timeout -= elapsed_time; + } - cleanup: - sf_header_destroy(header); - free_curl_desc(curl_desc); - snowflake_cJSON_Delete(body); - if (err->error_code != SF_STATUS_SUCCESS) { - AUTH_THROW(err); + // 5. Validate post_back_url matches Snowflake URL + std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); + std::string server_url = getServerURLSync().toString(); + if ((!m_connection->disable_saml_url_check) && + (!SFURL::urlHasSamePrefix(post_back_url, server_url))) + { + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "The specified authenticator and destination URL in " + "Saml Assertion did not " + "match, expected=%s, post back=%s", + server_url.c_str(), + post_back_url.c_str()); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "SFSamlResponseVerificationFailed", SF_SQLSTATE_GENERAL_ERROR); } - } + } void AuthenticatorOKTA::updateDataMap(jsonObject_t& dataMap) { dataMap.erase("LOGIN_NAME"); dataMap.erase("PASSWORD"); dataMap.erase("EXT_AUTHN_DUO_METHOD"); - dataMap["RAW_SAML_RESPONSE"] = picojson::value(m_samlResponse); } diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 781b315aff..7bbac80e2a 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -93,6 +93,7 @@ namespace Client static std::vector SHA256(const std::vector &message); }; + class AuthenticatorOKTA : public IAuthenticator { public: @@ -102,11 +103,23 @@ namespace Client void authenticate(); - void updateDataMap(jsonObject_t &dataMap); + void updateDataMap(jsonObject_t& dataMap); + + protected: + //Step1 + virtual void getIdp(jsonObject_t& respData); + //Step3 + virtual void getOneTimeToken(jsonObject_t& respData); + //Step4 + virtual bool getSAMLResponse(); + private: SF_CONNECT* m_connection; std::string m_samlResponse; + std::string tokenURLStr; + std::string ssoURLStr; + std::string onetimeToken; /** * Extract post back url from samel response. Input is in HTML format. diff --git a/cpp/lib/util.cpp b/cpp/lib/util.cpp index 88e18ebc7d..ff34aaede8 100644 --- a/cpp/lib/util.cpp +++ b/cpp/lib/util.cpp @@ -21,6 +21,15 @@ namespace Client snowflake_cJSON_DeleteItemFromObject(*cjson, "data"); snowflake_cJSON_AddItemToObject(*cjson, "data", new_body); } + + void strToPicoJson(jsonObject_t& picojson, std::string str) + { + jsonValue_t v; + picojson::parse(v, str); + picojson = v.get(); + } + + } } } \ No newline at end of file diff --git a/lib/util.h b/lib/util.h index 7f54a0da46..589324dbda 100644 --- a/lib/util.h +++ b/lib/util.h @@ -13,6 +13,7 @@ extern "C" { void cJSONtoPicoJson(cJSON* cjson, jsonObject_t& picojson); void picoJsonTocJson(jsonObject_t &picojson, cJSON** cjson); + void strToPicoJson(jsonObject_t& picojson, std::string str); #ifdef __cplusplus } From ef3612ccabf435c702ee442de4d07d996feee680 Mon Sep 17 00:00:00 2001 From: John Yun Date: Wed, 6 Nov 2024 17:23:44 -0800 Subject: [PATCH 28/55] fix --- cpp/lib/Authenticator.cpp | 2 +- cpp/lib/util.cpp | 2 - deps/zlib-1.3.1/zconf.h | 543 -------------------------------------- 3 files changed, 1 insertion(+), 546 deletions(-) delete mode 100644 deps/zlib-1.3.1/zconf.h diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 63e020ecdd..a8855f7680 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -534,7 +534,7 @@ namespace Client CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "The specified authenticator is not supported, " "authenticator=%s, token url=%s, sso url=%s", - m_connection->authenticator, tokenURLStr, ssoURLStr); + m_connection->authenticator, tokenURLStr.c_str(), ssoURLStr.c_str()); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); AUTH_THROW(err->msg); } diff --git a/cpp/lib/util.cpp b/cpp/lib/util.cpp index ff34aaede8..317df3cce6 100644 --- a/cpp/lib/util.cpp +++ b/cpp/lib/util.cpp @@ -28,8 +28,6 @@ namespace Client picojson::parse(v, str); picojson = v.get(); } - - } } } \ No newline at end of file diff --git a/deps/zlib-1.3.1/zconf.h b/deps/zlib-1.3.1/zconf.h deleted file mode 100644 index 62adc8d843..0000000000 --- a/deps/zlib-1.3.1/zconf.h +++ /dev/null @@ -1,543 +0,0 @@ -/* zconf.h -- configuration of the zlib compression library - * Copyright (C) 1995-2024 Jean-loup Gailly, Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ - -/* @(#) $Id$ */ - -#ifndef ZCONF_H -#define ZCONF_H - -/* - * If you *really* need a unique prefix for all types and library functions, - * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. - * Even better than compiling with -DZ_PREFIX would be to use configure to set - * this permanently in zconf.h using "./configure --zprefix". - */ -#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ -# define Z_PREFIX_SET - -/* all linked symbols and init macros */ -# define _dist_code z__dist_code -# define _length_code z__length_code -# define _tr_align z__tr_align -# define _tr_flush_bits z__tr_flush_bits -# define _tr_flush_block z__tr_flush_block -# define _tr_init z__tr_init -# define _tr_stored_block z__tr_stored_block -# define _tr_tally z__tr_tally -# define adler32 z_adler32 -# define adler32_combine z_adler32_combine -# define adler32_combine64 z_adler32_combine64 -# define adler32_z z_adler32_z -# ifndef Z_SOLO -# define compress z_compress -# define compress2 z_compress2 -# define compressBound z_compressBound -# endif -# define crc32 z_crc32 -# define crc32_combine z_crc32_combine -# define crc32_combine64 z_crc32_combine64 -# define crc32_combine_gen z_crc32_combine_gen -# define crc32_combine_gen64 z_crc32_combine_gen64 -# define crc32_combine_op z_crc32_combine_op -# define crc32_z z_crc32_z -# define deflate z_deflate -# define deflateBound z_deflateBound -# define deflateCopy z_deflateCopy -# define deflateEnd z_deflateEnd -# define deflateGetDictionary z_deflateGetDictionary -# define deflateInit z_deflateInit -# define deflateInit2 z_deflateInit2 -# define deflateInit2_ z_deflateInit2_ -# define deflateInit_ z_deflateInit_ -# define deflateParams z_deflateParams -# define deflatePending z_deflatePending -# define deflatePrime z_deflatePrime -# define deflateReset z_deflateReset -# define deflateResetKeep z_deflateResetKeep -# define deflateSetDictionary z_deflateSetDictionary -# define deflateSetHeader z_deflateSetHeader -# define deflateTune z_deflateTune -# define deflate_copyright z_deflate_copyright -# define get_crc_table z_get_crc_table -# ifndef Z_SOLO -# define gz_error z_gz_error -# define gz_intmax z_gz_intmax -# define gz_strwinerror z_gz_strwinerror -# define gzbuffer z_gzbuffer -# define gzclearerr z_gzclearerr -# define gzclose z_gzclose -# define gzclose_r z_gzclose_r -# define gzclose_w z_gzclose_w -# define gzdirect z_gzdirect -# define gzdopen z_gzdopen -# define gzeof z_gzeof -# define gzerror z_gzerror -# define gzflush z_gzflush -# define gzfread z_gzfread -# define gzfwrite z_gzfwrite -# define gzgetc z_gzgetc -# define gzgetc_ z_gzgetc_ -# define gzgets z_gzgets -# define gzoffset z_gzoffset -# define gzoffset64 z_gzoffset64 -# define gzopen z_gzopen -# define gzopen64 z_gzopen64 -# ifdef _WIN32 -# define gzopen_w z_gzopen_w -# endif -# define gzprintf z_gzprintf -# define gzputc z_gzputc -# define gzputs z_gzputs -# define gzread z_gzread -# define gzrewind z_gzrewind -# define gzseek z_gzseek -# define gzseek64 z_gzseek64 -# define gzsetparams z_gzsetparams -# define gztell z_gztell -# define gztell64 z_gztell64 -# define gzungetc z_gzungetc -# define gzvprintf z_gzvprintf -# define gzwrite z_gzwrite -# endif -# define inflate z_inflate -# define inflateBack z_inflateBack -# define inflateBackEnd z_inflateBackEnd -# define inflateBackInit z_inflateBackInit -# define inflateBackInit_ z_inflateBackInit_ -# define inflateCodesUsed z_inflateCodesUsed -# define inflateCopy z_inflateCopy -# define inflateEnd z_inflateEnd -# define inflateGetDictionary z_inflateGetDictionary -# define inflateGetHeader z_inflateGetHeader -# define inflateInit z_inflateInit -# define inflateInit2 z_inflateInit2 -# define inflateInit2_ z_inflateInit2_ -# define inflateInit_ z_inflateInit_ -# define inflateMark z_inflateMark -# define inflatePrime z_inflatePrime -# define inflateReset z_inflateReset -# define inflateReset2 z_inflateReset2 -# define inflateResetKeep z_inflateResetKeep -# define inflateSetDictionary z_inflateSetDictionary -# define inflateSync z_inflateSync -# define inflateSyncPoint z_inflateSyncPoint -# define inflateUndermine z_inflateUndermine -# define inflateValidate z_inflateValidate -# define inflate_copyright z_inflate_copyright -# define inflate_fast z_inflate_fast -# define inflate_table z_inflate_table -# ifndef Z_SOLO -# define uncompress z_uncompress -# define uncompress2 z_uncompress2 -# endif -# define zError z_zError -# ifndef Z_SOLO -# define zcalloc z_zcalloc -# define zcfree z_zcfree -# endif -# define zlibCompileFlags z_zlibCompileFlags -# define zlibVersion z_zlibVersion - -/* all zlib typedefs in zlib.h and zconf.h */ -# define Byte z_Byte -# define Bytef z_Bytef -# define alloc_func z_alloc_func -# define charf z_charf -# define free_func z_free_func -# ifndef Z_SOLO -# define gzFile z_gzFile -# endif -# define gz_header z_gz_header -# define gz_headerp z_gz_headerp -# define in_func z_in_func -# define intf z_intf -# define out_func z_out_func -# define uInt z_uInt -# define uIntf z_uIntf -# define uLong z_uLong -# define uLongf z_uLongf -# define voidp z_voidp -# define voidpc z_voidpc -# define voidpf z_voidpf - -/* all zlib structs in zlib.h and zconf.h */ -# define gz_header_s z_gz_header_s -# define internal_state z_internal_state - -#endif - -#if defined(__MSDOS__) && !defined(MSDOS) -# define MSDOS -#endif -#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) -# define OS2 -#endif -#if defined(_WINDOWS) && !defined(WINDOWS) -# define WINDOWS -#endif -#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) -# ifndef WIN32 -# define WIN32 -# endif -#endif -#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) -# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) -# ifndef SYS16BIT -# define SYS16BIT -# endif -# endif -#endif - -/* - * Compile with -DMAXSEG_64K if the alloc function cannot allocate more - * than 64k bytes at a time (needed on systems with 16-bit int). - */ -#ifdef SYS16BIT -# define MAXSEG_64K -#endif -#ifdef MSDOS -# define UNALIGNED_OK -#endif - -#ifdef __STDC_VERSION__ -# ifndef STDC -# define STDC -# endif -# if __STDC_VERSION__ >= 199901L -# ifndef STDC99 -# define STDC99 -# endif -# endif -#endif -#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) -# define STDC -#endif -#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) -# define STDC -#endif -#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) -# define STDC -#endif -#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) -# define STDC -#endif - -#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ -# define STDC -#endif - -#ifndef STDC -# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ -# define const /* note: need a more gentle solution here */ -# endif -#endif - -#if defined(ZLIB_CONST) && !defined(z_const) -# define z_const const -#else -# define z_const -#endif - -#ifdef Z_SOLO -# ifdef _WIN64 - typedef unsigned long long z_size_t; -# else - typedef unsigned long z_size_t; -# endif -#else -# define z_longlong long long -# if defined(NO_SIZE_T) - typedef unsigned NO_SIZE_T z_size_t; -# elif defined(STDC) -# include - typedef size_t z_size_t; -# else - typedef unsigned long z_size_t; -# endif -# undef z_longlong -#endif - -/* Maximum value for memLevel in deflateInit2 */ -#ifndef MAX_MEM_LEVEL -# ifdef MAXSEG_64K -# define MAX_MEM_LEVEL 8 -# else -# define MAX_MEM_LEVEL 9 -# endif -#endif - -/* Maximum value for windowBits in deflateInit2 and inflateInit2. - * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files - * created by gzip. (Files created by minigzip can still be extracted by - * gzip.) - */ -#ifndef MAX_WBITS -# define MAX_WBITS 15 /* 32K LZ77 window */ -#endif - -/* The memory requirements for deflate are (in bytes): - (1 << (windowBits+2)) + (1 << (memLevel+9)) - that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) - plus a few kilobytes for small objects. For example, if you want to reduce - the default memory requirements from 256K to 128K, compile with - make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" - Of course this will generally degrade compression (there's no free lunch). - - The memory requirements for inflate are (in bytes) 1 << windowBits - that is, 32K for windowBits=15 (default value) plus about 7 kilobytes - for small objects. -*/ - - /* Type declarations */ - -#ifndef OF /* function prototypes */ -# ifdef STDC -# define OF(args) args -# else -# define OF(args) () -# endif -#endif - -/* The following definitions for FAR are needed only for MSDOS mixed - * model programming (small or medium model with some far allocations). - * This was tested only with MSC; for other MSDOS compilers you may have - * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, - * just define FAR to be empty. - */ -#ifdef SYS16BIT -# if defined(M_I86SM) || defined(M_I86MM) - /* MSC small or medium model */ -# define SMALL_MEDIUM -# ifdef _MSC_VER -# define FAR _far -# else -# define FAR far -# endif -# endif -# if (defined(__SMALL__) || defined(__MEDIUM__)) - /* Turbo C small or medium model */ -# define SMALL_MEDIUM -# ifdef __BORLANDC__ -# define FAR _far -# else -# define FAR far -# endif -# endif -#endif - -#if defined(WINDOWS) || defined(WIN32) - /* If building or using zlib as a DLL, define ZLIB_DLL. - * This is not mandatory, but it offers a little performance increase. - */ -# ifdef ZLIB_DLL -# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) -# ifdef ZLIB_INTERNAL -# define ZEXTERN extern __declspec(dllexport) -# else -# define ZEXTERN extern __declspec(dllimport) -# endif -# endif -# endif /* ZLIB_DLL */ - /* If building or using zlib with the WINAPI/WINAPIV calling convention, - * define ZLIB_WINAPI. - * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. - */ -# ifdef ZLIB_WINAPI -# ifdef FAR -# undef FAR -# endif -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include - /* No need for _export, use ZLIB.DEF instead. */ - /* For complete Windows compatibility, use WINAPI, not __stdcall. */ -# define ZEXPORT WINAPI -# ifdef WIN32 -# define ZEXPORTVA WINAPIV -# else -# define ZEXPORTVA FAR CDECL -# endif -# endif -#endif - -#if defined (__BEOS__) -# ifdef ZLIB_DLL -# ifdef ZLIB_INTERNAL -# define ZEXPORT __declspec(dllexport) -# define ZEXPORTVA __declspec(dllexport) -# else -# define ZEXPORT __declspec(dllimport) -# define ZEXPORTVA __declspec(dllimport) -# endif -# endif -#endif - -#ifndef ZEXTERN -# define ZEXTERN extern -#endif -#ifndef ZEXPORT -# define ZEXPORT -#endif -#ifndef ZEXPORTVA -# define ZEXPORTVA -#endif - -#ifndef FAR -# define FAR -#endif - -#if !defined(__MACTYPES__) -typedef unsigned char Byte; /* 8 bits */ -#endif -typedef unsigned int uInt; /* 16 bits or more */ -typedef unsigned long uLong; /* 32 bits or more */ - -#ifdef SMALL_MEDIUM - /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ -# define Bytef Byte FAR -#else - typedef Byte FAR Bytef; -#endif -typedef char FAR charf; -typedef int FAR intf; -typedef uInt FAR uIntf; -typedef uLong FAR uLongf; - -#ifdef STDC - typedef void const *voidpc; - typedef void FAR *voidpf; - typedef void *voidp; -#else - typedef Byte const *voidpc; - typedef Byte FAR *voidpf; - typedef Byte *voidp; -#endif - -#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) -# include -# if (UINT_MAX == 0xffffffffUL) -# define Z_U4 unsigned -# elif (ULONG_MAX == 0xffffffffUL) -# define Z_U4 unsigned long -# elif (USHRT_MAX == 0xffffffffUL) -# define Z_U4 unsigned short -# endif -#endif - -#ifdef Z_U4 - typedef Z_U4 z_crc_t; -#else - typedef unsigned long z_crc_t; -#endif - -#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ -# define Z_HAVE_UNISTD_H -#endif - -#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ -# define Z_HAVE_STDARG_H -#endif - -#ifdef STDC -# ifndef Z_SOLO -# include /* for off_t */ -# endif -#endif - -#if defined(STDC) || defined(Z_HAVE_STDARG_H) -# ifndef Z_SOLO -# include /* for va_list */ -# endif -#endif - -#ifdef _WIN32 -# ifndef Z_SOLO -# include /* for wchar_t */ -# endif -#endif - -/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and - * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even - * though the former does not conform to the LFS document), but considering - * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as - * equivalently requesting no 64-bit operations - */ -#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 -# undef _LARGEFILE64_SOURCE -#endif - -#ifndef Z_HAVE_UNISTD_H -# ifdef __WATCOMC__ -# define Z_HAVE_UNISTD_H -# endif -#endif -#ifndef Z_HAVE_UNISTD_H -# if defined(_LARGEFILE64_SOURCE) && !defined(_WIN32) -# define Z_HAVE_UNISTD_H -# endif -#endif -#ifndef Z_SOLO -# if defined(Z_HAVE_UNISTD_H) -# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ -# ifdef VMS -# include /* for off_t */ -# endif -# ifndef z_off_t -# define z_off_t off_t -# endif -# endif -#endif - -#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 -# define Z_LFS64 -#endif - -#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) -# define Z_LARGE64 -#endif - -#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) -# define Z_WANT64 -#endif - -#if !defined(SEEK_SET) && !defined(Z_SOLO) -# define SEEK_SET 0 /* Seek from beginning of file. */ -# define SEEK_CUR 1 /* Seek from current position. */ -# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ -#endif - -#ifndef z_off_t -# define z_off_t long -#endif - -#if !defined(_WIN32) && defined(Z_LARGE64) -# define z_off64_t off64_t -#else -# if defined(_WIN32) && !defined(__GNUC__) -# define z_off64_t __int64 -# else -# define z_off64_t z_off_t -# endif -#endif - -/* MVS linker does not support external names larger than 8 bytes */ -#if defined(__MVS__) - #pragma map(deflateInit_,"DEIN") - #pragma map(deflateInit2_,"DEIN2") - #pragma map(deflateEnd,"DEEND") - #pragma map(deflateBound,"DEBND") - #pragma map(inflateInit_,"ININ") - #pragma map(inflateInit2_,"ININ2") - #pragma map(inflateEnd,"INEND") - #pragma map(inflateSync,"INSY") - #pragma map(inflateSetDictionary,"INSEDI") - #pragma map(compressBound,"CMBND") - #pragma map(inflate_table,"INTABL") - #pragma map(inflate_fast,"INFA") - #pragma map(inflate_copyright,"INCOPY") -#endif - -#endif /* ZCONF_H */ From f1dc3207978b5ce8bf386826eff720087d4d8927 Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 12 Nov 2024 09:19:41 -0800 Subject: [PATCH 29/55] refactoring the code --- cpp/lib/Authenticator.cpp | 154 +++++++++++++++++++++++-------- cpp/lib/Authenticator.hpp | 35 +++++-- include/snowflake/Exceptions.hpp | 39 ++++++++ lib/connection.c | 66 ------------- lib/connection.h | 6 -- 5 files changed, 183 insertions(+), 117 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index a8855f7680..8bf8a22973 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -46,7 +46,12 @@ { \ SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "Failed to created the header for the okta authentication", SF_SQLSTATE_GENERAL_ERROR); \ AUTH_THROW(err); \ -} +} + +#define RETRY_THROW(elapsedSeconds, retriedCount) \ +{ \ + throw Snowflake::Client::Exception::RenewTimeoutException(elapsedSeconds, retriedCount, false); \ +} // wrapper functions for C extern "C" { @@ -235,6 +240,7 @@ namespace Snowflake { namespace Client { + using namespace picojson; void IAuthenticator::renewDataMap(jsonObject_t& dataMap) { @@ -261,6 +267,70 @@ namespace Client } } + void IdentityAuthenticator::getIDPInfo() + { + // login info as a json post body + //Currently, the server is not enabled Okta authentication with C API. For testing purpose, I used the ODBC info. + jsonObject_t dataMap; + dataMap["ACCOUNT_NAME"] = value(m_connection->account); + dataMap["AUTHENTICATOR"] = value(m_connection->authenticator); + dataMap["LOGIN_NAME"] = value(m_connection->user); + dataMap["PORT"] = value(m_connection->port); + dataMap["PROTOCOL"] = value(m_connection->protocol); + dataMap["CLIENT_APP_ID"] = value("ODBC"); + dataMap["CLIENT_APP_VERSION"] = value("3.4.1"); + + jsonObject_t authnData, respData; + authnData["data"] = value(dataMap); + IDPPostCall(authnData, respData); + tokenURLStr = respData["tokenUrl"].get(); + ssoURLStr = respData["ssoUrl"].get(); + } + + void IdentityAuthenticator::IDPPostCall(jsonObject_t& authData, jsonObject_t& respData) + { + // connection URL + int64 elasped_time = 0; + int64 renew_timeout = 0; + int8* retried_count = &m_connection->retry_count; + + // add headers for account and authentication + SF_ERROR_STRUCT* err = &m_connection->error; + SF_HEADER* httpExtraHeaders = sf_header_create(); + std::string s_body = value(authData).serialize(); + + httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; + if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { + log_trace("sf", "AuthenticatorOKTA", + "getIdpInfo", + "Failed to create the header for the request to get the token URL and the SSO URL"); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + goto cleanup; + } + + cJSON* resp = NULL; + if (!request(m_connection, &resp, connectURL.c_str(), NULL, + 0, (char*) s_body.c_str(), httpExtraHeaders, + POST_REQUEST_TYPE, err, SF_BOOLEAN_FALSE, + renew_timeout, get_login_retry_count(m_connection), get_login_timeout(m_connection), &elasped_time, + retried_count, NULL, SF_BOOLEAN_TRUE)) + { + log_info("sf", "Connection", "getIdpInfo", + "Fail to get authenticator info, response body=%s\n", + snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data"))); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + goto cleanup; + } + + //deduct elasped time from the retry timeout + m_connection->retry_timeout -= elasped_time; + authData.clear(); + cJSONtoPicoJson(snowflake_cJSON_GetObjectItem(resp, "data"), respData); + cleanup: + sf_header_destroy(httpExtraHeaders); + } AuthenticatorJWT::AuthenticatorJWT(SF_CONNECT *conn) : m_jwt{Jwt::buildIJwt()} { @@ -389,7 +459,7 @@ namespace Client } AuthenticatorOKTA::AuthenticatorOKTA( - SF_CONNECT* connection) : m_connection(connection) + SF_CONNECT* connection) : IdentityAuthenticator(connection) { // nop } @@ -399,16 +469,6 @@ namespace Client // nop } - void AuthenticatorOKTA::getIdp(jsonObject_t& respData) - { - cJSON* resp_data = NULL; - if (!getIdpInfo(m_connection, &resp_data)) - { - AUTH_THROW(&m_connection->error); - } - cJSONtoPicoJson(resp_data, respData); - } - void AuthenticatorOKTA::getOneTimeToken(jsonObject_t& respData) { void* curl_desc; @@ -445,10 +505,6 @@ namespace Client SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); goto cleanup; } - - onetimeToken = snowflake_cJSON_HasObjectItem(resp, "sessionToken") ? - snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "sessionToken")) : - snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(resp, "cookieToken")); if (elapsed_time >= retry_timeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", @@ -457,9 +513,10 @@ namespace Client SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); goto cleanup; } - //update retry info. m_connection->retry_timeout -= elapsed_time; + respData.clear(); + cJSONtoPicoJson(resp, respData); cleanup: sf_header_destroy(header); @@ -476,7 +533,7 @@ namespace Client int64 retry_timeout = get_login_timeout(m_connection); int64 retry_max_count = get_login_retry_count(m_connection); int64 elapsed_time = 0; - int8* retried_count = &m_connection->retry_count; + int8 retried_count = m_connection->retry_count; void* curl_desc; CURL* curl; @@ -484,11 +541,11 @@ namespace Client curl = get_curl_from_desc(curl_desc); SF_ERROR_STRUCT* err = &m_connection->error; SFURL sso_url = SFURL::parse(ssoURLStr); - sso_url.addQueryParam("onetimetoken", onetimeToken); + sso_url.addQueryParam("onetimetoken", oneTimeToken); cJSON* resp = NULL; - if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, err, -1, retry_max_count, retry_timeout, &elapsed_time, retried_count)) + if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, err, -1, retry_max_count, retry_timeout, &elapsed_time, &retried_count)) { - if (elapsed_time >= retry_timeout || *retried_count >= retry_max_count) + if (elapsed_time >= retry_timeout || retried_count >= retry_max_count) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", @@ -497,37 +554,34 @@ namespace Client goto cleanup; } //Need to increase retried_count on the authentication level - (*retried_count)++; + retried_count++; CXX_LOG_TRACE("sf", "Connection", "Connect", "Retry on getting SAML response with one time token renewed for %d times " "with updated retryTimeout = %d", - *retried_count, retry_timeout - elapsed_time); + retried_count, retry_timeout - elapsed_time); isRetry = true; goto cleanup; } - //update retry info. - m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data")); - + m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data"));; cleanup: - m_connection->retry_timeout -= elapsed_time; free_curl_desc(curl_desc); snowflake_cJSON_Delete(resp); + if (isRetry) { + RETRY_THROW(elapsed_time, retried_count); + } + m_connection->retry_timeout -= elapsed_time; + m_connection->retry_count = retried_count; if (err->error_code != SF_STATUS_SUCCESS) { AUTH_THROW(err); } - return isRetry; } void AuthenticatorOKTA::authenticate() { // 1. get authenticator info - jsonObject_t dataMap; - getIdp(dataMap); + getIDPInfo(); SF_ERROR_STRUCT* err = &m_connection->error; - tokenURLStr = dataMap["tokenUrl"].get(); - ssoURLStr = dataMap["ssoUrl"].get(); - // 2. verify ssoUrl and tokenUrl contains same prefix if (!SFURL::urlHasSamePrefix(tokenURLStr, m_connection->authenticator)) { @@ -541,18 +595,46 @@ namespace Client // 3. get one time token from okta + jsonObject_t dataMap; dataMap.clear(); dataMap["username"] = picojson::value(m_connection->user); dataMap["password"] = picojson::value(m_connection->password); while (true) { getOneTimeToken(dataMap); - + oneTimeToken = dataMap["sessionToken"].get(); + if (oneTimeToken.empty()) { + oneTimeToken = dataMap["cookieToken"].get(); + } // 4. get SAML response - if (!getSAMLResponse()) - { + try { + getSAMLResponse(); break; } + catch (Exception::RenewTimeoutException& e) + { + int64 elapsedSeconds = e.getElapsedSeconds(); + int64 retryTimeout = get_retry_timeout(m_connection); + + if (elapsedSeconds >= retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retryTimeout, elapsedSeconds); + + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + } + + int8 retriedCount = e.getRetriedCount(); + CXX_LOG_TRACE("sf", "Connection", "Connect", + "Retry on getting SAML response with one time token renewed for %d times " + "with updated retryTimeout = %d", + retriedCount, retryTimeout - elapsedSeconds); + m_connection->retry_timeout -= elapsedSeconds; + m_connection->retry_count = retriedCount; + + } } // 5. Validate post_back_url matches Snowflake URL diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 7bbac80e2a..c229a613a1 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -59,6 +59,29 @@ namespace Client int64 m_renewTimeout; }; + class IdentityAuthenticator : public IAuthenticator + { + public: + IdentityAuthenticator(SF_CONNECT* conn) : m_connection(conn) + {}; + + ~IdentityAuthenticator() + {} + + + protected: + /* + * Get IdpInfo for OKTA and SAML 2.0 application + */ + void getIDPInfo(); + virtual void IDPPostCall(jsonObject_t& authData, jsonObject_t& resp); + jsonObject_t respdata; + SF_CONNECT* m_connection; + const std::string connectURL = "/session/authenticator-request"; + std::string tokenURLStr; + std::string ssoURLStr; + }; + /** * JWT Authenticator */ @@ -94,7 +117,7 @@ namespace Client }; - class AuthenticatorOKTA : public IAuthenticator + class AuthenticatorOKTA : public IdentityAuthenticator { public: AuthenticatorOKTA(SF_CONNECT* conn); @@ -106,20 +129,15 @@ namespace Client void updateDataMap(jsonObject_t& dataMap); protected: - //Step1 - virtual void getIdp(jsonObject_t& respData); //Step3 - virtual void getOneTimeToken(jsonObject_t& respData); + virtual void getOneTimeToken(jsonObject_t& dataMap); //Step4 virtual bool getSAMLResponse(); private: - SF_CONNECT* m_connection; std::string m_samlResponse; - std::string tokenURLStr; - std::string ssoURLStr; - std::string onetimeToken; + std::string oneTimeToken; /** * Extract post back url from samel response. Input is in HTML format. @@ -127,7 +145,6 @@ namespace Client std::string extractPostBackUrlFromSamlResponse(std::string html); SFURL getServerURLSync(); }; - } // namespace Client } // namespace Snowflake #endif //PROJECT_AUTHENTICATOR_HPP diff --git a/include/snowflake/Exceptions.hpp b/include/snowflake/Exceptions.hpp index 08b527897e..7a3eb1a37f 100644 --- a/include/snowflake/Exceptions.hpp +++ b/include/snowflake/Exceptions.hpp @@ -41,6 +41,45 @@ namespace Snowflake GeneralException(SF_ERROR_STRUCT* error) : SnowflakeException(error) {}; }; + class RenewTimeoutException : public std::exception + { + public: + RenewTimeoutException(int64 elapsedSeconds, + int8 retriedCount, + bool isCurlTimeoutNoBackoff) : + m_elapsedSeconds(elapsedSeconds), + m_retriedCount(retriedCount), + m_isCurlTimeoutNoBackoff(isCurlTimeoutNoBackoff) + {} + + int64 getElapsedSeconds() + { + return m_elapsedSeconds; + } + + int8 getRetriedCount() + { + return m_retriedCount; + } + + bool isCurlTimeoutNoBackoff() + { + return m_isCurlTimeoutNoBackoff; + } + + virtual const char* what() const noexcept + { + return "internal renew timeout exception"; + } + + private: + int64 m_elapsedSeconds; + int8 m_retriedCount; + // The flag indicate if the renew exception is thrown for renew the request + // within curl timeout and no backoff made + bool m_isCurlTimeoutNoBackoff; + }; + struct AuthException : public std::exception { AuthException(SF_ERROR_STRUCT* error) : message_(error->msg) {} diff --git a/lib/connection.c b/lib/connection.c index 1d69357d73..0b1c206331 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1361,70 +1361,4 @@ sf_bool is_saml_response(char* response) { char* doctype = ""; return strncmp(response, doctype, strlen(doctype)) == 0; -} - - -sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json) -{ - sf_bool ret = SF_BOOLEAN_FALSE; - cJSON* dataMap = snowflake_cJSON_CreateObject(); - // connection URL - const char* connectURL = "/session/authenticator-request"; - SF_ERROR_STRUCT* err = &sf->error; - - // login info as a json post body - //Currently, the server is not enabled Okta authentication with C API. For testing purpose, I used the ODBC info. - snowflake_cJSON_AddStringToObject(dataMap, "CLIENT_APP_ID", "ODBC"); - snowflake_cJSON_AddStringToObject(dataMap, "CLIENT_APP_VERSION", "3.4.1"); - snowflake_cJSON_AddStringToObject(dataMap, "ACCOUNT_NAME", sf->account); - snowflake_cJSON_AddStringToObject(dataMap, "AUTHENTICATOR", sf->authenticator); - snowflake_cJSON_AddStringToObject(dataMap, "LOGIN_NAME", sf->user); - snowflake_cJSON_AddStringToObject(dataMap, "PORT", sf->port); - snowflake_cJSON_AddStringToObject(dataMap, "PROTOCOL", sf->protocol); - - cJSON* authnData = snowflake_cJSON_CreateObject(); - cJSON* resp = NULL; - snowflake_cJSON_AddItemReferenceToObject(authnData, "data", dataMap); - - // add headers for account and authentication - SF_HEADER* httpExtraHeaders = sf_header_create(); - httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; - if (!create_header(sf, httpExtraHeaders, &sf->error)) { - log_trace("sf", "AuthenticatorOKTA", - "getIdpInfo", - "Failed to create the header for the request to get the token URL and the SSO URL"); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - ret = SF_BOOLEAN_FALSE; - goto cleanup; - } - - int64 renew_timeout = 0; - int8* retried_count = &sf->retry_count; - int64 elasped_time = 0; - char* s_body = snowflake_cJSON_Print(authnData); - time_t start = time(NULL); - - if (!request(sf, &resp, connectURL, NULL, - 0, s_body, httpExtraHeaders, - POST_REQUEST_TYPE, &sf->error, SF_BOOLEAN_FALSE, - renew_timeout, get_login_retry_count(sf), get_login_timeout(sf), &elasped_time, - retried_count, NULL, SF_BOOLEAN_TRUE)) - { - log_info("sf", "Connection", "getIdpInfo", - "Fail to get authenticator info, response body=%s\n", - snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data"))); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); - ret = SF_BOOLEAN_FALSE; - goto cleanup; - } - //deduct elasped time from the retry timeout - sf->retry_timeout -= elasped_time; - *json = snowflake_cJSON_GetObjectItem(resp, "data"); - ret = SF_BOOLEAN_TRUE; - -cleanup: - sf_header_destroy(httpExtraHeaders); - snowflake_cJSON_Delete(authnData); - - return ret; } \ No newline at end of file diff --git a/lib/connection.h b/lib/connection.h index 076e4ed601..d19f380d27 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -652,12 +652,6 @@ sf_bool is_one_time_token_request(cJSON* resp); * a function to check that this request is whether the response includes the SAML response. */ sf_bool is_saml_response(char* response); - -/* -* Get IdpInfo for OKTA and SAML 2.0 application -*/ -sf_bool getIdpInfo(SF_CONNECT* sf, cJSON** json); - #ifdef __cplusplus } #endif From c1a128d3c16eb784943cd533a0c437d43ae5f996 Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 12 Nov 2024 10:19:24 -0800 Subject: [PATCH 30/55] fix --- CMakeLists.txt | 4 ++-- cpp/lib/Authenticator.cpp | 29 +++++++++++++--------------- cpp/lib/Authenticator.hpp | 10 +++++----- cpp/lib/SnowflakeUtil.cpp | 33 ++++++++++++++++++++++++++++++++ cpp/lib/util.cpp | 2 +- lib/{util.h => snowflake_util.h} | 0 6 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 cpp/lib/SnowflakeUtil.cpp rename lib/{util.h => snowflake_util.h} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c50dd9ae9c..0fc0e0c1b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ set(SOURCE_FILES lib/basic_types.c lib/error.h lib/error.c - lib/util.h + lib/snowflake_util.h lib/client_int.h lib/chunk_downloader.h lib/chunk_downloader.c @@ -238,7 +238,7 @@ set(SOURCE_FILES_CPP_WRAPPER cpp/lib/ResultSetJson.hpp cpp/lib/Authenticator.cpp cpp/lib/Authenticator.hpp - cpp/lib/util.cpp + cpp/lib/SnowflakeUtil.cpp cpp/jwt/jwtWrapper.cpp cpp/util/SnowflakeCommon.cpp cpp/util/SFURL.cpp diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 8bf8a22973..0e84023bc7 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -42,15 +42,15 @@ throw Snowflake::Client::Exception::AuthException(msg); \ } -#define JWT_THROW(err, msg) \ -{ \ +#define JWT_THROW(err, msg)\ +{ \ SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "Failed to created the header for the okta authentication", SF_SQLSTATE_GENERAL_ERROR); \ - AUTH_THROW(err); \ + AUTH_THROW(err); \ } -#define RETRY_THROW(elapsedSeconds, retriedCount) \ -{ \ - throw Snowflake::Client::Exception::RenewTimeoutException(elapsedSeconds, retriedCount, false); \ +#define RETRY_THROW(elapsedSeconds, retriedCount)\ +{ \ + throw Snowflake::Client::Exception::RenewTimeoutException(elapsedSeconds, retriedCount, false);\ } // wrapper functions for C @@ -66,14 +66,12 @@ extern "C" { if (strcasecmp(authenticator, SF_AUTHENTICATOR_JWT) == 0) { return AUTH_JWT; - } + } if (strcasecmp(authenticator, SF_AUTHENTICATOR_OAUTH) == 0) { return AUTH_OAUTH; } - - return AUTH_OKTA; - + return AUTH_OKTA; } SF_STATUS STDCALL auth_initialize(SF_CONNECT * conn) @@ -267,7 +265,7 @@ namespace Client } } - void IdentityAuthenticator::getIDPInfo() + void IDPAuthenticator::getIDPInfo() { // login info as a json post body //Currently, the server is not enabled Okta authentication with C API. For testing purpose, I used the ODBC info. @@ -287,7 +285,7 @@ namespace Client ssoURLStr = respData["ssoUrl"].get(); } - void IdentityAuthenticator::IDPPostCall(jsonObject_t& authData, jsonObject_t& respData) + void IDPAuthenticator::IDPPostCall(jsonObject_t& authData, jsonObject_t& respData) { // connection URL int64 elasped_time = 0; @@ -298,6 +296,7 @@ namespace Client SF_ERROR_STRUCT* err = &m_connection->error; SF_HEADER* httpExtraHeaders = sf_header_create(); std::string s_body = value(authData).serialize(); + cJSON* resp = NULL; httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { @@ -309,7 +308,6 @@ namespace Client goto cleanup; } - cJSON* resp = NULL; if (!request(m_connection, &resp, connectURL.c_str(), NULL, 0, (char*) s_body.c_str(), httpExtraHeaders, POST_REQUEST_TYPE, err, SF_BOOLEAN_FALSE, @@ -459,7 +457,7 @@ namespace Client } AuthenticatorOKTA::AuthenticatorOKTA( - SF_CONNECT* connection) : IdentityAuthenticator(connection) + SF_CONNECT* connection) : IDPAuthenticator(connection) { // nop } @@ -540,9 +538,9 @@ namespace Client curl_desc = get_curl_desc_from_pool(ssoURLStr.c_str(), m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); SF_ERROR_STRUCT* err = &m_connection->error; + cJSON* resp = NULL; SFURL sso_url = SFURL::parse(ssoURLStr); sso_url.addQueryParam("onetimetoken", oneTimeToken); - cJSON* resp = NULL; if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, err, -1, retry_max_count, retry_timeout, &elapsed_time, &retried_count)) { if (elapsed_time >= retry_timeout || retried_count >= retry_max_count) @@ -593,7 +591,6 @@ namespace Client AUTH_THROW(err->msg); } - // 3. get one time token from okta jsonObject_t dataMap; dataMap.clear(); diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index c229a613a1..4dcd34b1e6 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -17,7 +17,7 @@ #include "authenticator.h" #include "picojson.h" #include "snowflake/SFURL.hpp" -#include "../../lib/util.h" +#include "../../lib/snowflake_util.h" @@ -59,13 +59,13 @@ namespace Client int64 m_renewTimeout; }; - class IdentityAuthenticator : public IAuthenticator + class IDPAuthenticator : public IAuthenticator { public: - IdentityAuthenticator(SF_CONNECT* conn) : m_connection(conn) + IDPAuthenticator(SF_CONNECT* conn) : m_connection(conn) {}; - ~IdentityAuthenticator() + ~IDPAuthenticator() {} @@ -117,7 +117,7 @@ namespace Client }; - class AuthenticatorOKTA : public IdentityAuthenticator + class AuthenticatorOKTA : public IDPAuthenticator { public: AuthenticatorOKTA(SF_CONNECT* conn); diff --git a/cpp/lib/SnowflakeUtil.cpp b/cpp/lib/SnowflakeUtil.cpp new file mode 100644 index 0000000000..1a190dd9e1 --- /dev/null +++ b/cpp/lib/SnowflakeUtil.cpp @@ -0,0 +1,33 @@ +#include "../../lib/snowflake_util.h" + +namespace Snowflake +{ +namespace Client +{ + // wrapper functions for C + extern "C" { + void cJSONtoPicoJson(cJSON* cjson, jsonObject_t& picojson) + { + jsonValue_t v; + const char* dataStr = snowflake_cJSON_Print(cjson); + picojson::parse(v, dataStr); + picojson = v.get(); + } + + void picoJsonTocJson(jsonObject_t& picojson, cJSON** cjson) + { + std::string body_str = picojson::value(picojson).serialize(); + cJSON* new_body = snowflake_cJSON_Parse(body_str.c_str()); + snowflake_cJSON_DeleteItemFromObject(*cjson, "data"); + snowflake_cJSON_AddItemToObject(*cjson, "data", new_body); + } + + void strToPicoJson(jsonObject_t& picojson, std::string str) + { + jsonValue_t v; + picojson::parse(v, str); + picojson = v.get(); + } + } +} +} \ No newline at end of file diff --git a/cpp/lib/util.cpp b/cpp/lib/util.cpp index 317df3cce6..1a190dd9e1 100644 --- a/cpp/lib/util.cpp +++ b/cpp/lib/util.cpp @@ -1,4 +1,4 @@ -#include "../../lib/util.h" +#include "../../lib/snowflake_util.h" namespace Snowflake { diff --git a/lib/util.h b/lib/snowflake_util.h similarity index 100% rename from lib/util.h rename to lib/snowflake_util.h From 6ebb28352b4320e720bca00c2cff1e0b1482181b Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 12 Nov 2024 10:46:55 -0800 Subject: [PATCH 31/55] fix --- CMakeLists.txt | 4 ++-- cpp/lib/Authenticator.cpp | 1 + cpp/lib/Authenticator.hpp | 5 ----- cpp/lib/util.cpp | 33 --------------------------------- 4 files changed, 3 insertions(+), 40 deletions(-) delete mode 100644 cpp/lib/util.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fc0e0c1b0..a50a847cbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,6 +216,7 @@ set(SOURCE_FILES_CPP_WRAPPER include/snowflake/SFURL.hpp include/snowflake/CurlDesc.hpp include/snowflake/CurlDescPool.hpp + cpp/lib/SnowflakeUtil.cpp cpp/lib/Exceptions.cpp cpp/lib/Connection.cpp cpp/lib/Statement.cpp @@ -237,8 +238,7 @@ set(SOURCE_FILES_CPP_WRAPPER cpp/lib/ResultSetJson.cpp cpp/lib/ResultSetJson.hpp cpp/lib/Authenticator.cpp - cpp/lib/Authenticator.hpp - cpp/lib/SnowflakeUtil.cpp + cpp/lib/Authenticator.hpp cpp/jwt/jwtWrapper.cpp cpp/util/SnowflakeCommon.cpp cpp/util/SFURL.cpp diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 0e84023bc7..2917ddda86 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -71,6 +71,7 @@ extern "C" { { return AUTH_OAUTH; } + return AUTH_OKTA; } diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 4dcd34b1e6..ca2fa58b16 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -19,8 +19,6 @@ #include "snowflake/SFURL.hpp" #include "../../lib/snowflake_util.h" - - namespace Snowflake { namespace Client @@ -68,7 +66,6 @@ namespace Client ~IDPAuthenticator() {} - protected: /* * Get IdpInfo for OKTA and SAML 2.0 application @@ -96,8 +93,6 @@ namespace Client void updateDataMap(jsonObject_t &dataMap); - - private: void loadPrivateKey(const std::string &privateKeyFile, const std::string &passcode); diff --git a/cpp/lib/util.cpp b/cpp/lib/util.cpp deleted file mode 100644 index 1a190dd9e1..0000000000 --- a/cpp/lib/util.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "../../lib/snowflake_util.h" - -namespace Snowflake -{ -namespace Client -{ - // wrapper functions for C - extern "C" { - void cJSONtoPicoJson(cJSON* cjson, jsonObject_t& picojson) - { - jsonValue_t v; - const char* dataStr = snowflake_cJSON_Print(cjson); - picojson::parse(v, dataStr); - picojson = v.get(); - } - - void picoJsonTocJson(jsonObject_t& picojson, cJSON** cjson) - { - std::string body_str = picojson::value(picojson).serialize(); - cJSON* new_body = snowflake_cJSON_Parse(body_str.c_str()); - snowflake_cJSON_DeleteItemFromObject(*cjson, "data"); - snowflake_cJSON_AddItemToObject(*cjson, "data", new_body); - } - - void strToPicoJson(jsonObject_t& picojson, std::string str) - { - jsonValue_t v; - picojson::parse(v, str); - picojson = v.get(); - } - } -} -} \ No newline at end of file From f26bca4cc4f7566b9de4df81e0770d3bec3baa08 Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 12 Nov 2024 11:19:46 -0800 Subject: [PATCH 32/55] fix --- cpp/lib/Authenticator.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index ca2fa58b16..c53622fba1 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -127,7 +127,7 @@ namespace Client //Step3 virtual void getOneTimeToken(jsonObject_t& dataMap); //Step4 - virtual bool getSAMLResponse(); + virtual void getSAMLResponse(); private: From b1142e9378db1e0ae260a0db09f299da595977bc Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 12 Nov 2024 11:20:10 -0800 Subject: [PATCH 33/55] fix --- cpp/lib/Authenticator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 2917ddda86..439a877d36 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -526,7 +526,7 @@ namespace Client } } - bool AuthenticatorOKTA::getSAMLResponse() + void AuthenticatorOKTA::getSAMLResponse() { bool isRetry = false; int64 retry_timeout = get_login_timeout(m_connection); From 9b11f2d54d1c882c32f2994b860ac32ac29d8a6f Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 12 Nov 2024 12:45:54 -0800 Subject: [PATCH 34/55] fix --- cpp/lib/Authenticator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 439a877d36..1d273a01a8 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -329,6 +329,7 @@ namespace Client cJSONtoPicoJson(snowflake_cJSON_GetObjectItem(resp, "data"), respData); cleanup: sf_header_destroy(httpExtraHeaders); + snowflake_cJSON_Delete(resp); } AuthenticatorJWT::AuthenticatorJWT(SF_CONNECT *conn) : m_jwt{Jwt::buildIJwt()} From b5adccc9293b9bde2bf8d0563a67f51804b174a5 Mon Sep 17 00:00:00 2001 From: John Yun Date: Wed, 13 Nov 2024 16:48:51 -0800 Subject: [PATCH 35/55] refactor code --- cpp/lib/Authenticator.cpp | 274 ++++++++++++++++++++------------------ cpp/lib/Authenticator.hpp | 21 ++- lib/connection.h | 10 ++ lib/http_perform.c | 53 ++++++-- 4 files changed, 212 insertions(+), 146 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 1d273a01a8..1aefb5fb6e 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -21,6 +21,7 @@ #include "curl_desc_pool.h" #include "snowflake/Exceptions.hpp" #include "cJSON.h" +#include "memory.h" #include "../cpp/jwt/Util.hpp" #include @@ -279,25 +280,50 @@ namespace Client dataMap["CLIENT_APP_ID"] = value("ODBC"); dataMap["CLIENT_APP_VERSION"] = value("3.4.1"); + int64 renew_timeout = 0; + int8* retried_count = &m_connection->retry_count; + + SFURL connectURL = getServerURLSync().path("/session/authenticator-request"); jsonObject_t authnData, respData; authnData["data"] = value(dataMap); - IDPPostCall(authnData, respData); - tokenURLStr = respData["tokenUrl"].get(); - ssoURLStr = respData["ssoUrl"].get(); + + int64 maxRetryCount = get_login_retry_count(m_connection); + int64 retryTimeout = get_login_timeout(m_connection); + int64 renewTimeout = auth_get_renew_timeout(m_connection); + bool injectCURLTimeout = false; + bool isNewRetry = true; + + post_curl_call(connectURL, authnData, respData, 0, retryTimeout, 0, maxRetryCount, injectCURLTimeout, renewTimeout, retried_count, isNewRetry); + jsonObject_t& data = respData["data"].get(); + tokenURLStr = data["tokenUrl"].get(); + ssoURLStr = data["ssoUrl"].get(); } - void IDPAuthenticator::IDPPostCall(jsonObject_t& authData, jsonObject_t& respData) + SFURL IDPAuthenticator::getServerURLSync() { - // connection URL - int64 elasped_time = 0; - int64 renew_timeout = 0; - int8* retried_count = &m_connection->retry_count; + SFURL url = SFURL().scheme(m_connection->protocol) + .host(m_connection->host) + .port(m_connection->port); - // add headers for account and authentication + return url; + } + + void IDPAuthenticator::post_curl_call(SFURL& url, const jsonObject_t& obj, jsonObject_t& resp, int64 curlTimeout, + int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, + int8 *retriedCount, bool isNewRetry) + { + std::string destination = url.toString(); + void* curl_desc; + CURL* curl; + curl_desc = get_curl_desc_from_pool(destination.c_str(), m_connection->proxy, m_connection->no_proxy); + curl = get_curl_from_desc(curl_desc); SF_ERROR_STRUCT* err = &m_connection->error; + int64 elapsedTime = 0; + + // add headers for account and authentication SF_HEADER* httpExtraHeaders = sf_header_create(); - std::string s_body = value(authData).serialize(); - cJSON* resp = NULL; + std::string s_body = value(obj).serialize(); + cJSON* resp_data = NULL; httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { @@ -309,28 +335,37 @@ namespace Client goto cleanup; } - if (!request(m_connection, &resp, connectURL.c_str(), NULL, - 0, (char*) s_body.c_str(), httpExtraHeaders, - POST_REQUEST_TYPE, err, SF_BOOLEAN_FALSE, - renew_timeout, get_login_retry_count(m_connection), get_login_timeout(m_connection), &elasped_time, - retried_count, NULL, SF_BOOLEAN_TRUE)) + if (!curl_post_call(m_connection, curl, (char*) destination.c_str(), httpExtraHeaders, (char*)s_body.c_str(), + &resp_data, err, renewTimeout, maxRetryCount, retryTimeout, &elapsedTime, + retriedCount, NULL, SF_BOOLEAN_TRUE)) { log_info("sf", "Connection", "getIdpInfo", "Fail to get authenticator info, response body=%s\n", - snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data"))); + snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp_data, "data"))); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); AUTH_THROW(err); goto cleanup; } + + if (elapsedTime >= retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retryTimeout, elapsedTime); + + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + } + //deduct elasped time from the retry timeout - m_connection->retry_timeout -= elasped_time; - authData.clear(); - cJSONtoPicoJson(snowflake_cJSON_GetObjectItem(resp, "data"), respData); + m_connection->retry_timeout -= elapsedTime; + cJSONtoPicoJson(resp_data, resp); cleanup: sf_header_destroy(httpExtraHeaders); - snowflake_cJSON_Delete(resp); + snowflake_cJSON_Delete(resp_data); } + AuthenticatorJWT::AuthenticatorJWT(SF_CONNECT *conn) : m_jwt{Jwt::buildIJwt()} { @@ -469,111 +504,115 @@ namespace Client // nop } - void AuthenticatorOKTA::getOneTimeToken(jsonObject_t& respData) + void AuthenticatorOKTA::get_curl_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, + int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, + int8* retriedCount, bool isNewRetry, bool parseJSON, std::string& rawData) { + bool isRetry = false; + std::string destination = url.toString(); void* curl_desc; CURL* curl; - curl_desc = get_curl_desc_from_pool(tokenURLStr.c_str(), m_connection->proxy, m_connection->no_proxy); + curl_desc = get_curl_desc_from_pool(destination.c_str(), m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); SF_ERROR_STRUCT* err = &m_connection->error; + int64 elapsedTime = 0; + char* raw_data = NULL; - // When renewTimeout < 0, throws Exception - // for each retry attempt so we can renew the one time token - int64 retry_timeout = get_login_timeout(m_connection); - int64 retry_max_count = get_login_retry_count(m_connection); - - int64 elapsed_time = 0; - int8* retried_count = &m_connection->retry_count; + // add headers for account and authentication + SF_HEADER* httpExtraHeaders = sf_header_create(); - // headers for step 4 - // add header for accept format - SF_HEADER* header = sf_header_create(); - header->use_application_json_accept_type = SF_BOOLEAN_TRUE; - if (!create_header(m_connection, header, &m_connection->error)) { - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "Failed to created the header for the okta authentication", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); + httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; + if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { + log_trace("sf", "AuthenticatorOKTA", + "get_curl_call", + "Failed to create the header for the request to get onetime token"); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + goto cleanup; } - cJSON* resp = NULL; - std::string s_body = picojson::value(respData).serialize(); - if (!curl_post_call(m_connection, curl, (char*)tokenURLStr.c_str(), header, (char*)s_body.c_str(), &resp, - err, 0, retry_max_count, retry_timeout, - &elapsed_time, retried_count, 0, false)) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, response body=%s", - snowflake_cJSON_Print(resp)); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); - goto cleanup; - } - if (elapsed_time >= retry_timeout) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - *retried_count, elapsed_time); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + + if (!http_perform_internal(curl, GET_REQUEST_TYPE, (char*)destination.c_str(), httpExtraHeaders, NULL, NULL, NULL, + get_retry_timeout(m_connection), SF_BOOLEAN_FALSE, err, + m_connection->insecure_mode, m_connection->ocsp_fail_open, + m_connection->retry_on_curle_couldnt_connect_count, + renewTimeout, maxRetryCount, &elapsedTime, retriedCount, NULL, SF_BOOLEAN_FALSE, + m_connection->proxy, m_connection->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE, parseJSON, &raw_data)) { + // Error is set in the perform function + isRetry = true; goto cleanup; - } - //update retry info. - m_connection->retry_timeout -= elapsed_time; - respData.clear(); - cJSONtoPicoJson(resp, respData); + } + + if (elapsedTime >= retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "get_curl_call", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + retryTimeout, elapsedTime); + + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + goto cleanup; + } + rawData = raw_data; cleanup: - sf_header_destroy(header); - snowflake_cJSON_Delete(resp); + sf_header_destroy(httpExtraHeaders); free_curl_desc(curl_desc); + SF_FREE(raw_data); + if (isRetry) { + RETRY_THROW(elapsedTime, *retriedCount); + } + m_connection->retry_timeout -= elapsedTime; + m_connection->retry_count = *retriedCount; if (err->error_code != SF_STATUS_SUCCESS) { AUTH_THROW(err); } } + void AuthenticatorOKTA::getOneTimeToken() + { + // When renewTimeout < 0, throws Exception + // for each retry attempt so we can renew the one time token + int64 retry_timeout = get_login_timeout(m_connection); + int8 retry_max_count = get_login_retry_count(m_connection); + int8* retried_count = &m_connection->retry_count; + int64 renewTimeout = auth_get_renew_timeout(m_connection); + + + SFURL tokenURL = SFURL::parse(tokenURLStr); + + jsonObject_t dataMap,respData; + dataMap["username"] = picojson::value(m_connection->user); + dataMap["password"] = picojson::value(m_connection->password); + + try { + post_curl_call(tokenURL, dataMap, respData, 0, retry_timeout, 8, retry_max_count, false, renewTimeout, retried_count, false); + } + catch (...) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getOneTimeToken", + "Fail to get one time token response, response body=%s", + picojson::value(respData).serialize()); + SET_SNOWFLAKE_ERROR(&m_connection->error, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW("Failed to get the one time token from Okta authentication.") + } + + oneTimeToken = respData["sessionToken"].get(); + if (oneTimeToken.empty()) { + oneTimeToken = respData["cookieToken"].get(); + } + } + void AuthenticatorOKTA::getSAMLResponse() { bool isRetry = false; int64 retry_timeout = get_login_timeout(m_connection); int64 retry_max_count = get_login_retry_count(m_connection); - int64 elapsed_time = 0; - int8 retried_count = m_connection->retry_count; + int64 elapsedTime = 0; + int8* retried_count = &m_connection->retry_count; + int64 renewTimeout = auth_get_renew_timeout(m_connection); - void* curl_desc; - CURL* curl; - curl_desc = get_curl_desc_from_pool(ssoURLStr.c_str(), m_connection->proxy, m_connection->no_proxy); - curl = get_curl_from_desc(curl_desc); - SF_ERROR_STRUCT* err = &m_connection->error; - cJSON* resp = NULL; + jsonObject_t resp; SFURL sso_url = SFURL::parse(ssoURLStr); sso_url.addQueryParam("onetimetoken", oneTimeToken); - if (!curl_get_call(m_connection, curl, (char*)sso_url.toString().c_str(), NULL, &resp, err, -1, retry_max_count, retry_timeout, &elapsed_time, &retried_count)) - { - if (elapsed_time >= retry_timeout || retried_count >= retry_max_count) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retried_count, elapsed_time); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - goto cleanup; - } - //Need to increase retried_count on the authentication level - retried_count++; - CXX_LOG_TRACE("sf", "Connection", "Connect", - "Retry on getting SAML response with one time token renewed for %d times " - "with updated retryTimeout = %d", - retried_count, retry_timeout - elapsed_time); - isRetry = true; - goto cleanup; - } - m_samlResponse = snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp, "data"));; - cleanup: - free_curl_desc(curl_desc); - snowflake_cJSON_Delete(resp); - if (isRetry) { - RETRY_THROW(elapsed_time, retried_count); - } - m_connection->retry_timeout -= elapsed_time; - m_connection->retry_count = retried_count; - if (err->error_code != SF_STATUS_SUCCESS) { - AUTH_THROW(err); - } + get_curl_call(sso_url, resp, 0, retry_timeout, 8, 0, retry_max_count, renewTimeout, retried_count, false, false, m_samlResponse); } void AuthenticatorOKTA::authenticate() @@ -585,26 +624,18 @@ namespace Client // 2. verify ssoUrl and tokenUrl contains same prefix if (!SFURL::urlHasSamePrefix(tokenURLStr, m_connection->authenticator)) { - CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", "The specified authenticator is not supported, " "authenticator=%s, token url=%s, sso url=%s", - m_connection->authenticator, tokenURLStr.c_str(), ssoURLStr.c_str()); + m_connection->authenticator, tokenURLStr, ssoURLStr); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err->msg); + AUTH_THROW(err); } // 3. get one time token from okta - jsonObject_t dataMap; - dataMap.clear(); - dataMap["username"] = picojson::value(m_connection->user); - dataMap["password"] = picojson::value(m_connection->password); while (true) { - getOneTimeToken(dataMap); - oneTimeToken = dataMap["sessionToken"].get(); - if (oneTimeToken.empty()) { - oneTimeToken = dataMap["cookieToken"].get(); - } + getOneTimeToken(); // 4. get SAML response try { getSAMLResponse(); @@ -617,7 +648,7 @@ namespace Client if (elapsedSeconds >= retryTimeout) { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponse", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", retryTimeout, elapsedSeconds); @@ -642,12 +673,12 @@ namespace Client if ((!m_connection->disable_saml_url_check) && (!SFURL::urlHasSamePrefix(post_back_url, server_url))) { - CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", "The specified authenticator and destination URL in " "Saml Assertion did not " "match, expected=%s, post back=%s", - server_url.c_str(), - post_back_url.c_str()); + server_url, + post_back_url); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "SFSamlResponseVerificationFailed", SF_SQLSTATE_GENERAL_ERROR); } } @@ -679,14 +710,5 @@ namespace Client "Post back url after unescape: %s", unescaped_url); return std::string(unescaped_url); } - - SFURL AuthenticatorOKTA::getServerURLSync() - { - SFURL url = SFURL().scheme(m_connection->protocol) - .host(m_connection->host) - .port(m_connection->port); - - return url; - } } // namespace Client } // namespace Snowflake diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index c53622fba1..9348ed5672 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -71,10 +71,15 @@ namespace Client * Get IdpInfo for OKTA and SAML 2.0 application */ void getIDPInfo(); - virtual void IDPPostCall(jsonObject_t& authData, jsonObject_t& resp); + virtual void post_curl_call(SFURL& url, const jsonObject_t& obj, jsonObject_t& resp, int64 curlTimeout, + int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, + int8 *retriedCount, bool isNewRetry); + virtual void get_curl_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, + int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, + int8 *retriedCount, bool isNewRetry, bool parseJSON, std::string& raw_data) = 0; + SFURL getServerURLSync(); jsonObject_t respdata; SF_CONNECT* m_connection; - const std::string connectURL = "/session/authenticator-request"; std::string tokenURLStr; std::string ssoURLStr; }; @@ -123,11 +128,14 @@ namespace Client void updateDataMap(jsonObject_t& dataMap); + protected: - //Step3 - virtual void getOneTimeToken(jsonObject_t& dataMap); - //Step4 - virtual void getSAMLResponse(); + + void get_curl_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, + int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, + int8* retriedCount, bool isNewRetry, bool parseJSON, std::string& rawData); + void getOneTimeToken(); + void getSAMLResponse(); private: @@ -138,7 +146,6 @@ namespace Client * Extract post back url from samel response. Input is in HTML format. */ std::string extractPostBackUrlFromSamlResponse(std::string html); - SFURL getServerURLSync(); }; } // namespace Client } // namespace Snowflake diff --git a/lib/connection.h b/lib/connection.h index d19f380d27..aa69b4fafa 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -474,6 +474,16 @@ sf_bool STDCALL http_perform(CURL *curl, SF_REQUEST_TYPE request_type, char *url sf_bool include_retry_reason, sf_bool is_login_request); +sf_bool STDCALL http_perform_internal(CURL* curl, SF_REQUEST_TYPE request_type, char* url, SF_HEADER* header, + char* body, cJSON** json, NON_JSON_RESP* non_json_resp, int64 network_timeout, sf_bool chunk_downloader, + SF_ERROR_STRUCT* error, sf_bool insecure_mode, sf_bool fail_open, + int8 retry_on_curle_couldnt_connect_count, + int64 renew_timeout, int8 retry_max_count, + int64* elapsed_time, int8* retried_count, + sf_bool* is_renew, sf_bool renew_injection, + const char* proxy, const char* no_proxy, + sf_bool include_retry_reason, + sf_bool is_login_request, sf_bool parse_json, char** raw_data); /** * Returns true if HTTP code is retryable, false otherwise. * diff --git a/lib/http_perform.c b/lib/http_perform.c index b00511e2ca..d1738a8120 100644 --- a/lib/http_perform.c +++ b/lib/http_perform.c @@ -138,7 +138,36 @@ void my_sleep_ms(uint32 sleepMs) #endif } -sf_bool STDCALL http_perform(CURL *curl, +sf_bool STDCALL http_perform(CURL* curl, + SF_REQUEST_TYPE request_type, + char* url, + SF_HEADER* header, + char* body, + cJSON** json, + NON_JSON_RESP* non_json_resp, + int64 network_timeout, + sf_bool chunk_downloader, + SF_ERROR_STRUCT* error, + sf_bool insecure_mode, + sf_bool fail_open, + int8 retry_on_curle_couldnt_connect_count, + int64 renew_timeout, + int8 retry_max_count, + int64* elapsed_time, + int8* retried_count, + sf_bool* is_renew, + sf_bool renew_injection, + const char* proxy, + const char* no_proxy, + sf_bool include_retry_reason, + sf_bool is_new_strategy_request) +{ + return http_perform_internal(curl, request_type, url, header, body, json, non_json_resp, network_timeout, + chunk_downloader, error, insecure_mode, fail_open, retry_on_curle_couldnt_connect_count, renew_timeout, retry_max_count, + elapsed_time, retried_count, is_renew, renew_injection, proxy, no_proxy, include_retry_reason, is_new_strategy_request, TRUE, NULL); +} + +sf_bool STDCALL http_perform_internal(CURL *curl, SF_REQUEST_TYPE request_type, char *url, SF_HEADER *header, @@ -160,7 +189,9 @@ sf_bool STDCALL http_perform(CURL *curl, const char *proxy, const char *no_proxy, sf_bool include_retry_reason, - sf_bool is_new_strategy_request) { + sf_bool is_new_strategy_request, + sf_bool parse_json, + char** raw_data) { CURLcode res; sf_bool ret = SF_BOOLEAN_FALSE; sf_bool retry = SF_BOOLEAN_FALSE; @@ -477,6 +508,11 @@ sf_bool STDCALL http_perform(CURL *curl, } while (retry); + if (!parse_json) { + *raw_data = (char*)SF_CALLOC(1, buffer.size + 1); + sf_strcpy(*raw_data, buffer.size+1, buffer.buffer); + } + if (ret && json) { // We were successful so parse JSON from text if (chunk_downloader) { @@ -488,17 +524,8 @@ sf_bool STDCALL http_perform(CURL *curl, buffer.buffer[buffer.size] = '\0'; } snowflake_cJSON_Delete(*json); - *json = NULL; - if (is_saml_response(buffer.buffer)) - { - *json = snowflake_cJSON_CreateObject(); - snowflake_cJSON_AddRawToObject(*json, "data", buffer.buffer); - snowflake_cJSON_AddNullToObject(*json, "code"); - } - else - { - *json = snowflake_cJSON_Parse(buffer.buffer); - } + *json = snowflake_cJSON_Parse(buffer.buffer); + if (*json) { ret = SF_BOOLEAN_TRUE; if (is_one_time_token_request(*json)) { From 5ee2a06f951eb18ceaa6a28dda878f75e441f36e Mon Sep 17 00:00:00 2001 From: John Yun Date: Wed, 13 Nov 2024 17:08:00 -0800 Subject: [PATCH 36/55] updated log messages --- cpp/lib/Authenticator.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 1aefb5fb6e..c80d45a772 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -280,8 +280,6 @@ namespace Client dataMap["CLIENT_APP_ID"] = value("ODBC"); dataMap["CLIENT_APP_VERSION"] = value("3.4.1"); - int64 renew_timeout = 0; - int8* retried_count = &m_connection->retry_count; SFURL connectURL = getServerURLSync().path("/session/authenticator-request"); jsonObject_t authnData, respData; @@ -292,8 +290,9 @@ namespace Client int64 renewTimeout = auth_get_renew_timeout(m_connection); bool injectCURLTimeout = false; bool isNewRetry = true; + int8* retriedCount = &m_connection->retry_count; - post_curl_call(connectURL, authnData, respData, 0, retryTimeout, 0, maxRetryCount, injectCURLTimeout, renewTimeout, retried_count, isNewRetry); + post_curl_call(connectURL, authnData, respData, 0, retryTimeout, 0, maxRetryCount, injectCURLTimeout, renewTimeout, retriedCount, isNewRetry); jsonObject_t& data = respData["data"].get(); tokenURLStr = data["tokenUrl"].get(); ssoURLStr = data["ssoUrl"].get(); @@ -318,6 +317,7 @@ namespace Client curl_desc = get_curl_desc_from_pool(destination.c_str(), m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); SF_ERROR_STRUCT* err = &m_connection->error; + int64 elapsedTime = 0; // add headers for account and authentication @@ -327,10 +327,10 @@ namespace Client httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { - log_trace("sf", "AuthenticatorOKTA", - "getIdpInfo", + log_trace("sf", "IDPAuthenticator", + "post_curl_call", "Failed to create the header for the request to get the token URL and the SSO URL"); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: failed to create the header", SF_SQLSTATE_GENERAL_ERROR); AUTH_THROW(err); goto cleanup; } @@ -339,7 +339,7 @@ namespace Client &resp_data, err, renewTimeout, maxRetryCount, retryTimeout, &elapsedTime, retriedCount, NULL, SF_BOOLEAN_TRUE)) { - log_info("sf", "Connection", "getIdpInfo", + log_info("sf", "IDPAuthenticator", "post_curl_call", "Fail to get authenticator info, response body=%s\n", snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp_data, "data"))); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); @@ -350,8 +350,8 @@ namespace Client if (elapsedTime >= retryTimeout) { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponseUsingOkta", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + CXX_LOG_WARN("sf", "IDPAuthenticator", "post_curl_call", + "timeout reached: %d, elapsed time: %d", retryTimeout, elapsedTime); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); @@ -509,14 +509,16 @@ namespace Client int8* retriedCount, bool isNewRetry, bool parseJSON, std::string& rawData) { bool isRetry = false; + std::string destination = url.toString(); void* curl_desc; CURL* curl; curl_desc = get_curl_desc_from_pool(destination.c_str(), m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); + SF_ERROR_STRUCT* err = &m_connection->error; - int64 elapsedTime = 0; char* raw_data = NULL; + int64 elapsedTime = 0; // add headers for account and authentication SF_HEADER* httpExtraHeaders = sf_header_create(); @@ -526,7 +528,7 @@ namespace Client log_trace("sf", "AuthenticatorOKTA", "get_curl_call", "Failed to create the header for the request to get onetime token"); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: failed to create the header", SF_SQLSTATE_GENERAL_ERROR); goto cleanup; } From a729180ed04d90d4114de2e71102f10483c64540 Mon Sep 17 00:00:00 2001 From: John Yun Date: Wed, 13 Nov 2024 17:55:41 -0800 Subject: [PATCH 37/55] fix --- lib/http_perform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/http_perform.c b/lib/http_perform.c index d1738a8120..4ef0769eaf 100644 --- a/lib/http_perform.c +++ b/lib/http_perform.c @@ -164,7 +164,7 @@ sf_bool STDCALL http_perform(CURL* curl, { return http_perform_internal(curl, request_type, url, header, body, json, non_json_resp, network_timeout, chunk_downloader, error, insecure_mode, fail_open, retry_on_curle_couldnt_connect_count, renew_timeout, retry_max_count, - elapsed_time, retried_count, is_renew, renew_injection, proxy, no_proxy, include_retry_reason, is_new_strategy_request, TRUE, NULL); + elapsed_time, retried_count, is_renew, renew_injection, proxy, no_proxy, include_retry_reason, is_new_strategy_request, SF_BOOLEAN_TRUE, NULL); } sf_bool STDCALL http_perform_internal(CURL *curl, From 96bd67d63a296e1ccb10c6a63b474deede59029a Mon Sep 17 00:00:00 2001 From: John Yun Date: Thu, 14 Nov 2024 09:21:10 -0800 Subject: [PATCH 38/55] mac error fix --- cpp/lib/Authenticator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index c80d45a772..7afd9baed4 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -591,7 +591,7 @@ namespace Client { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getOneTimeToken", "Fail to get one time token response, response body=%s", - picojson::value(respData).serialize()); + picojson::value(respData).serialize().c_str()); SET_SNOWFLAKE_ERROR(&m_connection->error, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); AUTH_THROW("Failed to get the one time token from Okta authentication.") } @@ -679,8 +679,8 @@ namespace Client "The specified authenticator and destination URL in " "Saml Assertion did not " "match, expected=%s, post back=%s", - server_url, - post_back_url); + server_url.c_str(), + post_back_url.c_str()); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "SFSamlResponseVerificationFailed", SF_SQLSTATE_GENERAL_ERROR); } } From 5896aeffe26e5d6e17c9a7794505d645192a663c Mon Sep 17 00:00:00 2001 From: John Yun Date: Thu, 14 Nov 2024 09:23:17 -0800 Subject: [PATCH 39/55] mac error fix --- cpp/lib/Authenticator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 7afd9baed4..9e2ae50520 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -629,7 +629,7 @@ namespace Client CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", "The specified authenticator is not supported, " "authenticator=%s, token url=%s, sso url=%s", - m_connection->authenticator, tokenURLStr, ssoURLStr); + m_connection->authenticator, tokenURLStr.c_str(), ssoURLStr.c_str()); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); AUTH_THROW(err); } From a47a982536e45bcf439ff00d59576a9d8f4396fe Mon Sep 17 00:00:00 2001 From: John Yun Date: Thu, 14 Nov 2024 10:55:43 -0800 Subject: [PATCH 40/55] change function names --- cpp/lib/Authenticator.cpp | 13 ++++++------- cpp/lib/Authenticator.hpp | 6 +++--- lib/connection.c | 6 ------ lib/connection.h | 5 ----- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 9e2ae50520..860f12a092 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -292,7 +292,7 @@ namespace Client bool isNewRetry = true; int8* retriedCount = &m_connection->retry_count; - post_curl_call(connectURL, authnData, respData, 0, retryTimeout, 0, maxRetryCount, injectCURLTimeout, renewTimeout, retriedCount, isNewRetry); + curl_post_call(connectURL, authnData, respData, 0, retryTimeout, 0, maxRetryCount, injectCURLTimeout, renewTimeout, retriedCount, isNewRetry); jsonObject_t& data = respData["data"].get(); tokenURLStr = data["tokenUrl"].get(); ssoURLStr = data["ssoUrl"].get(); @@ -307,7 +307,7 @@ namespace Client return url; } - void IDPAuthenticator::post_curl_call(SFURL& url, const jsonObject_t& obj, jsonObject_t& resp, int64 curlTimeout, + void IDPAuthenticator::curl_post_call(SFURL& url, const jsonObject_t& obj, jsonObject_t& resp, int64 curlTimeout, int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, int8 *retriedCount, bool isNewRetry) { @@ -335,7 +335,7 @@ namespace Client goto cleanup; } - if (!curl_post_call(m_connection, curl, (char*) destination.c_str(), httpExtraHeaders, (char*)s_body.c_str(), + if (!::curl_post_call(m_connection, curl, (char*) destination.c_str(), httpExtraHeaders, (char*)s_body.c_str(), &resp_data, err, renewTimeout, maxRetryCount, retryTimeout, &elapsedTime, retriedCount, NULL, SF_BOOLEAN_TRUE)) { @@ -504,7 +504,7 @@ namespace Client // nop } - void AuthenticatorOKTA::get_curl_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, + void AuthenticatorOKTA::curl_get_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, int8* retriedCount, bool isNewRetry, bool parseJSON, std::string& rawData) { @@ -585,7 +585,7 @@ namespace Client dataMap["password"] = picojson::value(m_connection->password); try { - post_curl_call(tokenURL, dataMap, respData, 0, retry_timeout, 8, retry_max_count, false, renewTimeout, retried_count, false); + curl_post_call(tokenURL, dataMap, respData, 0, retry_timeout, 8, retry_max_count, false, renewTimeout, retried_count, false); } catch (...) { @@ -614,7 +614,7 @@ namespace Client jsonObject_t resp; SFURL sso_url = SFURL::parse(ssoURLStr); sso_url.addQueryParam("onetimetoken", oneTimeToken); - get_curl_call(sso_url, resp, 0, retry_timeout, 8, 0, retry_max_count, renewTimeout, retried_count, false, false, m_samlResponse); + curl_get_call(sso_url, resp, 0, retry_timeout, 8, 0, retry_max_count, renewTimeout, retried_count, false, false, m_samlResponse); } void AuthenticatorOKTA::authenticate() @@ -665,7 +665,6 @@ namespace Client retriedCount, retryTimeout - elapsedSeconds); m_connection->retry_timeout -= elapsedSeconds; m_connection->retry_count = retriedCount; - } } diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 9348ed5672..ca0468efa1 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -71,10 +71,10 @@ namespace Client * Get IdpInfo for OKTA and SAML 2.0 application */ void getIDPInfo(); - virtual void post_curl_call(SFURL& url, const jsonObject_t& obj, jsonObject_t& resp, int64 curlTimeout, + virtual void curl_post_call(SFURL& url, const jsonObject_t& obj, jsonObject_t& resp, int64 curlTimeout, int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, int8 *retriedCount, bool isNewRetry); - virtual void get_curl_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, + virtual void curl_get_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, int8 *retriedCount, bool isNewRetry, bool parseJSON, std::string& raw_data) = 0; SFURL getServerURLSync(); @@ -131,7 +131,7 @@ namespace Client protected: - void get_curl_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, + void curl_get_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, int8* retriedCount, bool isNewRetry, bool parseJSON, std::string& rawData); void getOneTimeToken(); diff --git a/lib/connection.c b/lib/connection.c index 0b1c206331..6ab7c91514 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1356,9 +1356,3 @@ sf_bool is_one_time_token_request(cJSON* resp) { return snowflake_cJSON_HasObjectItem(resp, "cookieToken") || snowflake_cJSON_HasObjectItem(resp, "sessionToken"); } - -sf_bool is_saml_response(char* response) -{ - char* doctype = ""; - return strncmp(response, doctype, strlen(doctype)) == 0; -} \ No newline at end of file diff --git a/lib/connection.h b/lib/connection.h index aa69b4fafa..82e20f90f5 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -657,11 +657,6 @@ uint64 sf_get_current_time_millis(); * a function to check that this request is whether the one time token request. */ sf_bool is_one_time_token_request(cJSON* resp); - -/* -* a function to check that this request is whether the response includes the SAML response. -*/ -sf_bool is_saml_response(char* response); #ifdef __cplusplus } #endif From 74c6b902348b585aa348cdd34d497ad8c2485a40 Mon Sep 17 00:00:00 2001 From: John Yun Date: Thu, 14 Nov 2024 11:45:25 -0800 Subject: [PATCH 41/55] change the parameter name --- cpp/lib/Authenticator.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 860f12a092..415bff69b8 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -288,11 +288,12 @@ namespace Client int64 maxRetryCount = get_login_retry_count(m_connection); int64 retryTimeout = get_login_timeout(m_connection); int64 renewTimeout = auth_get_renew_timeout(m_connection); + int64 curlTimeout = m_connection->network_timeout; bool injectCURLTimeout = false; bool isNewRetry = true; int8* retriedCount = &m_connection->retry_count; - curl_post_call(connectURL, authnData, respData, 0, retryTimeout, 0, maxRetryCount, injectCURLTimeout, renewTimeout, retriedCount, isNewRetry); + curl_post_call(connectURL, authnData, respData, curlTimeout, retryTimeout, 0, maxRetryCount, injectCURLTimeout, renewTimeout, retriedCount, isNewRetry); jsonObject_t& data = respData["data"].get(); tokenURLStr = data["tokenUrl"].get(); ssoURLStr = data["ssoUrl"].get(); @@ -610,11 +611,12 @@ namespace Client int64 elapsedTime = 0; int8* retried_count = &m_connection->retry_count; int64 renewTimeout = auth_get_renew_timeout(m_connection); + int64 curlTimeout = m_connection->network_timeout; jsonObject_t resp; SFURL sso_url = SFURL::parse(ssoURLStr); sso_url.addQueryParam("onetimetoken", oneTimeToken); - curl_get_call(sso_url, resp, 0, retry_timeout, 8, 0, retry_max_count, renewTimeout, retried_count, false, false, m_samlResponse); + curl_get_call(sso_url, resp, curlTimeout, retry_timeout, 8, retry_max_count, false, renewTimeout, retried_count, false, false, m_samlResponse); } void AuthenticatorOKTA::authenticate() From 22438cd41179e84858bfb6479e7e0cacc84ab970 Mon Sep 17 00:00:00 2001 From: John Yun Date: Fri, 15 Nov 2024 08:52:46 -0800 Subject: [PATCH 42/55] refactor it with NON_JSON_RESP --- cpp/lib/Authenticator.cpp | 20 ++++++++++-------- lib/connection.c | 11 ++++++++++ lib/connection.h | 12 ++--------- lib/http_perform.c | 43 ++++++--------------------------------- 4 files changed, 31 insertions(+), 55 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 415bff69b8..ce029d5b06 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -518,9 +518,13 @@ namespace Client curl = get_curl_from_desc(curl_desc); SF_ERROR_STRUCT* err = &m_connection->error; - char* raw_data = NULL; int64 elapsedTime = 0; + NON_JSON_RESP* raw_resp = new NON_JSON_RESP; + raw_resp->write_callback = non_json_resp_write_callback; + RAW_JSON_BUFFER buf = { NULL,0 }; + raw_resp->buffer = (void*)&buf; + // add headers for account and authentication SF_HEADER* httpExtraHeaders = sf_header_create(); @@ -533,12 +537,12 @@ namespace Client goto cleanup; } - if (!http_perform_internal(curl, GET_REQUEST_TYPE, (char*)destination.c_str(), httpExtraHeaders, NULL, NULL, NULL, - get_retry_timeout(m_connection), SF_BOOLEAN_FALSE, err, + if (!http_perform(curl, GET_REQUEST_TYPE, (char*)destination.c_str(), httpExtraHeaders, NULL, NULL, raw_resp, + curlTimeout, SF_BOOLEAN_FALSE, err, m_connection->insecure_mode, m_connection->ocsp_fail_open, m_connection->retry_on_curle_couldnt_connect_count, renewTimeout, maxRetryCount, &elapsedTime, retriedCount, NULL, SF_BOOLEAN_FALSE, - m_connection->proxy, m_connection->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE, parseJSON, &raw_data)) { + m_connection->proxy, m_connection->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE)) { // Error is set in the perform function isRetry = true; goto cleanup; @@ -554,11 +558,11 @@ namespace Client goto cleanup; } - rawData = raw_data; + rawData = buf.buffer; cleanup: sf_header_destroy(httpExtraHeaders); free_curl_desc(curl_desc); - SF_FREE(raw_data); + SF_FREE(raw_resp); if (isRetry) { RETRY_THROW(elapsedTime, *retriedCount); } @@ -577,7 +581,7 @@ namespace Client int8 retry_max_count = get_login_retry_count(m_connection); int8* retried_count = &m_connection->retry_count; int64 renewTimeout = auth_get_renew_timeout(m_connection); - + int64 curlTimeout = m_connection->network_timeout; SFURL tokenURL = SFURL::parse(tokenURLStr); @@ -586,7 +590,7 @@ namespace Client dataMap["password"] = picojson::value(m_connection->password); try { - curl_post_call(tokenURL, dataMap, respData, 0, retry_timeout, 8, retry_max_count, false, renewTimeout, retried_count, false); + curl_post_call(tokenURL, dataMap, respData, curlTimeout, retry_timeout, 8, retry_max_count, false, renewTimeout, retried_count, false); } catch (...) { diff --git a/lib/connection.c b/lib/connection.c index 6ab7c91514..f9f7702281 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1356,3 +1356,14 @@ sf_bool is_one_time_token_request(cJSON* resp) { return snowflake_cJSON_HasObjectItem(resp, "cookieToken") || snowflake_cJSON_HasObjectItem(resp, "sessionToken"); } + +size_t non_json_resp_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + return json_resp_cb(ptr, size, nmemb, userdata); +} + +NON_JSON_RESP* create_non_json_resp() { + NON_JSON_RESP* resp = (NON_JSON_RESP*)SF_CALLOC(1, sizeof(NON_JSON_RESP)); + resp->write_callback = non_json_resp_write_callback; + return resp; +} diff --git a/lib/connection.h b/lib/connection.h index 82e20f90f5..53c6fce433 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -474,16 +474,6 @@ sf_bool STDCALL http_perform(CURL *curl, SF_REQUEST_TYPE request_type, char *url sf_bool include_retry_reason, sf_bool is_login_request); -sf_bool STDCALL http_perform_internal(CURL* curl, SF_REQUEST_TYPE request_type, char* url, SF_HEADER* header, - char* body, cJSON** json, NON_JSON_RESP* non_json_resp, int64 network_timeout, sf_bool chunk_downloader, - SF_ERROR_STRUCT* error, sf_bool insecure_mode, sf_bool fail_open, - int8 retry_on_curle_couldnt_connect_count, - int64 renew_timeout, int8 retry_max_count, - int64* elapsed_time, int8* retried_count, - sf_bool* is_renew, sf_bool renew_injection, - const char* proxy, const char* no_proxy, - sf_bool include_retry_reason, - sf_bool is_login_request, sf_bool parse_json, char** raw_data); /** * Returns true if HTTP code is retryable, false otherwise. * @@ -657,6 +647,8 @@ uint64 sf_get_current_time_millis(); * a function to check that this request is whether the one time token request. */ sf_bool is_one_time_token_request(cJSON* resp); + +size_t non_json_resp_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata); #ifdef __cplusplus } #endif diff --git a/lib/http_perform.c b/lib/http_perform.c index 4ef0769eaf..bd5c8b5c79 100644 --- a/lib/http_perform.c +++ b/lib/http_perform.c @@ -138,36 +138,7 @@ void my_sleep_ms(uint32 sleepMs) #endif } -sf_bool STDCALL http_perform(CURL* curl, - SF_REQUEST_TYPE request_type, - char* url, - SF_HEADER* header, - char* body, - cJSON** json, - NON_JSON_RESP* non_json_resp, - int64 network_timeout, - sf_bool chunk_downloader, - SF_ERROR_STRUCT* error, - sf_bool insecure_mode, - sf_bool fail_open, - int8 retry_on_curle_couldnt_connect_count, - int64 renew_timeout, - int8 retry_max_count, - int64* elapsed_time, - int8* retried_count, - sf_bool* is_renew, - sf_bool renew_injection, - const char* proxy, - const char* no_proxy, - sf_bool include_retry_reason, - sf_bool is_new_strategy_request) -{ - return http_perform_internal(curl, request_type, url, header, body, json, non_json_resp, network_timeout, - chunk_downloader, error, insecure_mode, fail_open, retry_on_curle_couldnt_connect_count, renew_timeout, retry_max_count, - elapsed_time, retried_count, is_renew, renew_injection, proxy, no_proxy, include_retry_reason, is_new_strategy_request, SF_BOOLEAN_TRUE, NULL); -} - -sf_bool STDCALL http_perform_internal(CURL *curl, +sf_bool STDCALL http_perform(CURL *curl, SF_REQUEST_TYPE request_type, char *url, SF_HEADER *header, @@ -189,9 +160,7 @@ sf_bool STDCALL http_perform_internal(CURL *curl, const char *proxy, const char *no_proxy, sf_bool include_retry_reason, - sf_bool is_new_strategy_request, - sf_bool parse_json, - char** raw_data) { + sf_bool is_new_strategy_request) { CURLcode res; sf_bool ret = SF_BOOLEAN_FALSE; sf_bool retry = SF_BOOLEAN_FALSE; @@ -508,10 +477,10 @@ sf_bool STDCALL http_perform_internal(CURL *curl, } while (retry); - if (!parse_json) { - *raw_data = (char*)SF_CALLOC(1, buffer.size + 1); - sf_strcpy(*raw_data, buffer.size+1, buffer.buffer); - } + //if (!parse_json) { + // *raw_data = (char*)SF_CALLOC(1, buffer.size + 1); + // sf_strcpy(*raw_data, buffer.size+1, buffer.buffer); + //} if (ret && json) { // We were successful so parse JSON from text From cb358300b17ad3783029d408b480dea64b27ae92 Mon Sep 17 00:00:00 2001 From: John Yun Date: Fri, 15 Nov 2024 09:32:44 -0800 Subject: [PATCH 43/55] remove and refactor unnecessary data --- cpp/lib/Authenticator.cpp | 14 +-- cpp/lib/Authenticator.hpp | 10 +- cpp/lib/SnowflakeUtil.cpp | 2 +- include/snowflake/Exceptions.hpp | 159 +++++++++++++++---------------- lib/connection.c | 8 +- lib/connection.h | 5 +- lib/snowflake_util.h | 13 ++- 7 files changed, 101 insertions(+), 110 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index ce029d5b06..a69f3f9691 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -40,18 +40,12 @@ #define AUTH_THROW(msg) \ { \ - throw Snowflake::Client::Exception::AuthException(msg); \ -} - -#define JWT_THROW(err, msg)\ -{ \ - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "Failed to created the header for the okta authentication", SF_SQLSTATE_GENERAL_ERROR); \ - AUTH_THROW(err); \ + throw ::AuthException(msg); \ } #define RETRY_THROW(elapsedSeconds, retriedCount)\ { \ - throw Snowflake::Client::Exception::RenewTimeoutException(elapsedSeconds, retriedCount, false);\ + throw ::RenewTimeoutException(elapsedSeconds, retriedCount, false);\ } // wrapper functions for C @@ -407,7 +401,7 @@ namespace Client claimSet->addClaim("iss", issuer); m_jwt->setClaimSet(std::move(claimSet)); } - catch (Exception::AuthException e) { + catch (AuthException& e) { SET_SNOWFLAKE_ERROR(&conn->error, SF_STATUS_ERROR_GENERAL, e.message_.c_str(), SF_SQLSTATE_GENERAL_ERROR); AUTH_THROW("JWT Authentication failed"); } @@ -649,7 +643,7 @@ namespace Client getSAMLResponse(); break; } - catch (Exception::RenewTimeoutException& e) + catch (RenewTimeoutException& e) { int64 elapsedSeconds = e.getElapsedSeconds(); int64 retryTimeout = get_retry_timeout(m_connection); diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index ca0468efa1..26982606c5 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -78,7 +78,6 @@ namespace Client int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, int8 *retriedCount, bool isNewRetry, bool parseJSON, std::string& raw_data) = 0; SFURL getServerURLSync(); - jsonObject_t respdata; SF_CONNECT* m_connection; std::string tokenURLStr; std::string ssoURLStr; @@ -128,20 +127,19 @@ namespace Client void updateDataMap(jsonObject_t& dataMap); - protected: - void curl_get_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, int8* retriedCount, bool isNewRetry, bool parseJSON, std::string& rawData); - void getOneTimeToken(); - void getSAMLResponse(); - private: std::string m_samlResponse; std::string oneTimeToken; + void getOneTimeToken(); + + void getSAMLResponse(); + /** * Extract post back url from samel response. Input is in HTML format. */ diff --git a/cpp/lib/SnowflakeUtil.cpp b/cpp/lib/SnowflakeUtil.cpp index 1a190dd9e1..7bd8c1a5fa 100644 --- a/cpp/lib/SnowflakeUtil.cpp +++ b/cpp/lib/SnowflakeUtil.cpp @@ -22,7 +22,7 @@ namespace Client snowflake_cJSON_AddItemToObject(*cjson, "data", new_body); } - void strToPicoJson(jsonObject_t& picojson, std::string str) + void strToPicoJson(jsonObject_t& picojson, std::string& str) { jsonValue_t v; picojson::parse(v, str); diff --git a/include/snowflake/Exceptions.hpp b/include/snowflake/Exceptions.hpp index 7a3eb1a37f..4663b7e487 100644 --- a/include/snowflake/Exceptions.hpp +++ b/include/snowflake/Exceptions.hpp @@ -8,91 +8,82 @@ #include #include "client.h" -namespace Snowflake +class SnowflakeException: public std::exception { +public: + SnowflakeException(SF_ERROR_STRUCT *error); + + const char * what() const throw(); + + SF_STATUS code(); + + const char *sqlstate(); + + const char *msg(); + + const char *sfqid(); + + const char *file(); + + int line(); + +protected: + SF_ERROR_STRUCT *error; +}; + +class GeneralException: public SnowflakeException { +public: + GeneralException(SF_ERROR_STRUCT *error) : SnowflakeException(error) {}; +}; + +class RenewTimeoutException : public std::exception +{ +public: + RenewTimeoutException(int64 elapsedSeconds, + int8 retriedCount, + bool isCurlTimeoutNoBackoff) : + m_elapsedSeconds(elapsedSeconds), + m_retriedCount(retriedCount), + m_isCurlTimeoutNoBackoff(isCurlTimeoutNoBackoff) + {} + + int64 getElapsedSeconds() + { + return m_elapsedSeconds; + } + + int8 getRetriedCount() + { + return m_retriedCount; + } + + bool isCurlTimeoutNoBackoff() + { + return m_isCurlTimeoutNoBackoff; + } + + virtual const char* what() const noexcept + { + return "internal renew timeout exception"; + } + +private: + int64 m_elapsedSeconds; + int8 m_retriedCount; + // The flag indicate if the renew exception is thrown for renew the request + // within curl timeout and no backoff made + bool m_isCurlTimeoutNoBackoff; +}; + +struct AuthException : public std::exception { - namespace Client + AuthException(SF_ERROR_STRUCT* error) : message_(error->msg) {} + AuthException(const std::string& message) : message_(message) {} + + const char* what() const noexcept { - namespace Exception - { - class SnowflakeException : public std::exception { - public: - SnowflakeException(SF_ERROR_STRUCT* error); - - const char* what() const throw(); - - SF_STATUS code(); - - const char* sqlstate(); - - const char* msg(); - - const char* sfqid(); - - const char* file(); - - int line(); - - protected: - SF_ERROR_STRUCT* error; - }; - - class GeneralException : public SnowflakeException { - public: - GeneralException(SF_ERROR_STRUCT* error) : SnowflakeException(error) {}; - }; - - class RenewTimeoutException : public std::exception - { - public: - RenewTimeoutException(int64 elapsedSeconds, - int8 retriedCount, - bool isCurlTimeoutNoBackoff) : - m_elapsedSeconds(elapsedSeconds), - m_retriedCount(retriedCount), - m_isCurlTimeoutNoBackoff(isCurlTimeoutNoBackoff) - {} - - int64 getElapsedSeconds() - { - return m_elapsedSeconds; - } - - int8 getRetriedCount() - { - return m_retriedCount; - } - - bool isCurlTimeoutNoBackoff() - { - return m_isCurlTimeoutNoBackoff; - } - - virtual const char* what() const noexcept - { - return "internal renew timeout exception"; - } - - private: - int64 m_elapsedSeconds; - int8 m_retriedCount; - // The flag indicate if the renew exception is thrown for renew the request - // within curl timeout and no backoff made - bool m_isCurlTimeoutNoBackoff; - }; - - struct AuthException : public std::exception - { - AuthException(SF_ERROR_STRUCT* error) : message_(error->msg) {} - AuthException(const std::string& message) : message_(message) {} - - const char* what() const noexcept - { - return message_.c_str(); - } - - std::string message_; - }; - } + return message_.c_str(); } -} + + std::string message_; +}; #endif //SNOWFLAKECLIENT_EXCEPTIONS_HPP diff --git a/lib/connection.c b/lib/connection.c index f9f7702281..fd4a075071 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -1360,10 +1360,4 @@ sf_bool is_one_time_token_request(cJSON* resp) size_t non_json_resp_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) { return json_resp_cb(ptr, size, nmemb, userdata); -} - -NON_JSON_RESP* create_non_json_resp() { - NON_JSON_RESP* resp = (NON_JSON_RESP*)SF_CALLOC(1, sizeof(NON_JSON_RESP)); - resp->write_callback = non_json_resp_write_callback; - return resp; -} +} \ No newline at end of file diff --git a/lib/connection.h b/lib/connection.h index 53c6fce433..be84501e79 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -644,10 +644,13 @@ int64 get_retry_timeout(SF_CONNECT *sf); uint64 sf_get_current_time_millis(); /* -* a function to check that this request is whether the one time token request. +* A function to check that this request is whether the one time token request. */ sf_bool is_one_time_token_request(cJSON* resp); +/* +* A write callback function to use to write the response text received from the cURL response with non_json_resp +*/ size_t non_json_resp_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata); #ifdef __cplusplus } diff --git a/lib/snowflake_util.h b/lib/snowflake_util.h index 589324dbda..e0a5d11cc4 100644 --- a/lib/snowflake_util.h +++ b/lib/snowflake_util.h @@ -11,9 +11,20 @@ extern "C" { typedef std::map jsonObject_t; typedef std::vector jsonArray_t; + /* + * Convert the cJSON to picoJSON + */ void cJSONtoPicoJson(cJSON* cjson, jsonObject_t& picojson); + + /* + * Convert the picojson to cJSON + */ void picoJsonTocJson(jsonObject_t &picojson, cJSON** cjson); - void strToPicoJson(jsonObject_t& picojson, std::string str); + + /* + * Stringfy the picojson data. + */ + void strToPicoJson(jsonObject_t& picojson, std::string& str); #ifdef __cplusplus } From df7d539c30daa7c494641a4e76b2221034a8b112 Mon Sep 17 00:00:00 2001 From: John Yun Date: Fri, 15 Nov 2024 10:45:25 -0800 Subject: [PATCH 44/55] lint fix --- cpp/lib/Authenticator.cpp | 31 +++++++++++++++---------------- cpp/lib/Authenticator.hpp | 2 +- lib/connection.h | 2 +- lib/http_perform.c | 6 +----- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index a69f3f9691..6ea61df271 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -70,7 +70,7 @@ extern "C" { return AUTH_OKTA; } - SF_STATUS STDCALL auth_initialize(SF_CONNECT * conn) + SF_STATUS STDCALL auth_initialize(SF_CONNECT *conn) { if (!conn) { @@ -102,7 +102,7 @@ extern "C" { return SF_STATUS_SUCCESS; } - int64 auth_get_renew_timeout(SF_CONNECT * conn) + int64 auth_get_renew_timeout(SF_CONNECT *conn) { if (!conn || !conn->auth_object) { @@ -120,7 +120,7 @@ extern "C" { } } - SF_STATUS STDCALL auth_authenticate(SF_CONNECT * conn) + SF_STATUS STDCALL auth_authenticate(SF_CONNECT *conn) { if (!conn || !conn->auth_object) { @@ -139,7 +139,7 @@ extern "C" { return SF_STATUS_SUCCESS; } - void auth_update_json_body(SF_CONNECT * conn, cJSON* body) + void auth_update_json_body(SF_CONNECT *conn, cJSON* body) { cJSON* data = snowflake_cJSON_GetObjectItem(body, "data"); if (!data) @@ -178,7 +178,7 @@ extern "C" { return; } - void auth_renew_json_body(SF_CONNECT * conn, cJSON* body) + void auth_renew_json_body(SF_CONNECT *conn, cJSON* body) { if (!conn || !conn->auth_object) { @@ -202,7 +202,7 @@ extern "C" { return; } - void STDCALL auth_terminate(SF_CONNECT * conn) + void STDCALL auth_terminate(SF_CONNECT *conn) { if (!conn || !conn->auth_object) { @@ -234,7 +234,7 @@ namespace Snowflake { namespace Client { - using namespace picojson; + using namespace picojson; void IAuthenticator::renewDataMap(jsonObject_t& dataMap) { @@ -274,7 +274,6 @@ namespace Client dataMap["CLIENT_APP_ID"] = value("ODBC"); dataMap["CLIENT_APP_VERSION"] = value("3.4.1"); - SFURL connectURL = getServerURLSync().path("/session/authenticator-request"); jsonObject_t authnData, respData; authnData["data"] = value(dataMap); @@ -311,7 +310,7 @@ namespace Client CURL* curl; curl_desc = get_curl_desc_from_pool(destination.c_str(), m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); - SF_ERROR_STRUCT* err = &m_connection->error; + SF_ERROR_STRUCT *err = &m_connection->error; int64 elapsedTime = 0; @@ -342,7 +341,6 @@ namespace Client goto cleanup; } - if (elapsedTime >= retryTimeout) { CXX_LOG_WARN("sf", "IDPAuthenticator", "post_curl_call", @@ -426,7 +424,7 @@ namespace Client claimSet->addClaim("exp", (long)seconds.count() + m_timeOut); } - void AuthenticatorJWT::updateDataMap(jsonObject_t &dataMap) + void AuthenticatorJWT::updateDataMap(jsonObject_t& dataMap) { dataMap["AUTHENTICATOR"] = picojson::value(SF_AUTHENTICATOR_JWT); dataMap["TOKEN"] = picojson::value(m_jwt->serialize(m_privKey)); @@ -489,7 +487,7 @@ namespace Client } AuthenticatorOKTA::AuthenticatorOKTA( - SF_CONNECT* connection) : IDPAuthenticator(connection) + SF_CONNECT *connection) : IDPAuthenticator(connection) { // nop } @@ -511,7 +509,7 @@ namespace Client curl_desc = get_curl_desc_from_pool(destination.c_str(), m_connection->proxy, m_connection->no_proxy); curl = get_curl_from_desc(curl_desc); - SF_ERROR_STRUCT* err = &m_connection->error; + SF_ERROR_STRUCT *err = &m_connection->error; int64 elapsedTime = 0; NON_JSON_RESP* raw_resp = new NON_JSON_RESP; @@ -536,8 +534,9 @@ namespace Client m_connection->insecure_mode, m_connection->ocsp_fail_open, m_connection->retry_on_curle_couldnt_connect_count, renewTimeout, maxRetryCount, &elapsedTime, retriedCount, NULL, SF_BOOLEAN_FALSE, - m_connection->proxy, m_connection->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE)) { - // Error is set in the perform function + m_connection->proxy, m_connection->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE)) + { + //Fail to get the saml response. Retry. isRetry = true; goto cleanup; } @@ -622,7 +621,7 @@ namespace Client // 1. get authenticator info getIDPInfo(); - SF_ERROR_STRUCT* err = &m_connection->error; + SF_ERROR_STRUCT *err = &m_connection->error; // 2. verify ssoUrl and tokenUrl contains same prefix if (!SFURL::urlHasSamePrefix(tokenURLStr, m_connection->authenticator)) { diff --git a/cpp/lib/Authenticator.hpp b/cpp/lib/Authenticator.hpp index 26982606c5..517947a045 100644 --- a/cpp/lib/Authenticator.hpp +++ b/cpp/lib/Authenticator.hpp @@ -119,7 +119,7 @@ namespace Client class AuthenticatorOKTA : public IDPAuthenticator { public: - AuthenticatorOKTA(SF_CONNECT* conn); + AuthenticatorOKTA(SF_CONNECT *conn); ~AuthenticatorOKTA(); diff --git a/lib/connection.h b/lib/connection.h index be84501e79..f05733065d 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -646,7 +646,7 @@ uint64 sf_get_current_time_millis(); /* * A function to check that this request is whether the one time token request. */ -sf_bool is_one_time_token_request(cJSON* resp); +sf_bool is_one_time_token_request(cJSON *resp); /* * A write callback function to use to write the response text received from the cURL response with non_json_resp diff --git a/lib/http_perform.c b/lib/http_perform.c index bd5c8b5c79..8190a261f3 100644 --- a/lib/http_perform.c +++ b/lib/http_perform.c @@ -477,11 +477,6 @@ sf_bool STDCALL http_perform(CURL *curl, } while (retry); - //if (!parse_json) { - // *raw_data = (char*)SF_CALLOC(1, buffer.size + 1); - // sf_strcpy(*raw_data, buffer.size+1, buffer.buffer); - //} - if (ret && json) { // We were successful so parse JSON from text if (chunk_downloader) { @@ -493,6 +488,7 @@ sf_bool STDCALL http_perform(CURL *curl, buffer.buffer[buffer.size] = '\0'; } snowflake_cJSON_Delete(*json); + *json = NULL; *json = snowflake_cJSON_Parse(buffer.buffer); if (*json) { From bf71bb3baa393fc871437d5c22f19746d1b787b2 Mon Sep 17 00:00:00 2001 From: John Yun Date: Fri, 22 Nov 2024 09:29:22 -0800 Subject: [PATCH 45/55] refactoring IAuth --- CMakeLists.txt | 4 +- cpp/lib/Authenticator.cpp | 352 ++++++++++----------------------- cpp/lib/Authenticator.hpp | 87 +------- include/snowflake/entities.hpp | 3 +- 4 files changed, 114 insertions(+), 332 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a50a847cbf..2c3488a1cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,7 +198,6 @@ set (SOURCE_FILES_PUT_GET include/snowflake/ITransferResult.hpp include/snowflake/PutGetParseResponse.hpp include/snowflake/SnowflakeTransferException.hpp - include/snowflake/IJwt.hpp include/snowflake/IBase64.hpp include/snowflake/Proxy.hpp include/snowflake/entities.hpp @@ -216,6 +215,8 @@ set(SOURCE_FILES_CPP_WRAPPER include/snowflake/SFURL.hpp include/snowflake/CurlDesc.hpp include/snowflake/CurlDescPool.hpp + include/snowflake/IJwt.hpp + include/snowflake/IAuth.hpp cpp/lib/SnowflakeUtil.cpp cpp/lib/Exceptions.cpp cpp/lib/Connection.cpp @@ -239,6 +240,7 @@ set(SOURCE_FILES_CPP_WRAPPER cpp/lib/ResultSetJson.hpp cpp/lib/Authenticator.cpp cpp/lib/Authenticator.hpp + cpp/lib/IAuth.cpp cpp/jwt/jwtWrapper.cpp cpp/util/SnowflakeCommon.cpp cpp/util/SFURL.cpp diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 6ea61df271..05064da98d 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -38,14 +38,9 @@ #define strcasecmp _stricmp #endif -#define AUTH_THROW(msg) \ -{ \ - throw ::AuthException(msg); \ -} - -#define RETRY_THROW(elapsedSeconds, retriedCount)\ -{ \ - throw ::RenewTimeoutException(elapsedSeconds, retriedCount, false);\ +#define RETRY_THROW(elapsedSeconds, retriedCount) \ +{ \ + throw RenewTimeoutException(elapsedSeconds, retriedCount, false);\ } // wrapper functions for C @@ -150,6 +145,7 @@ extern "C" { snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); + if (AUTH_OAUTH == getAuthenticatorType(conn->authenticator)) { snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); @@ -236,12 +232,6 @@ namespace Client { using namespace picojson; - void IAuthenticator::renewDataMap(jsonObject_t& dataMap) - { - authenticate(); - updateDataMap(dataMap); - } - void AuthenticatorJWT::loadPrivateKey(const std::string &privateKeyFile, const std::string &passcode) { @@ -261,104 +251,6 @@ namespace Client } } - void IDPAuthenticator::getIDPInfo() - { - // login info as a json post body - //Currently, the server is not enabled Okta authentication with C API. For testing purpose, I used the ODBC info. - jsonObject_t dataMap; - dataMap["ACCOUNT_NAME"] = value(m_connection->account); - dataMap["AUTHENTICATOR"] = value(m_connection->authenticator); - dataMap["LOGIN_NAME"] = value(m_connection->user); - dataMap["PORT"] = value(m_connection->port); - dataMap["PROTOCOL"] = value(m_connection->protocol); - dataMap["CLIENT_APP_ID"] = value("ODBC"); - dataMap["CLIENT_APP_VERSION"] = value("3.4.1"); - - SFURL connectURL = getServerURLSync().path("/session/authenticator-request"); - jsonObject_t authnData, respData; - authnData["data"] = value(dataMap); - - int64 maxRetryCount = get_login_retry_count(m_connection); - int64 retryTimeout = get_login_timeout(m_connection); - int64 renewTimeout = auth_get_renew_timeout(m_connection); - int64 curlTimeout = m_connection->network_timeout; - bool injectCURLTimeout = false; - bool isNewRetry = true; - int8* retriedCount = &m_connection->retry_count; - - curl_post_call(connectURL, authnData, respData, curlTimeout, retryTimeout, 0, maxRetryCount, injectCURLTimeout, renewTimeout, retriedCount, isNewRetry); - jsonObject_t& data = respData["data"].get(); - tokenURLStr = data["tokenUrl"].get(); - ssoURLStr = data["ssoUrl"].get(); - } - - SFURL IDPAuthenticator::getServerURLSync() - { - SFURL url = SFURL().scheme(m_connection->protocol) - .host(m_connection->host) - .port(m_connection->port); - - return url; - } - - void IDPAuthenticator::curl_post_call(SFURL& url, const jsonObject_t& obj, jsonObject_t& resp, int64 curlTimeout, - int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, - int8 *retriedCount, bool isNewRetry) - { - std::string destination = url.toString(); - void* curl_desc; - CURL* curl; - curl_desc = get_curl_desc_from_pool(destination.c_str(), m_connection->proxy, m_connection->no_proxy); - curl = get_curl_from_desc(curl_desc); - SF_ERROR_STRUCT *err = &m_connection->error; - - int64 elapsedTime = 0; - - // add headers for account and authentication - SF_HEADER* httpExtraHeaders = sf_header_create(); - std::string s_body = value(obj).serialize(); - cJSON* resp_data = NULL; - - httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; - if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { - log_trace("sf", "IDPAuthenticator", - "post_curl_call", - "Failed to create the header for the request to get the token URL and the SSO URL"); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: failed to create the header", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); - goto cleanup; - } - - if (!::curl_post_call(m_connection, curl, (char*) destination.c_str(), httpExtraHeaders, (char*)s_body.c_str(), - &resp_data, err, renewTimeout, maxRetryCount, retryTimeout, &elapsedTime, - retriedCount, NULL, SF_BOOLEAN_TRUE)) - { - log_info("sf", "IDPAuthenticator", "post_curl_call", - "Fail to get authenticator info, response body=%s\n", - snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp_data, "data"))); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); - goto cleanup; - } - - if (elapsedTime >= retryTimeout) - { - CXX_LOG_WARN("sf", "IDPAuthenticator", "post_curl_call", - "timeout reached: %d, elapsed time: %d", - retryTimeout, elapsedTime); - - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); - } - - //deduct elasped time from the retry timeout - m_connection->retry_timeout -= elapsedTime; - cJSONtoPicoJson(resp_data, resp); - cleanup: - sf_header_destroy(httpExtraHeaders); - snowflake_cJSON_Delete(resp_data); - } - AuthenticatorJWT::AuthenticatorJWT(SF_CONNECT *conn) : m_jwt{Jwt::buildIJwt()} { @@ -486,10 +378,82 @@ namespace Client return coded; } + void AuthenticatorOKTA::curl_post_call(SFURL& url, const jsonObject_t& obj, jsonObject_t& resp) + { + std::string destination = url.toString(); + void* curl_desc; + CURL* curl; + curl_desc = get_curl_desc_from_pool(destination.c_str(), m_connection->proxy, m_connection->no_proxy); + curl = get_curl_from_desc(curl_desc); + SF_ERROR_STRUCT* err = &m_connection->error; + + int64 elapsedTime = 0; + int8 maxRetryCount = get_login_retry_count(m_connection); + int64 renewTimeout = auth_get_renew_timeout(m_connection); + + // add headers for account and authentication + SF_HEADER* httpExtraHeaders = sf_header_create(); + std::string s_body = value(obj).serialize(); + cJSON* resp_data = NULL; + + httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; + if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { + log_trace("sf", "IDPAuthenticator", + "post_curl_call", + "Failed to create the header for the request to get the token URL and the SSO URL"); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: failed to create the header", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + goto cleanup; + } + + if (!::curl_post_call(m_connection, curl, (char*)destination.c_str(), httpExtraHeaders, (char*)s_body.c_str(), + &resp_data, err, renewTimeout, maxRetryCount, m_retryTimeout, &elapsedTime, + &m_retriedCount, NULL, SF_BOOLEAN_TRUE)) + { + log_info("sf", "IDPAuthenticator", "post_curl_call", + "Fail to get authenticator info, response body=%s\n", + snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp_data, "data"))); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + goto cleanup; + } + + if (elapsedTime >= m_retryTimeout) + { + CXX_LOG_WARN("sf", "IDPAuthenticator", "post_curl_call", + "timeout reached: %d, elapsed time: %d", + m_retryTimeout, elapsedTime); + + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + } + + cleanup: + m_retryTimeout -= elapsedTime; + cJSONtoPicoJson(resp_data, resp); + sf_header_destroy(httpExtraHeaders); + snowflake_cJSON_Delete(resp_data); + } + + AuthenticatorOKTA::AuthenticatorOKTA( - SF_CONNECT *connection) : IDPAuthenticator(connection) + SF_CONNECT* connection) : m_connection(connection) { - // nop + m_account = m_connection->account; + m_authenticator = m_connection->authenticator; + m_user = m_connection->user; + m_password = m_connection->password; + m_port = m_connection->port; + m_host = m_connection->host; + m_protocol = m_connection->protocol; + m_disableSamlUrlCheck = m_connection->disable_saml_url_check; + m_retriedCount = get_login_retry_count(m_connection); + m_retryTimeout = get_retry_timeout(m_connection); + + //m_appID = m_connection->application_name; + //m_appVersion = m_connection->application_version; + m_appID = "ODBC"; + m_appVersion = "3.4.1"; } AuthenticatorOKTA::~AuthenticatorOKTA() @@ -497,11 +461,13 @@ namespace Client // nop } - void AuthenticatorOKTA::curl_get_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, - int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, - int8* retriedCount, bool isNewRetry, bool parseJSON, std::string& rawData) + void AuthenticatorOKTA::curl_get_call(SFURL& url, jsonObject_t& resp, bool parseJSON, std::string& rawData) { bool isRetry = false; + int64 maxRetryCount = get_login_retry_count(m_connection); + int64 elapsedTime = 0; + int64 renewTimeout = auth_get_renew_timeout(m_connection); + int64 curlTimeout = m_connection->network_timeout; std::string destination = url.toString(); void* curl_desc; @@ -510,7 +476,6 @@ namespace Client curl = get_curl_from_desc(curl_desc); SF_ERROR_STRUCT *err = &m_connection->error; - int64 elapsedTime = 0; NON_JSON_RESP* raw_resp = new NON_JSON_RESP; raw_resp->write_callback = non_json_resp_write_callback; @@ -533,7 +498,7 @@ namespace Client curlTimeout, SF_BOOLEAN_FALSE, err, m_connection->insecure_mode, m_connection->ocsp_fail_open, m_connection->retry_on_curle_couldnt_connect_count, - renewTimeout, maxRetryCount, &elapsedTime, retriedCount, NULL, SF_BOOLEAN_FALSE, + renewTimeout, maxRetryCount, &elapsedTime, &m_retriedCount, NULL, SF_BOOLEAN_FALSE, m_connection->proxy, m_connection->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE)) { //Fail to get the saml response. Retry. @@ -541,11 +506,11 @@ namespace Client goto cleanup; } - if (elapsedTime >= retryTimeout) + if (elapsedTime >= m_retryTimeout) { CXX_LOG_WARN("sf", "AuthenticatorOKTA", "get_curl_call", "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retryTimeout, elapsedTime); + m_retryTimeout, elapsedTime); SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); goto cleanup; @@ -553,133 +518,40 @@ namespace Client rawData = buf.buffer; cleanup: + m_retryTimeout -= elapsedTime; sf_header_destroy(httpExtraHeaders); free_curl_desc(curl_desc); SF_FREE(raw_resp); if (isRetry) { - RETRY_THROW(elapsedTime, *retriedCount); + RETRY_THROW(elapsedTime, m_retriedCount); } - m_connection->retry_timeout -= elapsedTime; - m_connection->retry_count = *retriedCount; if (err->error_code != SF_STATUS_SUCCESS) { AUTH_THROW(err); } } - void AuthenticatorOKTA::getOneTimeToken() - { - // When renewTimeout < 0, throws Exception - // for each retry attempt so we can renew the one time token - int64 retry_timeout = get_login_timeout(m_connection); - int8 retry_max_count = get_login_retry_count(m_connection); - int8* retried_count = &m_connection->retry_count; - int64 renewTimeout = auth_get_renew_timeout(m_connection); - int64 curlTimeout = m_connection->network_timeout; - - SFURL tokenURL = SFURL::parse(tokenURLStr); - - jsonObject_t dataMap,respData; - dataMap["username"] = picojson::value(m_connection->user); - dataMap["password"] = picojson::value(m_connection->password); - - try { - curl_post_call(tokenURL, dataMap, respData, curlTimeout, retry_timeout, 8, retry_max_count, false, renewTimeout, retried_count, false); - } - catch (...) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getOneTimeToken", - "Fail to get one time token response, response body=%s", - picojson::value(respData).serialize().c_str()); - SET_SNOWFLAKE_ERROR(&m_connection->error, SF_STATUS_ERROR_BAD_REQUEST, "OktaConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW("Failed to get the one time token from Okta authentication.") - } - - oneTimeToken = respData["sessionToken"].get(); - if (oneTimeToken.empty()) { - oneTimeToken = respData["cookieToken"].get(); - } - } - - void AuthenticatorOKTA::getSAMLResponse() - { - bool isRetry = false; - int64 retry_timeout = get_login_timeout(m_connection); - int64 retry_max_count = get_login_retry_count(m_connection); - int64 elapsedTime = 0; - int8* retried_count = &m_connection->retry_count; - int64 renewTimeout = auth_get_renew_timeout(m_connection); - int64 curlTimeout = m_connection->network_timeout; - - jsonObject_t resp; - SFURL sso_url = SFURL::parse(ssoURLStr); - sso_url.addQueryParam("onetimetoken", oneTimeToken); - curl_get_call(sso_url, resp, curlTimeout, retry_timeout, 8, retry_max_count, false, renewTimeout, retried_count, false, false, m_samlResponse); - } - void AuthenticatorOKTA::authenticate() { - // 1. get authenticator info - getIDPInfo(); - - SF_ERROR_STRUCT *err = &m_connection->error; - // 2. verify ssoUrl and tokenUrl contains same prefix - if (!SFURL::urlHasSamePrefix(tokenURLStr, m_connection->authenticator)) + try { - CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", - "The specified authenticator is not supported, " - "authenticator=%s, token url=%s, sso url=%s", - m_connection->authenticator, tokenURLStr.c_str(), ssoURLStr.c_str()); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, "SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); + IAuthenticatorOKTA::authenticate(); } - - // 3. get one time token from okta - while (true) + catch (AuthException& e) { - getOneTimeToken(); - // 4. get SAML response - try { - getSAMLResponse(); - break; - } - catch (RenewTimeoutException& e) + SF_ERROR_STRUCT* err = &m_connection->error; + if (!err) { - int64 elapsedSeconds = e.getElapsedSeconds(); - int64 retryTimeout = get_retry_timeout(m_connection); - - if (elapsedSeconds >= retryTimeout) + std::string timeoutErr = "timeout"; + if (timeoutErr.compare(e.what())) { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponse", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - retryTimeout, elapsedSeconds); - - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + } + else + { + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_BAD_REQUEST, e.what(), SF_SQLSTATE_GENERAL_ERROR); } - - int8 retriedCount = e.getRetriedCount(); - CXX_LOG_TRACE("sf", "Connection", "Connect", - "Retry on getting SAML response with one time token renewed for %d times " - "with updated retryTimeout = %d", - retriedCount, retryTimeout - elapsedSeconds); - m_connection->retry_timeout -= elapsedSeconds; - m_connection->retry_count = retriedCount; } - } - - // 5. Validate post_back_url matches Snowflake URL - std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); - std::string server_url = getServerURLSync().toString(); - if ((!m_connection->disable_saml_url_check) && - (!SFURL::urlHasSamePrefix(post_back_url, server_url))) - { - CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", - "The specified authenticator and destination URL in " - "Saml Assertion did not " - "match, expected=%s, post back=%s", - server_url.c_str(), - post_back_url.c_str()); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "SFSamlResponseVerificationFailed", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(e.what()); } } @@ -688,27 +560,7 @@ namespace Client dataMap.erase("LOGIN_NAME"); dataMap.erase("PASSWORD"); dataMap.erase("EXT_AUTHN_DUO_METHOD"); - dataMap["RAW_SAML_RESPONSE"] = picojson::value(m_samlResponse); - } - - std::string AuthenticatorOKTA::extractPostBackUrlFromSamlResponse(std::string html) - { - std::size_t form_start = html.find(" SHA256(const std::vector &message); }; - - class AuthenticatorOKTA : public IDPAuthenticator + class AuthenticatorOKTA : public IAuthenticatorOKTA { public: AuthenticatorOKTA(SF_CONNECT *conn); @@ -124,26 +65,12 @@ namespace Client ~AuthenticatorOKTA(); void authenticate(); - void updateDataMap(jsonObject_t& dataMap); - - protected: - void curl_get_call(SFURL& url, jsonObject_t& resp, int64 curlTimeout, - int64 retryTimeout, int8 flags, int8 maxRetryCount, bool injectCURLTimeout, int64 renewTimeout, - int8* retriedCount, bool isNewRetry, bool parseJSON, std::string& rawData); + void curl_post_call(SFURL& url, const jsonObject_t& body, jsonObject_t& resp); + void curl_get_call(SFURL& url, jsonObject_t& resp, bool parseJSON, std::string& raw_data); private: - std::string m_samlResponse; - std::string oneTimeToken; - - void getOneTimeToken(); - - void getSAMLResponse(); - - /** - * Extract post back url from samel response. Input is in HTML format. - */ - std::string extractPostBackUrlFromSamlResponse(std::string html); + SF_CONNECT* m_connection; }; } // namespace Client } // namespace Snowflake diff --git a/include/snowflake/entities.hpp b/include/snowflake/entities.hpp index 86ab3734b0..123601ceec 100644 --- a/include/snowflake/entities.hpp +++ b/include/snowflake/entities.hpp @@ -13,7 +13,6 @@ namespace Snowflake { namespace Client { - size_t decode_html_entities_utf8(char* dest, const char* src); /* Takes input from and decodes into , which should be a buffer large enough to hold characters. @@ -22,6 +21,8 @@ namespace Snowflake The function returns the length of the decoded string. */ + size_t decode_html_entities_utf8(char* dest, const char* src); + } } From 1f37bf842a5329e69915e0ecbf9a98d8ac13fcc4 Mon Sep 17 00:00:00 2001 From: John Yun Date: Fri, 22 Nov 2024 10:38:32 -0800 Subject: [PATCH 46/55] add IAuth files --- cpp/lib/IAuth.cpp | 167 ++++++++++++++++++++++++++++++++++++ include/snowflake/IAuth.hpp | 125 +++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 cpp/lib/IAuth.cpp create mode 100644 include/snowflake/IAuth.hpp diff --git a/cpp/lib/IAuth.cpp b/cpp/lib/IAuth.cpp new file mode 100644 index 0000000000..c5f23aaf3a --- /dev/null +++ b/cpp/lib/IAuth.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024 Snowflake Computing, Inc. All rights reserved. + */ + +#include +#include "snowflake/Exceptions.hpp" +#include "cJSON.h" +#include "../include/snowflake/entities.hpp" +#include "../logger/SFLogger.hpp" +#include "snowflake/IAuth.hpp" + +namespace Snowflake +{ +namespace Client +{ + namespace IAuth { + using namespace picojson; + + void IAuthenticator::renewDataMap(jsonObject_t& dataMap) + { + authenticate(); + updateDataMap(dataMap); + } + + void IIDPAuthenticator::getIDPInfo() + { + jsonObject_t dataMap; + SFURL connectURL = getServerURLSync().path("/session/authenticator-request"); + dataMap["ACCOUNT_NAME"] = value(m_account); + dataMap["AUTHENTICATOR"] = value(m_authenticator); + dataMap["LOGIN_NAME"] = value(m_user); + dataMap["PORT"] = value(m_port); + dataMap["PROTOCOL"] = value(m_protocol); + dataMap["CLIENT_APP_ID"] = value(m_appID); + dataMap["CLIENT_APP_VERSION"] = value(m_appVersion);; + + jsonObject_t authnData, respData; + authnData["data"] = value(dataMap); + + curl_post_call(connectURL, authnData, respData); + jsonObject_t& data = respData["data"].get(); + tokenURLStr = data["tokenUrl"].get(); + ssoURLStr = data["ssoUrl"].get(); + } + + SFURL IIDPAuthenticator::getServerURLSync() + { + SFURL url = SFURL().scheme(m_protocol) + .host(m_host) + .port(m_port); + + return url; + } + + void IAuthenticatorOKTA::authenticate() + { + // 1. get authenticator info + getIDPInfo(); + + // 2. verify ssoUrl and tokenUrl contains same prefix + if (!SFURL::urlHasSamePrefix(tokenURLStr, m_authenticator)) + { + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", + "The specified authenticator is not supported, " + "authenticator=%s, token url=%s, sso url=%s", + m_authenticator.c_str(), tokenURLStr.c_str(), ssoURLStr.c_str()); + AUTH_THROW("SFAuthenticatorVerificationFailed: the token URL does not have the same prefix with the authenticator"); + } + + // 3. get one time token from okta + while (true) + { + SFURL tokenURL = SFURL::parse(tokenURLStr); + + jsonObject_t dataMap, respData; + dataMap["username"] = picojson::value(m_user); + dataMap["password"] = picojson::value(m_password); + + try { + curl_post_call(tokenURL, dataMap, respData); + } + catch (...) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getOneTimeToken", + "Fail to get one time token response, response body=%s", + picojson::value(respData).serialize().c_str()); + AUTH_THROW("Failed to get the one time token from Okta authentication.") + } + + oneTimeToken = respData["sessionToken"].get(); + if (oneTimeToken.empty()) { + oneTimeToken = respData["cookieToken"].get(); + } + // 4. get SAML response + try { + + jsonObject_t resp; + SFURL sso_url = SFURL::parse(ssoURLStr); + sso_url.addQueryParam("onetimetoken", oneTimeToken); + curl_get_call(sso_url, resp, false, m_samlResponse); + break; + } + catch (RenewTimeoutException& e) + { + int64 elapsedSeconds = e.getElapsedSeconds(); + + if (elapsedSeconds >= m_retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "getSamlResponse", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + m_retryTimeout, elapsedSeconds); + + AUTH_THROW("timeout"); + } + + m_retriedCount = e.getRetriedCount(); + m_retryTimeout -= elapsedSeconds; + CXX_LOG_TRACE("sf", "Connection", "Connect", + "Retry on getting SAML response with one time token renewed for %d times " + "with updated retryTimeout = %d", + m_retriedCount, m_retryTimeout); + } + } + + // 5. Validate post_back_url matches Snowflake URL + std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); + std::string server_url = getServerURLSync().toString(); + if ((!m_disableSamlUrlCheck) && + (!SFURL::urlHasSamePrefix(post_back_url, server_url))) + { + CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", + "The specified authenticator and destination URL in " + "Saml Assertion did not " + "match, expected=%s, post back=%s", + server_url.c_str(), + post_back_url.c_str()); + AUTH_THROW("SFSamlResponseVerificationFailed"); + } + } + + void IAuthenticatorOKTA::updateDataMap(jsonObject_t& dataMap) + { + dataMap["RAW_SAML_RESPONSE"] = picojson::value(m_samlResponse); + } + + std::string IAuthenticatorOKTA::extractPostBackUrlFromSamlResponse(std::string html) + { + std::size_t form_start = html.find(" +#include +#include "../../lib/snowflake_util.h" +#include "./Exceptions.hpp" + +#define AUTH_THROW(msg) \ +{ \ + throw AuthException(msg); \ +} + +namespace Snowflake +{ +namespace Client +{ +namespace IAuth +{ + /** + * Authenticator + */ + class IAuthenticator + { + public: + + IAuthenticator() : m_renewTimeout(0) + {} + + virtual ~IAuthenticator() + {} + + virtual void authenticate() = 0; + + virtual void updateDataMap(jsonObject_t& dataMap) = 0; + + // Retrieve authenticator renew timeout, return 0 if not available. + // When the authenticator renew timeout is available, the connection should + // renew the authentication (call renewDataMap) for each time the + // authenticator specific timeout exceeded within the entire login timeout. + int64 getAuthRenewTimeout() + { + return m_renewTimeout; + } + + // Renew the autentication and update datamap. + // The default behavior is to call authenticate() and updateDataMap(). + virtual void renewDataMap(jsonObject_t& dataMap); + + protected: + int64 m_renewTimeout; + }; + + + class IIDPAuthenticator + { + public: + IIDPAuthenticator() + {}; + + virtual ~IIDPAuthenticator() + {}; + + void getIDPInfo(); + + virtual SFURL getServerURLSync(); + protected: + /* + * Get IdpInfo for OKTA and SAML 2.0 application + */ + virtual void curl_post_call(SFURL& url, const jsonObject_t& body, jsonObject_t& resp) = 0; + virtual void curl_get_call(SFURL& url, jsonObject_t& resp, bool parseJSON, std::string& raw_data) = 0; + + std::string tokenURLStr; + std::string ssoURLStr; + //For EXTERNALBROSER in the future + std::string proofKeyStr; + + //These fields should be definied in the child class. + std::string m_authenticator; + std::string m_account; + std::string m_appID; + std::string m_appVersion; + std::string m_user; + std::string m_port; + std::string m_host; + std::string m_protocol; + }; + + class IAuthenticatorOKTA : public IIDPAuthenticator, public IAuthenticator + { + public: + IAuthenticatorOKTA() {}; + + virtual ~IAuthenticatorOKTA() {}; + + virtual void authenticate() = 0; + + virtual void updateDataMap(jsonObject_t& dataMap); + + /** + * Extract post back url from samel response. Input is in HTML format. + */ + std::string extractPostBackUrlFromSamlResponse(std::string html); + + protected: + //These fields should be definied in the child class. + std::string m_password; + bool m_disableSamlUrlCheck; + int8 m_retriedCount; + int64 m_retryTimeout; + + private: + std::string oneTimeToken; + std::string m_samlResponse; + }; +} // namespace Auth +} // namespace Client +} // namespace Snowflake + +#endif //SNOWFLAKECLIENT_IIDP_AUTH_HPP From cd3cf014660be1374ed82817f0485904a02de4c6 Mon Sep 17 00:00:00 2001 From: John Yun Date: Mon, 25 Nov 2024 18:18:00 -0800 Subject: [PATCH 47/55] fix minior errors --- cpp/lib/Authenticator.cpp | 17 ++--------------- lib/connection.c | 7 +++++++ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 05064da98d..8bf6aabec5 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -80,7 +80,7 @@ extern "C" { conn->auth_object = static_cast( new Snowflake::Client::AuthenticatorJWT(conn)); } - if (AUTH_OKTA == auth_type) + else if (AUTH_OKTA == auth_type) { conn->auth_object = static_cast( new Snowflake::Client::AuthenticatorOKTA(conn)); @@ -146,12 +146,6 @@ extern "C" { snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); - if (AUTH_OAUTH == getAuthenticatorType(conn->authenticator)) - { - snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); - snowflake_cJSON_AddStringToObject(data, "TOKEN", conn->oauth_token); - } - if (!conn || !conn->auth_object) { return; @@ -208,14 +202,7 @@ extern "C" { AuthenticatorType auth_type = getAuthenticatorType(conn->authenticator); try { - if (AUTH_JWT == auth_type) - { - delete static_cast(conn->auth_object); - } - if (AUTH_OKTA == auth_type) - { - delete static_cast(conn->auth_object); - } + delete static_cast(conn->auth_object); } catch (...) { diff --git a/lib/connection.c b/lib/connection.c index fd4a075071..cecc8285ac 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -187,6 +187,7 @@ cJSON *STDCALL create_auth_json_body(SF_CONNECT *sf, snowflake_cJSON_AddStringToObject(data, "EXT_AUTHN_DUO_METHOD", "push"); } } + snowflake_cJSON_AddItemToObject(data, "CLIENT_ENVIRONMENT", client_env); snowflake_cJSON_AddItemToObject(data, "SESSION_PARAMETERS", session_parameters); @@ -198,6 +199,12 @@ cJSON *STDCALL create_auth_json_body(SF_CONNECT *sf, // update authentication information to body auth_update_json_body(sf, body); + if (AUTH_OAUTH == getAuthenticatorType(sf->authenticator)) + { + snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); + snowflake_cJSON_AddStringToObject(data, "TOKEN", sf->oauth_token); + } + return body; } From 8eb336a9a9c9345ed0711ac07393904134590309 Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 26 Nov 2024 11:02:14 -0800 Subject: [PATCH 48/55] fix --- deps/zlib-1.3.1/zconf.h | 543 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 543 insertions(+) create mode 100644 deps/zlib-1.3.1/zconf.h diff --git a/deps/zlib-1.3.1/zconf.h b/deps/zlib-1.3.1/zconf.h new file mode 100644 index 0000000000..62adc8d843 --- /dev/null +++ b/deps/zlib-1.3.1/zconf.h @@ -0,0 +1,543 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2024 Jean-loup Gailly, Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + * Even better than compiling with -DZ_PREFIX would be to use configure to set + * this permanently in zconf.h using "./configure --zprefix". + */ +#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET + +/* all linked symbols and init macros */ +# define _dist_code z__dist_code +# define _length_code z__length_code +# define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits +# define _tr_flush_block z__tr_flush_block +# define _tr_init z__tr_init +# define _tr_stored_block z__tr_stored_block +# define _tr_tally z__tr_tally +# define adler32 z_adler32 +# define adler32_combine z_adler32_combine +# define adler32_combine64 z_adler32_combine64 +# define adler32_z z_adler32_z +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif +# define crc32 z_crc32 +# define crc32_combine z_crc32_combine +# define crc32_combine64 z_crc32_combine64 +# define crc32_combine_gen z_crc32_combine_gen +# define crc32_combine_gen64 z_crc32_combine_gen64 +# define crc32_combine_op z_crc32_combine_op +# define crc32_z z_crc32_z +# define deflate z_deflate +# define deflateBound z_deflateBound +# define deflateCopy z_deflateCopy +# define deflateEnd z_deflateEnd +# define deflateGetDictionary z_deflateGetDictionary +# define deflateInit z_deflateInit +# define deflateInit2 z_deflateInit2 +# define deflateInit2_ z_deflateInit2_ +# define deflateInit_ z_deflateInit_ +# define deflateParams z_deflateParams +# define deflatePending z_deflatePending +# define deflatePrime z_deflatePrime +# define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep +# define deflateSetDictionary z_deflateSetDictionary +# define deflateSetHeader z_deflateSetHeader +# define deflateTune z_deflateTune +# define deflate_copyright z_deflate_copyright +# define get_crc_table z_get_crc_table +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzfread z_gzfread +# define gzfwrite z_gzfwrite +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzvprintf z_gzvprintf +# define gzwrite z_gzwrite +# endif +# define inflate z_inflate +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define inflateBackInit z_inflateBackInit +# define inflateBackInit_ z_inflateBackInit_ +# define inflateCodesUsed z_inflateCodesUsed +# define inflateCopy z_inflateCopy +# define inflateEnd z_inflateEnd +# define inflateGetDictionary z_inflateGetDictionary +# define inflateGetHeader z_inflateGetHeader +# define inflateInit z_inflateInit +# define inflateInit2 z_inflateInit2 +# define inflateInit2_ z_inflateInit2_ +# define inflateInit_ z_inflateInit_ +# define inflateMark z_inflateMark +# define inflatePrime z_inflatePrime +# define inflateReset z_inflateReset +# define inflateReset2 z_inflateReset2 +# define inflateResetKeep z_inflateResetKeep +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateUndermine z_inflateUndermine +# define inflateValidate z_inflateValidate +# define inflate_copyright z_inflate_copyright +# define inflate_fast z_inflate_fast +# define inflate_table z_inflate_table +# ifndef Z_SOLO +# define uncompress z_uncompress +# define uncompress2 z_uncompress2 +# endif +# define zError z_zError +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif +# define zlibCompileFlags z_zlibCompileFlags +# define zlibVersion z_zlibVersion + +/* all zlib typedefs in zlib.h and zconf.h */ +# define Byte z_Byte +# define Bytef z_Bytef +# define alloc_func z_alloc_func +# define charf z_charf +# define free_func z_free_func +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif +# define gz_header z_gz_header +# define gz_headerp z_gz_headerp +# define in_func z_in_func +# define intf z_intf +# define out_func z_out_func +# define uInt z_uInt +# define uIntf z_uIntf +# define uLong z_uLong +# define uLongf z_uLongf +# define voidp z_voidp +# define voidpc z_voidpc +# define voidpf z_voidpf + +/* all zlib structs in zlib.h and zconf.h */ +# define gz_header_s z_gz_header_s +# define internal_state z_internal_state + +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + +#ifdef Z_SOLO +# ifdef _WIN64 + typedef unsigned long long z_size_t; +# else + typedef unsigned long z_size_t; +# endif +#else +# define z_longlong long long +# if defined(NO_SIZE_T) + typedef unsigned NO_SIZE_T z_size_t; +# elif defined(STDC) +# include + typedef size_t z_size_t; +# else + typedef unsigned long z_size_t; +# endif +# undef z_longlong +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus about 7 kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_UNISTD_H +#endif + +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + +#ifdef STDC +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif +#endif + +/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and + * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even + * though the former does not conform to the LFS document), but considering + * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as + * equivalently requesting no 64-bit operations + */ +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 +# undef _LARGEFILE64_SOURCE +#endif + +#ifndef Z_HAVE_UNISTD_H +# ifdef __WATCOMC__ +# define Z_HAVE_UNISTD_H +# endif +#endif +#ifndef Z_HAVE_UNISTD_H +# if defined(_LARGEFILE64_SOURCE) && !defined(_WIN32) +# define Z_HAVE_UNISTD_H +# endif +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif +# endif +#endif + +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#ifndef z_off_t +# define z_off_t long +#endif + +#if !defined(_WIN32) && defined(Z_LARGE64) +# define z_off64_t off64_t +#else +# if defined(_WIN32) && !defined(__GNUC__) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) + #pragma map(deflateInit_,"DEIN") + #pragma map(deflateInit2_,"DEIN2") + #pragma map(deflateEnd,"DEEND") + #pragma map(deflateBound,"DEBND") + #pragma map(inflateInit_,"ININ") + #pragma map(inflateInit2_,"ININ2") + #pragma map(inflateEnd,"INEND") + #pragma map(inflateSync,"INSY") + #pragma map(inflateSetDictionary,"INSEDI") + #pragma map(compressBound,"CMBND") + #pragma map(inflate_table,"INTABL") + #pragma map(inflate_fast,"INFA") + #pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ From 24af0a67ea045d6a7ffa2015bd6784f8eae0fe3a Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 26 Nov 2024 11:21:42 -0800 Subject: [PATCH 49/55] testing --- cpp/lib/Authenticator.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 8bf6aabec5..a785d38c8c 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -202,7 +202,15 @@ extern "C" { AuthenticatorType auth_type = getAuthenticatorType(conn->authenticator); try { - delete static_cast(conn->auth_object); + /*delete static_cast(conn->auth_object);*/ + if (AUTH_JWT == auth_type) + { + delete static_cast(conn->auth_object); + } + if (AUTH_OKTA == auth_type) + { + delete static_cast(conn->auth_object); + } } catch (...) { From c5be6123cdfaf88d1fd3a6c884855b48cd1325c9 Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 26 Nov 2024 11:40:10 -0800 Subject: [PATCH 50/55] testing --- cpp/lib/Authenticator.cpp | 16 +++++++--------- lib/connection.c | 6 ------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index a785d38c8c..86585ceeaf 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -146,6 +146,12 @@ extern "C" { snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); + if (AUTH_OAUTH == getAuthenticatorType(conn->authenticator)) + { + snowflake_cJSON_AddStringToObject(body, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); + snowflake_cJSON_AddStringToObject(body, "TOKEN", conn->oauth_token); + } + if (!conn || !conn->auth_object) { return; @@ -202,15 +208,7 @@ extern "C" { AuthenticatorType auth_type = getAuthenticatorType(conn->authenticator); try { - /*delete static_cast(conn->auth_object);*/ - if (AUTH_JWT == auth_type) - { - delete static_cast(conn->auth_object); - } - if (AUTH_OKTA == auth_type) - { - delete static_cast(conn->auth_object); - } + delete static_cast(conn->auth_object); } catch (...) { diff --git a/lib/connection.c b/lib/connection.c index cecc8285ac..1f3725172e 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -199,12 +199,6 @@ cJSON *STDCALL create_auth_json_body(SF_CONNECT *sf, // update authentication information to body auth_update_json_body(sf, body); - if (AUTH_OAUTH == getAuthenticatorType(sf->authenticator)) - { - snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); - snowflake_cJSON_AddStringToObject(data, "TOKEN", sf->oauth_token); - } - return body; } From b6138605b41c8996cf0e4c53879d0057067b6fb7 Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 26 Nov 2024 12:11:34 -0800 Subject: [PATCH 51/55] fix --- cpp/lib/Authenticator.cpp | 7 ------- lib/connection.c | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 86585ceeaf..93e2ee39f5 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -146,12 +146,6 @@ extern "C" { snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); snowflake_cJSON_DeleteItemFromObject(data, "TOKEN"); - if (AUTH_OAUTH == getAuthenticatorType(conn->authenticator)) - { - snowflake_cJSON_AddStringToObject(body, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); - snowflake_cJSON_AddStringToObject(body, "TOKEN", conn->oauth_token); - } - if (!conn || !conn->auth_object) { return; @@ -160,7 +154,6 @@ extern "C" { try { jsonObject_t picoBody; - cJSON* data = snowflake_cJSON_GetObjectItem(body, "data"); cJSONtoPicoJson(data, picoBody); static_cast(conn->auth_object)-> updateDataMap(picoBody); diff --git a/lib/connection.c b/lib/connection.c index 1f3725172e..2d14095d9a 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -199,6 +199,13 @@ cJSON *STDCALL create_auth_json_body(SF_CONNECT *sf, // update authentication information to body auth_update_json_body(sf, body); + if (AUTH_OAUTH == getAuthenticatorType(sf->authenticator)) + { + data = snowflake_cJSON_GetObjectItem(body, "data"); + snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); + snowflake_cJSON_AddStringToObject(data, "TOKEN", sf->oauth_token); + } + return body; } From 3bcbb91b49f7410ddea47b24718b4ab1bb49b29e Mon Sep 17 00:00:00 2001 From: John Yun Date: Tue, 26 Nov 2024 12:52:24 -0800 Subject: [PATCH 52/55] refactor --- lib/connection.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/connection.c b/lib/connection.c index 2d14095d9a..cecc8285ac 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -201,7 +201,6 @@ cJSON *STDCALL create_auth_json_body(SF_CONNECT *sf, if (AUTH_OAUTH == getAuthenticatorType(sf->authenticator)) { - data = snowflake_cJSON_GetObjectItem(body, "data"); snowflake_cJSON_AddStringToObject(data, "AUTHENTICATOR", SF_AUTHENTICATOR_OAUTH); snowflake_cJSON_AddStringToObject(data, "TOKEN", sf->oauth_token); } From 77278da7aece6c4d4e3f459ccf22f11cbd2c2bb9 Mon Sep 17 00:00:00 2001 From: John Yun Date: Mon, 2 Dec 2024 14:53:30 -0800 Subject: [PATCH 53/55] update --- cpp/lib/Authenticator.cpp | 117 +++++++++++++++++++----------------- cpp/lib/IAuth.cpp | 4 +- include/snowflake/IAuth.hpp | 12 ++-- 3 files changed, 71 insertions(+), 62 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 93e2ee39f5..1b6c1714c5 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -381,44 +381,49 @@ namespace Client SF_HEADER* httpExtraHeaders = sf_header_create(); std::string s_body = value(obj).serialize(); cJSON* resp_data = NULL; + try { + httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; + if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { + log_trace("sf", "IDPAuthenticator", + "post_curl_call", + "Failed to create the header for the request to get the token URL and the SSO URL"); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: failed to create the header", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + } - httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; - if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { - log_trace("sf", "IDPAuthenticator", - "post_curl_call", - "Failed to create the header for the request to get the token URL and the SSO URL"); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: failed to create the header", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); - goto cleanup; - } + if (!::curl_post_call(m_connection, curl, (char*)destination.c_str(), httpExtraHeaders, (char*)s_body.c_str(), + &resp_data, err, renewTimeout, maxRetryCount, m_retryTimeout, &elapsedTime, + &m_retriedCount, NULL, SF_BOOLEAN_TRUE)) + { + log_info("sf", "IDPAuthenticator", "post_curl_call", + "Fail to get authenticator info, response body=%s\n", + snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp_data, "data"))); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + } - if (!::curl_post_call(m_connection, curl, (char*)destination.c_str(), httpExtraHeaders, (char*)s_body.c_str(), - &resp_data, err, renewTimeout, maxRetryCount, m_retryTimeout, &elapsedTime, - &m_retriedCount, NULL, SF_BOOLEAN_TRUE)) - { - log_info("sf", "IDPAuthenticator", "post_curl_call", - "Fail to get authenticator info, response body=%s\n", - snowflake_cJSON_Print(snowflake_cJSON_GetObjectItem(resp_data, "data"))); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "SFConnectionFailed", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); - goto cleanup; + if (elapsedTime >= m_retryTimeout) + { + CXX_LOG_WARN("sf", "IDPAuthenticator", "post_curl_call", + "timeout reached: %d, elapsed time: %d", + m_retryTimeout, elapsedTime); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + } + cJSONtoPicoJson(resp_data, resp); } - - if (elapsedTime >= m_retryTimeout) - { - CXX_LOG_WARN("sf", "IDPAuthenticator", "post_curl_call", - "timeout reached: %d, elapsed time: %d", - m_retryTimeout, elapsedTime); - - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - AUTH_THROW(err); + catch (AuthException& e) { + // just to escape from the try block } - cleanup: + //Clean up resources m_retryTimeout -= elapsedTime; - cJSONtoPicoJson(resp_data, resp); sf_header_destroy(httpExtraHeaders); + free_curl_desc(curl_desc); snowflake_cJSON_Delete(resp_data); + if (err->error_code != SF_STATUS_SUCCESS) { + AUTH_THROW(err); + } } @@ -470,44 +475,48 @@ namespace Client // add headers for account and authentication SF_HEADER* httpExtraHeaders = sf_header_create(); - httpExtraHeaders->use_application_json_accept_type = SF_BOOLEAN_TRUE; - if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { - log_trace("sf", "AuthenticatorOKTA", - "get_curl_call", - "Failed to create the header for the request to get onetime token"); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: failed to create the header", SF_SQLSTATE_GENERAL_ERROR); - goto cleanup; - } - if (!http_perform(curl, GET_REQUEST_TYPE, (char*)destination.c_str(), httpExtraHeaders, NULL, NULL, raw_resp, + try { + if (!create_header(m_connection, httpExtraHeaders, &m_connection->error)) { + log_trace("sf", "AuthenticatorOKTA", + "get_curl_call", + "Failed to create the header for the request to get onetime token"); + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_GENERAL, "OktaConnectionFailed: failed to create the header", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + } + + if (!http_perform(curl, GET_REQUEST_TYPE, (char*)destination.c_str(), httpExtraHeaders, NULL, NULL, raw_resp, curlTimeout, SF_BOOLEAN_FALSE, err, m_connection->insecure_mode, m_connection->ocsp_fail_open, m_connection->retry_on_curle_couldnt_connect_count, renewTimeout, maxRetryCount, &elapsedTime, &m_retriedCount, NULL, SF_BOOLEAN_FALSE, - m_connection->proxy, m_connection->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE)) - { - //Fail to get the saml response. Retry. + m_connection->proxy, m_connection->no_proxy, SF_BOOLEAN_FALSE, SF_BOOLEAN_FALSE)) + { + //Fail to get the saml response. Retry. isRetry = true; - goto cleanup; - } + AUTH_THROW("retry"); + } - if (elapsedTime >= m_retryTimeout) - { - CXX_LOG_WARN("sf", "AuthenticatorOKTA", "get_curl_call", - "Fail to get SAML response, timeout reached: %d, elapsed time: %d", - m_retryTimeout, elapsedTime); + if (elapsedTime >= m_retryTimeout) + { + CXX_LOG_WARN("sf", "AuthenticatorOKTA", "get_curl_call", + "Fail to get SAML response, timeout reached: %d, elapsed time: %d", + m_retryTimeout, elapsedTime); - SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); - goto cleanup; + SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); + AUTH_THROW(err); + } + rawData = buf.buffer; + } + catch (AuthException& e) { + // just to escape from the try block } - rawData = buf.buffer; - cleanup: m_retryTimeout -= elapsedTime; sf_header_destroy(httpExtraHeaders); free_curl_desc(curl_desc); - SF_FREE(raw_resp); + delete raw_resp; if (isRetry) { RETRY_THROW(elapsedTime, m_retriedCount); } diff --git a/cpp/lib/IAuth.cpp b/cpp/lib/IAuth.cpp index c5f23aaf3a..f27441288e 100644 --- a/cpp/lib/IAuth.cpp +++ b/cpp/lib/IAuth.cpp @@ -22,7 +22,7 @@ namespace Client updateDataMap(dataMap); } - void IIDPAuthenticator::getIDPInfo() + void IDPAuthenticator::getIDPInfo() { jsonObject_t dataMap; SFURL connectURL = getServerURLSync().path("/session/authenticator-request"); @@ -43,7 +43,7 @@ namespace Client ssoURLStr = data["ssoUrl"].get(); } - SFURL IIDPAuthenticator::getServerURLSync() + SFURL IDPAuthenticator::getServerURLSync() { SFURL url = SFURL().scheme(m_protocol) .host(m_host) diff --git a/include/snowflake/IAuth.hpp b/include/snowflake/IAuth.hpp index 737e2b9352..5fdfcfe235 100644 --- a/include/snowflake/IAuth.hpp +++ b/include/snowflake/IAuth.hpp @@ -56,25 +56,25 @@ namespace IAuth }; - class IIDPAuthenticator + class IDPAuthenticator { public: - IIDPAuthenticator() + IDPAuthenticator() {}; - virtual ~IIDPAuthenticator() + virtual ~IDPAuthenticator() {}; void getIDPInfo(); virtual SFURL getServerURLSync(); - protected: /* * Get IdpInfo for OKTA and SAML 2.0 application */ virtual void curl_post_call(SFURL& url, const jsonObject_t& body, jsonObject_t& resp) = 0; virtual void curl_get_call(SFURL& url, jsonObject_t& resp, bool parseJSON, std::string& raw_data) = 0; - + + protected: std::string tokenURLStr; std::string ssoURLStr; //For EXTERNALBROSER in the future @@ -91,7 +91,7 @@ namespace IAuth std::string m_protocol; }; - class IAuthenticatorOKTA : public IIDPAuthenticator, public IAuthenticator + class IAuthenticatorOKTA : public IDPAuthenticator, public IAuthenticator { public: IAuthenticatorOKTA() {}; From 38f061950ed763c4d50b2c27d57f8adddc934441 Mon Sep 17 00:00:00 2001 From: John Yun Date: Mon, 2 Dec 2024 16:20:33 -0800 Subject: [PATCH 54/55] move urlSamePrefix function to snowflake util --- cpp/lib/Authenticator.cpp | 4 ++-- cpp/lib/IAuth.cpp | 4 ++-- cpp/lib/SnowflakeUtil.cpp | 21 +++++++++++++++++++++ cpp/util/SFURL.cpp | 20 -------------------- include/snowflake/SFURL.hpp | 9 --------- lib/snowflake_util.h | 10 ++++++++++ 6 files changed, 35 insertions(+), 33 deletions(-) diff --git a/cpp/lib/Authenticator.cpp b/cpp/lib/Authenticator.cpp index 1b6c1714c5..9a6146f647 100644 --- a/cpp/lib/Authenticator.cpp +++ b/cpp/lib/Authenticator.cpp @@ -468,7 +468,7 @@ namespace Client SF_ERROR_STRUCT *err = &m_connection->error; - NON_JSON_RESP* raw_resp = new NON_JSON_RESP; + NON_JSON_RESP* raw_resp = (NON_JSON_RESP*) SF_MALLOC(sizeof(NON_JSON_RESP)); raw_resp->write_callback = non_json_resp_write_callback; RAW_JSON_BUFFER buf = { NULL,0 }; raw_resp->buffer = (void*)&buf; @@ -516,7 +516,7 @@ namespace Client m_retryTimeout -= elapsedTime; sf_header_destroy(httpExtraHeaders); free_curl_desc(curl_desc); - delete raw_resp; + SF_FREE(raw_resp); if (isRetry) { RETRY_THROW(elapsedTime, m_retriedCount); } diff --git a/cpp/lib/IAuth.cpp b/cpp/lib/IAuth.cpp index f27441288e..c8e48e1f4e 100644 --- a/cpp/lib/IAuth.cpp +++ b/cpp/lib/IAuth.cpp @@ -58,7 +58,7 @@ namespace Client getIDPInfo(); // 2. verify ssoUrl and tokenUrl contains same prefix - if (!SFURL::urlHasSamePrefix(tokenURLStr, m_authenticator)) + if (!urlHasSamePrefix(tokenURLStr, m_authenticator)) { CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", "The specified authenticator is not supported, " @@ -126,7 +126,7 @@ namespace Client std::string post_back_url = extractPostBackUrlFromSamlResponse(m_samlResponse); std::string server_url = getServerURLSync().toString(); if ((!m_disableSamlUrlCheck) && - (!SFURL::urlHasSamePrefix(post_back_url, server_url))) + (!urlHasSamePrefix(post_back_url, server_url))) { CXX_LOG_ERROR("sf", "AuthenticatorOKTA", "authenticate", "The specified authenticator and destination URL in " diff --git a/cpp/lib/SnowflakeUtil.cpp b/cpp/lib/SnowflakeUtil.cpp index 7bd8c1a5fa..e5684c8c6b 100644 --- a/cpp/lib/SnowflakeUtil.cpp +++ b/cpp/lib/SnowflakeUtil.cpp @@ -1,4 +1,5 @@ #include "../../lib/snowflake_util.h" +#include "../include/snowflake/SFURL.hpp" namespace Snowflake { @@ -28,6 +29,26 @@ namespace Client picojson::parse(v, str); picojson = v.get(); } + + bool urlHasSamePrefix(std::string url1, std::string url2) + { + SFURL parsed_url1 = SFURL::parse(url1); + SFURL parsed_url2 = SFURL::parse(url2); + + if (parsed_url1.port() == "" && parsed_url1.scheme() == "https") + { + parsed_url1.port("443"); + } + + if (parsed_url2.port() == "" && parsed_url2.scheme() == "https") + { + parsed_url2.port("443"); + } + + return parsed_url1.scheme() == parsed_url2.scheme() && + parsed_url1.host() == parsed_url2.host() && + parsed_url1.port() == parsed_url2.port(); + } } } } \ No newline at end of file diff --git a/cpp/util/SFURL.cpp b/cpp/util/SFURL.cpp index 9d0a15ff22..23b75f7273 100644 --- a/cpp/util/SFURL.cpp +++ b/cpp/util/SFURL.cpp @@ -429,25 +429,5 @@ std::string SFURL::toString() m_cacheValid = true; return m_cacheURL; } - -bool SFURL::urlHasSamePrefix(std::string url1, std::string url2) -{ - SFURL parsed_url1 = parse(url1); - SFURL parsed_url2 = parse(url2); - - if (parsed_url1.port() == "" && parsed_url1.scheme() == "https") - { - parsed_url1.port("443"); - } - - if (parsed_url2.port() == "" && parsed_url2.scheme() == "https") - { - parsed_url2.port("443"); - } - - return parsed_url1.scheme() == parsed_url2.scheme() && - parsed_url1.host() == parsed_url2.host() && - parsed_url1.port() == parsed_url2.port(); -} } } diff --git a/include/snowflake/SFURL.hpp b/include/snowflake/SFURL.hpp index 9f81dcb0fd..a61c764748 100644 --- a/include/snowflake/SFURL.hpp +++ b/include/snowflake/SFURL.hpp @@ -422,15 +422,6 @@ class SFURL return m_proxy; } - /** - * Verify that if two urls has same prefix (protocl + host + port) - * @param url1 - * @param url2 - * - * @return true if same prefix otherwise false - */ - static bool urlHasSamePrefix(std::string url1, std::string url2); - private: /** diff --git a/lib/snowflake_util.h b/lib/snowflake_util.h index e0a5d11cc4..c6c9025dc7 100644 --- a/lib/snowflake_util.h +++ b/lib/snowflake_util.h @@ -26,6 +26,16 @@ extern "C" { */ void strToPicoJson(jsonObject_t& picojson, std::string& str); + + /** + * Verify that if two urls has same prefix (protocl + host + port) + * @param url1 + * @param url2 + * + * @return true if same prefix otherwise false + */ + bool urlHasSamePrefix(std::string url1, std::string url2); + #ifdef __cplusplus } #endif From 1f178ece1649ddc25579219dcb3c61c0c50475a7 Mon Sep 17 00:00:00 2001 From: John Yun Date: Mon, 2 Dec 2024 16:22:35 -0800 Subject: [PATCH 55/55] remove testing from unit_sfurl --- tests/test_unit_sfurl.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_unit_sfurl.cpp b/tests/test_unit_sfurl.cpp index cf234a2f85..762d3f7280 100644 --- a/tests/test_unit_sfurl.cpp +++ b/tests/test_unit_sfurl.cpp @@ -140,11 +140,6 @@ void test_error_parse(void ** unused) REQUIRE_THROWS(SFURL::parse("http://github.com:80ad")); } -void test_url_has_same_prefix(void** unused) -{ - assert_true(SFURL::urlHasSamePrefix("https://okta.snowflake.com", "https://okta.snowflake.com/request_path")); -} - static int gr_setup(void **unused) { initialize_test(SF_BOOLEAN_FALSE); @@ -157,7 +152,6 @@ int main(void) { cmocka_unit_test(test_parse_authority), cmocka_unit_test(test_construct), cmocka_unit_test(test_error_parse), - cmocka_unit_test(test_url_has_same_prefix), }; int ret = cmocka_run_group_tests(tests, gr_setup, NULL); return ret;