-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bf71bb3
commit 1f37bf8
Showing
2 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/* | ||
* Copyright (c) 2024 Snowflake Computing, Inc. All rights reserved. | ||
*/ | ||
|
||
#include <string> | ||
#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<jsonObject_t>(); | ||
tokenURLStr = data["tokenUrl"].get<std::string>(); | ||
ssoURLStr = data["ssoUrl"].get<std::string>(); | ||
} | ||
|
||
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<std::string>(); | ||
if (oneTimeToken.empty()) { | ||
oneTimeToken = respData["cookieToken"].get<std::string>(); | ||
} | ||
// 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("<form"); | ||
std::size_t post_back_start = html.find("action=\"", form_start); | ||
post_back_start += 8; | ||
std::size_t post_back_end = html.find("\"", post_back_start); | ||
|
||
std::string post_back_url = html.substr(post_back_start, | ||
post_back_end - post_back_start); | ||
CXX_LOG_TRACE("sf", "AuthenticatorOKTA", | ||
"extractPostBackUrlFromSamlResponse", | ||
"Post back url before unescape: %s", post_back_url.c_str()); | ||
char unescaped_url[200]; | ||
decode_html_entities_utf8(unescaped_url, post_back_url.c_str()); | ||
CXX_LOG_TRACE("sf", "AuthenticatorOKTA", | ||
"extractPostBackUrlFromSamlResponse", | ||
"Post back url after unescape: %s", unescaped_url); | ||
return std::string(unescaped_url); | ||
} | ||
} // namespace IAuth | ||
} // namespace Client | ||
} // namespace Snowflake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
* Copyright (c) 2018-2019 Snowflake Computing, Inc. All rights reserved. | ||
*/ | ||
|
||
#ifndef SNOWFLAKECLIENT_IAUTH_HPP | ||
#define SNOWFLAKECLIENT_IAUTH_HPP | ||
|
||
#include <string> | ||
#include <snowflake/SFURL.hpp> | ||
#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 |