From f907edb4479afa78a55a1da2e52bd61470a505ab Mon Sep 17 00:00:00 2001 From: John Yun <140559986+sfc-gh-ext-simba-jy@users.noreply.github.com> Date: Thu, 12 Sep 2024 09:19:14 -0700 Subject: [PATCH] SNOW-1526628 implemented passcode MFA authentication (#739) --- lib/connection.c | 14 ++++ tests/CMakeLists.txt | 3 + tests/test_mfa_connect.c | 132 +++++++++++++++++++++++++++++++++++++ tests/test_unit_mfa_auth.c | 78 ++++++++++++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 tests/test_mfa_connect.c create mode 100644 tests/test_unit_mfa_auth.c diff --git a/lib/connection.c b/lib/connection.c index d900d7a6d9..5176607223 100644 --- a/lib/connection.c +++ b/lib/connection.c @@ -172,6 +172,20 @@ cJSON *STDCALL create_auth_json_body(SF_CONNECT *sf, // Add password if one exists if (sf->password && *(sf->password)) { snowflake_cJSON_AddStringToObject(data, "PASSWORD", sf->password); + + if (sf->passcode_in_password) { + snowflake_cJSON_AddStringToObject(data, "EXT_AUTHN_DUO_METHOD", "passcode"); + snowflake_cJSON_AddBoolToObject(data, "passcodeInPassword", SF_BOOLEAN_TRUE); + } + else if (sf->passcode && *(sf->passcode)) + { + snowflake_cJSON_AddStringToObject(data, "EXT_AUTHN_DUO_METHOD", "passcode"); + snowflake_cJSON_AddStringToObject(data, "PASSCODE", sf->passcode); + } + else + { + snowflake_cJSON_AddStringToObject(data, "EXT_AUTHN_DUO_METHOD", "push"); + } } snowflake_cJSON_AddItemToObject(data, "CLIENT_ENVIRONMENT", client_env); snowflake_cJSON_AddItemToObject(data, "SESSION_PARAMETERS", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 65795ebcd5..20b09d62eb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,10 +41,13 @@ SET(TESTS_C test_get_query_result_response test_get_describe_only_query_result test_stmt_functions + test_unit_mfa_auth # FEATURE_INCREASED_MAX_LOB_SIZE_IN_MEMORY is internal switch # 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_mfa_connect ) SET(TESTS_CXX diff --git a/tests/test_mfa_connect.c b/tests/test_mfa_connect.c new file mode 100644 index 0000000000..fbc69e1f1d --- /dev/null +++ b/tests/test_mfa_connect.c @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2018-2024 Snowflake Computing, Inc. All rights reserved. + */ + +#include "utils/test_setup.h" + +void test_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); + } + + 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_connect_with_duo_passcode(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, *passcode; + 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); + } + passcode = getenv("SNOWFLAKE_TEST_PASSCODE"); + if (passcode) + { + snowflake_set_attribute(sf, SF_CON_PASSCODE, passcode); + } + else { + dump_error(&(sf->error)); + } + + 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_connect_with_duo_passcodeInPassword(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")); + sf_bool passcode_in_password = SF_BOOLEAN_TRUE; + snowflake_set_attribute(sf, SF_CON_PASSCODE_IN_PASSWORD, &passcode_in_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); +} + +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), + //Need to run this testing separately. + //cmocka_unit_test(test_connect_with_duo_passcodeInPassword), + }; + int ret = cmocka_run_group_tests(tests, NULL, NULL); + snowflake_global_term(); + return ret; +} diff --git a/tests/test_unit_mfa_auth.c b/tests/test_unit_mfa_auth.c new file mode 100644 index 0000000000..9ad4b98fae --- /dev/null +++ b/tests/test_unit_mfa_auth.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018-2024 Snowflake Computing, Inc. All rights reserved. + */ + +#include +#include "utils/test_setup.h" +#include "connection.h" +#include "memory.h" + +/** + * Test json body is properly updated. + */ +void test_json_data_in_MFA_Auth(void **unused) +{ + SF_CONNECT* sf = (SF_CONNECT*)SF_CALLOC(1, sizeof(SF_CONNECT)); + sf->account = "testaccount"; + sf->host = "testaccount.snowflakecomputing.com"; + sf->user = "testuser"; + sf->password = "testpassword"; + sf->authenticator = SF_AUTHENTICATOR_DEFAULT; + sf->application_name = SF_API_NAME; + sf->application_version = SF_API_VERSION; + + cJSON *body = create_auth_json_body( + sf, + sf->application, + sf->application_name, + sf->application_version, + sf->timezone, + sf->autocommit); + cJSON* data = snowflake_cJSON_GetObjectItem(body, "data"); + + assert_string_equal(snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(data, "EXT_AUTHN_DUO_METHOD")), "push"); + + sf->passcode = "123456"; + body = create_auth_json_body( + sf, + sf->application, + sf->application_name, + sf->application_version, + sf->timezone, + sf->autocommit); + data = snowflake_cJSON_GetObjectItem(body, "data"); + + assert_string_equal(snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(data, "EXT_AUTHN_DUO_METHOD")), "passcode"); + assert_string_equal(snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(data, "passcode")), "123456"); + + sf->passcode_in_password = SF_BOOLEAN_TRUE; + + body = create_auth_json_body( + sf, + sf->application, + sf->application_name, + sf->application_version, + sf->timezone, + sf->autocommit); + data = snowflake_cJSON_GetObjectItem(body, "data"); + + assert_string_equal(snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(data, "EXT_AUTHN_DUO_METHOD")), "passcode"); + assert_int_equal(snowflake_cJSON_GetStringValue(snowflake_cJSON_GetObjectItem(data, "passcode")), NULL); + + sf_bool passcodeInPassword; + json_copy_bool(&passcodeInPassword, data, "passcodeInPassword"); + assert_int_equal(passcodeInPassword, SF_BOOLEAN_TRUE); + + SF_FREE(sf); +} + +int main(void) +{ + initialize_test(SF_BOOLEAN_FALSE); + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_json_data_in_MFA_Auth), + }; + int ret = cmocka_run_group_tests(tests, NULL, NULL); + snowflake_global_term(); + return ret; +}