This is an automated email from the ASF dual-hosted git repository.
isapego pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 747c2ef34c IGNITE-19722 ODBC 3.0: Implement basic authentication
(#2885)
747c2ef34c is described below
commit 747c2ef34c0a400d500e5b21eb0f84fa615153ab
Author: Dmitriy Zabotlin <[email protected]>
AuthorDate: Tue Nov 28 14:07:47 2023 +0200
IGNITE-19722 ODBC 3.0: Implement basic authentication (#2885)
Co-authored-by: dzabotlin <[email protected]>
---
.../cpp/ignite/odbc/config/configuration.cpp | 8 ++
.../cpp/ignite/odbc/config/configuration.h | 36 +++++++
modules/platforms/cpp/ignite/odbc/odbc.cpp | 11 +-
.../platforms/cpp/ignite/odbc/sql_connection.cpp | 9 +-
.../tests/client-test/basic_authenticator_test.cpp | 88 +---------------
.../cpp/tests/client-test/ignite_runner_suite.h | 2 +-
.../cpp/tests/odbc-test/connection_test.cpp | 86 +++++++++++++++-
.../cpp/tests/odbc-test/odbc_connection.h | 18 +++-
modules/platforms/cpp/tests/odbc-test/odbc_suite.h | 4 +-
.../cpp/tests/odbc-test/odbc_test_utils.h | 2 +-
.../platforms/cpp/tests/test-common/CMakeLists.txt | 1 +
.../cpp/tests/test-common/basic_auth_test_suite.h | 112 +++++++++++++++++++++
12 files changed, 278 insertions(+), 99 deletions(-)
diff --git a/modules/platforms/cpp/ignite/odbc/config/configuration.cpp
b/modules/platforms/cpp/ignite/odbc/config/configuration.cpp
index 9159f181ba..4468e8c7d0 100644
--- a/modules/platforms/cpp/ignite/odbc/config/configuration.cpp
+++ b/modules/platforms/cpp/ignite/odbc/config/configuration.cpp
@@ -39,6 +39,12 @@ static inline const std::string address{"address"};
/** Key for address attribute. */
static inline const std::string schema{"schema"};
+/** Key for authentication identity. */
+static inline const std::string identity{"identity"};
+
+/** Key for authentication secret. */
+static inline const std::string secret{"secret"};
+
} // namespace key
namespace ignite {
@@ -85,6 +91,8 @@ void configuration::from_config_map(const config_map
&config_params) {
}
try_get_string_param(m_schema, config_params, key::schema);
+ try_get_string_param(m_auth_identity, config_params, key::identity);
+ try_get_string_param(m_auth_secret, config_params, key::secret);
}
} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/config/configuration.h
b/modules/platforms/cpp/ignite/odbc/config/configuration.h
index 52a6ac83ae..86c6a79c6c 100644
--- a/modules/platforms/cpp/ignite/odbc/config/configuration.h
+++ b/modules/platforms/cpp/ignite/odbc/config/configuration.h
@@ -53,6 +53,12 @@ public:
// Default.
configuration() = default;
+ // With auth.
+ configuration(std::string identity, std::string secret)
+ : m_auth_identity{std::move(identity), true}
+ , m_auth_secret{std::move(secret), true}
+ {}
+
/**
* Get addresses.
*
@@ -74,6 +80,27 @@ public:
*/
[[nodiscard]] const value_with_default<std::string> &get_schema() const {
return m_schema; }
+ /**
+ * Get authentication type.
+ *
+ * @return Authentication type.
+ */
+ [[nodiscard]] const std::string &get_auth_type() const { return TYPE; };
+
+ /**
+ * Get identity.
+ *
+ * @return Identity.
+ */
+ [[nodiscard]] const value_with_default<std::string> &get_auth_identity()
const { return m_auth_identity; };
+
+ /**
+ * Get secret.
+ *
+ * @return Secret.
+ */
+ [[nodiscard]] const value_with_default<std::string> &get_auth_secret()
const { return m_auth_secret; };
+
/**
* Fill from configuration params.
*
@@ -83,6 +110,9 @@ public:
void from_config_map(const config_map &config_params);
private:
+ /** Type constant. */
+ inline static const std::string TYPE{"basic"};
+
/** Request and response page size. */
value_with_default<std::int32_t> m_page_size{default_value::page_size,
false};
@@ -91,6 +121,12 @@ private:
/** Schema. */
value_with_default<std::string> m_schema{default_value::schema, false};
+
+ /** Identity. */
+ value_with_default<std::string> m_auth_identity{"", false};
+
+ /** Secret. */
+ value_with_default<std::string> m_auth_secret{"", false};
};
} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/odbc.cpp
b/modules/platforms/cpp/ignite/odbc/odbc.cpp
index 82e4fc7ec5..28aa37b453 100644
--- a/modules/platforms/cpp/ignite/odbc/odbc.cpp
+++ b/modules/platforms/cpp/ignite/odbc/odbc.cpp
@@ -256,10 +256,6 @@ SQLRETURN SQLDriverConnect(SQLHDBC conn, SQLHWND
windowHandle, SQLCHAR *inConnec
SQLRETURN SQLConnect(SQLHDBC conn, SQLCHAR *server_name, SQLSMALLINT
server_name_len, SQLCHAR *user_name,
SQLSMALLINT user_name_len, SQLCHAR *auth, SQLSMALLINT auth_len) {
- UNUSED_VALUE(user_name);
- UNUSED_VALUE(user_name_len);
- UNUSED_VALUE(auth);
- UNUSED_VALUE(auth_len);
LOG_MSG("SQLConnect called\n");
@@ -267,14 +263,15 @@ SQLRETURN SQLConnect(SQLHDBC conn, SQLCHAR *server_name,
SQLSMALLINT server_name
if (!connection)
return SQL_INVALID_HANDLE;
- configuration config;
-
std::string dsn = sql_string_to_string(server_name, server_name_len);
LOG_MSG("DSN: " << dsn);
// TODO: IGNITE-19210 Add DSN support
- connection->establish(config);
+ std::string auth_identity = sql_string_to_string(user_name, user_name_len);
+ std::string auth_secret = sql_string_to_string(auth, auth_len);
+
+ connection->establish({auth_identity, auth_secret});
return connection->get_diagnostic_records().get_return_code();
}
diff --git a/modules/platforms/cpp/ignite/odbc/sql_connection.cpp
b/modules/platforms/cpp/ignite/odbc/sql_connection.cpp
index 61ea0dcc03..d69f4a16d8 100644
--- a/modules/platforms/cpp/ignite/odbc/sql_connection.cpp
+++ b/modules/platforms/cpp/ignite/odbc/sql_connection.cpp
@@ -621,7 +621,14 @@ sql_result sql_connection::make_request_handshake() {
m_protocol_version = protocol::protocol_version::get_current();
try {
- std::vector<std::byte> message =
protocol::make_handshake_request(ODBC_CLIENT, m_protocol_version, {});
+ std::map<std::string, std::string> extensions;
+ if (!m_config.get_auth_identity().get_value().empty()) {
+ extensions.emplace("authn-type", m_config.get_auth_type());
+ extensions.emplace("authn-identity",
m_config.get_auth_identity().get_value());
+ extensions.emplace("authn-secret",
m_config.get_auth_secret().get_value());
+ }
+
+ std::vector<std::byte> message =
protocol::make_handshake_request(ODBC_CLIENT, m_protocol_version, extensions);
auto res = send_all(message.data(), message.size(), m_login_timeout);
if (res != operation_result::SUCCESS) {
diff --git
a/modules/platforms/cpp/tests/client-test/basic_authenticator_test.cpp
b/modules/platforms/cpp/tests/client-test/basic_authenticator_test.cpp
index 77b399c1d2..4c9e3506a7 100644
--- a/modules/platforms/cpp/tests/client-test/basic_authenticator_test.cpp
+++ b/modules/platforms/cpp/tests/client-test/basic_authenticator_test.cpp
@@ -15,102 +15,18 @@
* limitations under the License.
*/
-#include "ignite_runner_suite.h"
-
-#include <ignite/client/basic_authenticator.h>
-#include <ignite/client/ignite_client.h>
-#include <ignite/client/ignite_client_configuration.h>
+#include "tests/test-common/basic_auth_test_suite.h"
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
-#include <chrono>
-#include <thread>
-
using namespace ignite;
-/**
- * Test suite.
- */
-class basic_authenticator_test : public ignite_runner_suite {
-public:
- /** Correct username */
- inline static const std::string CORRECT_USERNAME{"user-1"};
-
- /** Correct password */
- inline static const std::string CORRECT_PASSWORD{"password-1"};
-
+struct basic_authenticator_test : public basic_auth_test_suite {
/**
* Tear down.
*/
static void TearDownTestSuite() { set_authentication_enabled(false); }
-
- /**
- * Get default configuration.
- *
- * @return Configuration.
- */
- static ignite_client_configuration get_configuration() {
- ignite_client_configuration cfg{get_node_addrs()};
- cfg.set_logger(get_logger());
-
- return cfg;
- }
-
- /**
- * Get configuration with basic auth enabled.
- *
- * @param user Username to use.
- * @param password Password to use.
- * @return Configuration.
- */
- static ignite_client_configuration get_configuration(std::string user,
std::string password) {
- ignite_client_configuration cfg{get_configuration()};
-
- auto authenticator =
std::make_shared<basic_authenticator>(std::move(user), std::move(password));
- cfg.set_authenticator(authenticator);
-
- return cfg;
- }
-
- /**
- * Get configuration with correct credentials.
- *
- * @return Configuration with correct credentials.
- */
- static ignite_client_configuration get_configuration_correct() {
- return get_configuration(CORRECT_USERNAME, CORRECT_PASSWORD);
- }
-
- /**
- * Change cluster authentication state.
- *
- * @param enable Authentication enabled.
- */
- static void set_authentication_enabled(bool enable) {
- if (m_auth_enabled == enable)
- return;
-
- ignite_client_configuration cfg = m_auth_enabled ?
get_configuration_correct() : get_configuration();
-
- try {
- auto client = ignite_client::start(cfg, std::chrono::seconds(30));
- auto nodes = client.get_cluster_nodes();
- client.get_compute().execute(nodes, {}, ENABLE_AUTHN_JOB, {enable
? 1 : 0});
- } catch (const ignite_error &) {
- // Ignore.
- // As a result of this call, the client may be disconnected from
the server due to authn config change.
- }
-
- // Wait for the server to apply the configuration change and drop the
client connection.
- std::this_thread::sleep_for(std::chrono::seconds(3));
-
- m_auth_enabled = enable;
- }
-
-private:
- /** Authentication enabled. */
- inline static bool m_auth_enabled{false};
};
TEST_F(basic_authenticator_test, disabled_on_server) {
diff --git a/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
b/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
index 09b8df6777..4efa2752bd 100644
--- a/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
+++ b/modules/platforms/cpp/tests/client-test/ignite_runner_suite.h
@@ -36,7 +36,7 @@ using namespace std::string_view_literals;
/**
* Test suite.
*/
-class ignite_runner_suite : public ::testing::Test {
+class ignite_runner_suite : public virtual ::testing::Test {
public:
static constexpr std::string_view TABLE_1 = "tbl1"sv;
static constexpr std::string_view TABLE_NAME_ALL_COLUMNS =
"tbl_all_columns"sv;
diff --git a/modules/platforms/cpp/tests/odbc-test/connection_test.cpp
b/modules/platforms/cpp/tests/odbc-test/connection_test.cpp
index 276267bad0..49f9a80597 100644
--- a/modules/platforms/cpp/tests/odbc-test/connection_test.cpp
+++ b/modules/platforms/cpp/tests/odbc-test/connection_test.cpp
@@ -17,6 +17,7 @@
#include "odbc_suite.h"
+#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include <string>
@@ -26,13 +27,96 @@ using namespace ignite;
/**
* Test suite.
*/
-class connection_test : public ignite::odbc_suite {};
+class connection_test : public odbc_suite, public basic_auth_test_suite {
+public:
+ /**
+ * Tear down.
+ */
+ static void TearDownTestSuite() { set_authentication_enabled(false); }
+
+ /**
+ * Get node addresses and user credentials to use for tests.
+ *
+ * @return Addresses and credentials.
+ */
+ static std::string get_auth_connection_string() {
+ return get_basic_connection_string() + "identity=" + CORRECT_USERNAME
+ ';' + "secret=" + CORRECT_PASSWORD
+ + ';';
+ }
+
+ /**
+ * Get node addresses and user credentials with incorrect identity to use
for tests.
+ *
+ * @return Addresses and credentials.
+ */
+ static std::string get_incorrect_identity_auth_connection_string() {
+ return get_basic_connection_string() + "identity=" +
INCORRECT_USERNAME + ';' + "secret=" + CORRECT_PASSWORD
+ + ';';
+ }
+
+ /**
+ * Get node addresses and user credentials with incorrect secret to use
for tests.
+ *
+ * @return Addresses and credentials.
+ */
+ static std::string get_incorrect_secret_auth_connection_string() {
+ return get_basic_connection_string() + "identity=" + CORRECT_USERNAME
+ ';' + "secret=" + INCORRECT_PASSWORD
+ + ';';
+ }
+
+ /**
+ * Get node addresses and user credentials with incorrect identity and
secret to use for tests.
+ *
+ * @return Addresses and credentials.
+ */
+ static std::string get_incorrect_auth_connection_string() {
+ return get_basic_connection_string() + "identity=" +
INCORRECT_USERNAME + ';' + "secret=" + INCORRECT_PASSWORD
+ + ';';
+ }
+};
TEST_F(connection_test, connection_success) {
+ set_authentication_enabled(false);
odbc_connect(get_basic_connection_string());
}
+TEST_F(connection_test, auth_connection_success) {
+ set_authentication_enabled(true);
+ EXPECT_NO_THROW(odbc_connect_throw(get_auth_connection_string()));
+}
+
+TEST_F(connection_test, auth_connection_disabled_on_server) {
+ set_authentication_enabled(false);
+ EXPECT_NO_THROW(odbc_connect_throw(get_auth_connection_string()));
+}
+
+TEST_F(connection_test, auth_connection_disabled_on_client) {
+ set_authentication_enabled(true);
+ EXPECT_THROW(
+ try { odbc_connect_throw(get_basic_connection_string()); } catch
(const ignite_error &e) {
+ EXPECT_THAT(e.what_str(), testing::HasSubstr("Authentication
failed"));
+ throw;
+ },
+ ignite_error);
+}
+
+TEST_F(connection_test, auth_connection_incorrect_creds) {
+ set_authentication_enabled(true);
+ auto test_conn_str = [this](const std::string &conn_str) {
+ EXPECT_THROW(
+ try { odbc_connect_throw(conn_str); } catch (const ignite_error
&e) {
+ EXPECT_THAT(e.what_str(), testing::HasSubstr("Authentication
failed"));
+ throw;
+ },
+ ignite_error);
+ };
+ test_conn_str(get_incorrect_identity_auth_connection_string());
+ test_conn_str(get_incorrect_secret_auth_connection_string());
+ test_conn_str(get_incorrect_auth_connection_string());
+}
+
TEST_F(connection_test, odbc3_supported) {
+ set_authentication_enabled(false);
// Allocate an environment handle
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_env);
diff --git a/modules/platforms/cpp/tests/odbc-test/odbc_connection.h
b/modules/platforms/cpp/tests/odbc-test/odbc_connection.h
index df952b2852..639129d07f 100644
--- a/modules/platforms/cpp/tests/odbc-test/odbc_connection.h
+++ b/modules/platforms/cpp/tests/odbc-test/odbc_connection.h
@@ -56,8 +56,24 @@ public:
* ODBC connect.
*
* @param connect_str Connect string.
+ * @throws ignite_error on connection failed.
*/
- void odbc_connect(std::string_view connect_str) {
ignite::odbc_connect(connect_str, m_env, m_conn, m_statement); }
+ void odbc_connect_throw(std::string_view connect_str) {
+ ignite::odbc_connect(connect_str, m_env, m_conn, m_statement);
+ }
+
+ /**
+ * ODBC connect.
+ *
+ * @param connect_str Connect string.
+ */
+ void odbc_connect(std::string_view connect_str) {
+ try {
+ odbc_connect_throw(connect_str);
+ } catch (const ignite_error &error) {
+ FAIL() << error.what();
+ }
+ }
/**
* Disconnect.
diff --git a/modules/platforms/cpp/tests/odbc-test/odbc_suite.h
b/modules/platforms/cpp/tests/odbc-test/odbc_suite.h
index 5043acaba1..11015c99b5 100644
--- a/modules/platforms/cpp/tests/odbc-test/odbc_suite.h
+++ b/modules/platforms/cpp/tests/odbc-test/odbc_suite.h
@@ -26,6 +26,8 @@
#include "odbc_test_utils.h"
#include "test_utils.h"
+#include "tests/test-common/basic_auth_test_suite.h"
+
#include <gtest/gtest.h>
#include <memory>
@@ -39,7 +41,7 @@ namespace ignite {
/**
* Test suite.
*/
-class odbc_suite : public ::testing::Test, public odbc_connection {
+class odbc_suite : public virtual ::testing::Test, public odbc_connection {
public:
static inline const std::string TABLE_1 = "tbl1";
static inline const std::string TABLE_NAME_ALL_COLUMNS = "tbl_all_columns";
diff --git a/modules/platforms/cpp/tests/odbc-test/odbc_test_utils.h
b/modules/platforms/cpp/tests/odbc-test/odbc_test_utils.h
index 1329b8548b..20cf9da5d2 100644
--- a/modules/platforms/cpp/tests/odbc-test/odbc_test_utils.h
+++ b/modules/platforms/cpp/tests/odbc-test/odbc_test_utils.h
@@ -167,7 +167,7 @@ inline void odbc_connect(std::string_view connect_str,
SQLHENV &env, SQLHDBC &co
out_str, sizeof(out_str), &out_str_len, SQL_DRIVER_COMPLETE);
if (!SQL_SUCCEEDED(ret)) {
- FAIL() << get_odbc_error_message(SQL_HANDLE_DBC, conn);
+ throw ignite_error(get_odbc_error_message(SQL_HANDLE_DBC, conn));
}
// Allocate a statement handle
diff --git a/modules/platforms/cpp/tests/test-common/CMakeLists.txt
b/modules/platforms/cpp/tests/test-common/CMakeLists.txt
index c3eb4c0451..d642058957 100644
--- a/modules/platforms/cpp/tests/test-common/CMakeLists.txt
+++ b/modules/platforms/cpp/tests/test-common/CMakeLists.txt
@@ -20,6 +20,7 @@ project(ignite-test-common)
set(TARGET ${PROJECT_NAME})
set(SOURCES
+ basic_auth_test_suite.h
cmd_process.h
ignite_runner.cpp ignite_runner.h
process.cpp
diff --git a/modules/platforms/cpp/tests/test-common/basic_auth_test_suite.h
b/modules/platforms/cpp/tests/test-common/basic_auth_test_suite.h
new file mode 100644
index 0000000000..6357010c08
--- /dev/null
+++ b/modules/platforms/cpp/tests/test-common/basic_auth_test_suite.h
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "tests/client-test/ignite_runner_suite.h"
+
+#include <ignite/client/basic_authenticator.h>
+#include <ignite/client/ignite_client.h>
+#include <ignite/client/ignite_client_configuration.h>
+
+#include <chrono>
+#include <thread>
+
+/**
+ * Test suite.
+ */
+class basic_auth_test_suite : public ignite::ignite_runner_suite {
+public:
+ /** Correct username */
+ inline static const std::string CORRECT_USERNAME{"user-1"};
+
+ /** Correct password */
+ inline static const std::string CORRECT_PASSWORD{"password-1"};
+
+ /** Correct username */
+ inline static const std::string INCORRECT_USERNAME{"root"};
+
+ /** Correct password */
+ inline static const std::string INCORRECT_PASSWORD{"123"};
+
+ /**
+ * Get default configuration.
+ *
+ * @return Configuration.
+ */
+ static ignite::ignite_client_configuration get_configuration() {
+ ignite::ignite_client_configuration cfg{get_node_addrs()};
+ cfg.set_logger(get_logger());
+
+ return cfg;
+ }
+
+ /**
+ * Get configuration with basic auth enabled.
+ *
+ * @param user Username to use.
+ * @param password Password to use.
+ * @return Configuration.
+ */
+ static ignite::ignite_client_configuration get_configuration(std::string
user, std::string password) {
+ ignite::ignite_client_configuration cfg{get_configuration()};
+
+ auto authenticator =
std::make_shared<ignite::basic_authenticator>(std::move(user),
std::move(password));
+ cfg.set_authenticator(authenticator);
+
+ return cfg;
+ }
+
+ /**
+ * Get configuration with correct credentials.
+ *
+ * @return Configuration with correct credentials.
+ */
+ static ignite::ignite_client_configuration get_configuration_correct() {
+ return get_configuration(CORRECT_USERNAME, CORRECT_PASSWORD);
+ }
+
+ /**
+ * Change cluster authentication state.
+ *
+ * @param enable Authentication enabled.
+ */
+ static void set_authentication_enabled(bool enable) {
+ if (m_auth_enabled == enable)
+ return;
+
+ ignite::ignite_client_configuration cfg = m_auth_enabled ?
get_configuration_correct() : get_configuration();
+
+ try {
+ auto client = ignite::ignite_client::start(cfg,
std::chrono::seconds(30));
+ auto nodes = client.get_cluster_nodes();
+ client.get_compute().execute(nodes, {}, ENABLE_AUTHN_JOB, {enable
? 1 : 0});
+ } catch (const ignite::ignite_error &) {
+ // Ignore.
+ // As a result of this call, the client may be disconnected from
the server due to authn config change.
+ }
+
+ // Wait for the server to apply the configuration change and drop the
client connection.
+ std::this_thread::sleep_for(std::chrono::seconds(3));
+
+ m_auth_enabled = enable;
+ }
+
+private:
+ /** Authentication enabled. */
+ inline static bool m_auth_enabled{false};
+};