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

Reply via email to