Skip to content
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

Open
wants to merge 73 commits into
base: master
Choose a base branch
from

Conversation

sfc-gh-ext-simba-jy
Copy link
Collaborator

@sfc-gh-ext-simba-jy sfc-gh-ext-simba-jy commented Oct 8, 2024

  • 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
Copy link
Collaborator

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?

if ((retry) && (renew_timeout > 0) &&
((time(NULL) - elapsedRetryTime) >= renew_timeout)) {
if (((retry) && (renew_timeout > 0) &&
((time(NULL) - elapsedRetryTime) >= renew_timeout)) || renew_timeout < 0) {
Copy link
Collaborator

@sfc-gh-ext-simba-hx sfc-gh-ext-simba-hx Oct 11, 2024

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.

Copy link
Contributor

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) 

Copy link
Collaborator Author

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.

if ((retry) && (renew_timeout > 0) &&
((time(NULL) - elapsedRetryTime) >= renew_timeout)) {
if (((retry) && (renew_timeout > 0) &&
((time(NULL) - elapsedRetryTime) >= renew_timeout)) || renew_timeout < 0) {
Copy link
Contributor

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) 

lib/connection.c Show resolved Hide resolved
cpp/lib/Authenticator.hpp Outdated Show resolved Hide resolved
@@ -73,6 +80,11 @@ extern "C" {
conn->auth_object = static_cast<Snowflake::Client::IAuthenticator*>(
new Snowflake::Client::AuthenticatorJWT(conn));
}
if (AUTH_OKTA == auth_type)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch or else if?

Copy link
Collaborator Author

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");
Copy link
Collaborator

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.

Copy link
Collaborator Author

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

@@ -194,6 +212,10 @@ extern "C" {
{
Copy link
Collaborator

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);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

@sfc-gh-ext-simba-jy sfc-gh-ext-simba-jy marked this pull request as ready for review November 26, 2024 20:52
@sfc-gh-ext-simba-jy sfc-gh-ext-simba-jy requested a review from a team as a code owner November 26, 2024 20:52
Copy link
Contributor

@sfc-gh-jszczerbinski sfc-gh-jszczerbinski left a 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:

};


class IIDPAuthenticator
Copy link
Contributor

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.

Copy link
Collaborator Author

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.

std::string m_protocol;
};

class IAuthenticatorOKTA : public IIDPAuthenticator, public IAuthenticator
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

Copy link
Contributor

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?

Copy link
Collaborator Author

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.

*
* @return true if same prefix otherwise false
*/
static bool urlHasSamePrefix(std::string url1, std::string url2);
Copy link
Contributor

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);

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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");
Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Collaborator Author

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.

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;
Copy link
Contributor

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

Copy link
Collaborator Author

@sfc-gh-ext-simba-jy sfc-gh-ext-simba-jy Nov 29, 2024

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);
Copy link
Contributor

@sfc-gh-jszczerbinski sfc-gh-jszczerbinski Nov 28, 2024

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

Copy link
Collaborator Author

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);
Copy link
Contributor

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

Copy link
Collaborator Author

@sfc-gh-ext-simba-jy sfc-gh-ext-simba-jy Dec 2, 2024

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);
Copy link
Contributor

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?

Copy link
Collaborator Author

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?

m_retryTimeout, elapsedTime);

SET_SNOWFLAKE_ERROR(err, SF_STATUS_ERROR_REQUEST_TIMEOUT, "OktaConnectionFailed: timeout reached", SF_SQLSTATE_GENERAL_ERROR);
goto cleanup;
Copy link
Contributor

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))

Copy link
Collaborator Author

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.

@sfc-gh-ext-simba-hx
Copy link
Collaborator

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.
Or maybe we could consider not to reuse it in ODBC? That would make the logic (including the inheritance structure) a lot simpler.

@sfc-gh-ext-simba-jy
Copy link
Collaborator Author

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.

@sfc-gh-ext-simba-jy
Copy link
Collaborator Author

Closed this PR. I made a new one. #799

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants