-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SNOW-715551: Support authentication with OKTA #750
base: master
Are you sure you want to change the base?
Conversation
sfc-gh-ext-simba-jy
commented
Oct 8, 2024
•
edited
Loading
edited
- Implemented the okta authencation
- Added the disable saml URL check option
CMakeLists.txt
Outdated
@@ -155,6 +155,8 @@ set (SOURCE_FILES_PUT_GET | |||
cpp/StorageClientFactory.hpp | |||
cpp/StorageClientFactory.cpp | |||
cpp/RemoteStorageRequestOutcome.hpp | |||
cpp/entities.cpp | |||
cpp/entities.hpp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is copied from ODBC I think better to make ODBC to reuse this later. Can we move entities.hpp to include/snowflake or move the function declaration into an existing header file there?
lib/http_perform.c
Outdated
if ((retry) && (renew_timeout > 0) && | ||
((time(NULL) - elapsedRetryTime) >= renew_timeout)) { | ||
if (((retry) && (renew_timeout > 0) && | ||
((time(NULL) - elapsedRetryTime) >= renew_timeout)) || renew_timeout < 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is somehow misleading, please put renew_timeout < 0
in a new line, retry in a new line, and others in the same line. I think the condition here is incorrectly, renew should not be triggered if retry is false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if expression is too long. How about?
SF_BOOL renew_timeout_reached = (renew_timeout > 0) && ((time(NULL) - elapsedRetryTime) >= renew_timeout)
SF_BOOL renew_timeout_disabled = renew_timeout < 0
if ((retry && renew_timeout_reached) || renew_timeout_disabled)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied it @sfc-gh-jszczerbinski. Thank you for your suggestion.
lib/http_perform.c
Outdated
if ((retry) && (renew_timeout > 0) && | ||
((time(NULL) - elapsedRetryTime) >= renew_timeout)) { | ||
if (((retry) && (renew_timeout > 0) && | ||
((time(NULL) - elapsedRetryTime) >= renew_timeout)) || renew_timeout < 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if expression is too long. How about?
SF_BOOL renew_timeout_reached = (renew_timeout > 0) && ((time(NULL) - elapsedRetryTime) >= renew_timeout)
SF_BOOL renew_timeout_disabled = renew_timeout < 0
if ((retry && renew_timeout_reached) || renew_timeout_disabled)
cpp/lib/Authenticator.cpp
Outdated
@@ -73,6 +80,11 @@ extern "C" { | |||
conn->auth_object = static_cast<Snowflake::Client::IAuthenticator*>( | |||
new Snowflake::Client::AuthenticatorJWT(conn)); | |||
} | |||
if (AUTH_OKTA == auth_type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switch or else if?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
snowflake_cJSON_AddItemToObject(body, "data", data); | ||
} | ||
|
||
snowflake_cJSON_DeleteItemFromObject(data, "AUTHENTICATOR"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auth_update_json_body is a C wrapper for updateDataMap in C++. AUTH_OAUTH is an exception since it's quite simple so we don't have authenticator implementation for it in C++. For others we should fully rely on updateDataMap in case any common logic here might not apply.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this to connection.c
cpp/lib/Authenticator.cpp
Outdated
@@ -194,6 +212,10 @@ extern "C" { | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think better to use
if (conn->auth_object)
{
delete static_castSnowflake::Client::IAuthenticator*(conn->auth_object);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is moving in the right direction. We need to improve some things before the merge:
- Please rethink the inheritance structure. I don't think we need a complex one for this change
- Please avoid using exceptions (https://google.github.io/styleguide/cppguide.html#Exceptions) and goto, it complicates the flow and makes it hard to reason about resource lifetimes.
- Consider moving the code which handles SAML auth to separate file. Currently it dominates files that are containing implementations of other auth methods.
- In C++ we use RAII to managed lifetimes of resources. When using resource from C API consider using unique_ptr with custom deleter (https://stackoverflow.com/questions/19053351/how-do-i-use-a-custom-deleter-with-a-stdunique-ptr-member)
include/snowflake/IAuth.hpp
Outdated
}; | ||
|
||
|
||
class IIDPAuthenticator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this class necessary? It is named like it should be an interface, but it has member variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this class is more close to an abstract class. I will summarize the reason of this in the ticket.
include/snowflake/IAuth.hpp
Outdated
std::string m_protocol; | ||
}; | ||
|
||
class IAuthenticatorOKTA : public IIDPAuthenticator, public IAuthenticator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multiple inheritance looks like an issue with underlying design. Can you get rid of it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIDPAuthenticator is for the HTTP call to get IDP info for Okta and ExternalBrowser in the future. IAuthenticator is the original Interface class for all authenticators in the libsnowflakeclient.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense, but also makes the code hard to read. Generally in C++ multiple inheritance is not advised and composition is preferred over implementation inheritance (https://google.github.io/styleguide/cppguide.html#Inheritance). Can we make IDPAuthenticator (perhaps could be a better name) a member of AuthenticatorOKTA instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try to redesign this based on your feedback.
include/snowflake/SFURL.hpp
Outdated
* | ||
* @return true if same prefix otherwise false | ||
*/ | ||
static bool urlHasSamePrefix(std::string url1, std::string url2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to compare urls here? Maybe we should implement function like this instead.
bool hasSamePrefix(SFURL other);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is from ODBC and I try to use the same function without any changes. Actually, this is not a good place for this function. I will try to move this function elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this function to SNOWFLAKE_UTIL
"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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a macro for throwing an exception?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also please avoid using exceptions. Based on our internal discussions we should try to follow google c++ style guide: https://google.github.io/styleguide/cppguide.html#Exceptions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this error throwing is to escape from the authentication block and go to auth_update_json_body or auth_renew_json_body. We have used this in JWT authentication as well.
cpp/lib/Authenticator.cpp
Outdated
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is dead code, we never reach clean up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored this with try .. catch
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are leaking curl descriptors right now. Also you are using C API, but C++ API for curl descriptors are also available
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
m_retryTimeout -= elapsedTime; | ||
sf_header_destroy(httpExtraHeaders); | ||
free_curl_desc(curl_desc); | ||
SF_FREE(raw_resp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We allocate with new and deallocate with SF_FREE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed it as using SF_MALLOC instead of new when the NON_JSON_RESP pointer is created.
cleanup: | ||
m_retryTimeout -= elapsedTime; | ||
sf_header_destroy(httpExtraHeaders); | ||
free_curl_desc(curl_desc); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we free curl_desc here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, it is unclear to me. I use free_curl_desc here because this is the end of the function. Don't you think that this is reasonable?
cpp/lib/Authenticator.cpp
Outdated
m_retryTimeout, elapsedTime); | ||
|
||
SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR); | ||
goto cleanup; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't use goto in C++ code. Use RAII and if you need to manage resource from C API use unique_ptr (https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr constructors (3) and (4))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use the exception instead of goto. In my opinion, using unique_ptr is inappropriate for this because I should move the pointer to call the function and get the pointer back from the HTTP perform.
The c++ implementation in libsnowflakeclient are mainly for reusing in ODBC, which is using exception for error handling. That's from the SimbaEngineSDK framework and unlikely that could be changed. Keep using exception would make it easier to reuse it in ODBC, especially when the implementation is originally from ODBC. |
As we decide to follow Google c++ style, I will create a new PR instead of refactoring the code on the current PR. I will close this PR after I create a new one. |
Closed this PR. I made a new one. #799 |