From d7d0b4c239b5dcabd1e7bce6cd0ad5d21b4dc643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Korina=20=C5=A0imi=C4=8Devi=C4=87?= Date: Fri, 19 Jan 2024 09:26:54 +0100 Subject: [PATCH] Add re authentication tests Summary: related to T12015 Reviewers: ivica Reviewed By: ivica Subscribers: miljen, iljazovic Differential Revision: https://repo.mireo.local/D27436 --- include/async_mqtt5/error.hpp | 6 +- include/async_mqtt5/impl/re_auth_op.hpp | 5 +- test/include/test_common/packet_util.hpp | 12 +- .../test_common/test_authenticators.hpp | 76 +++++++ test/integration/re_authentication.cpp | 207 ++++++++++++++++++ test/unit/connect_op.cpp | 106 +++------ 6 files changed, 337 insertions(+), 75 deletions(-) create mode 100644 test/include/test_common/test_authenticators.hpp create mode 100644 test/integration/re_authentication.cpp diff --git a/include/async_mqtt5/error.hpp b/include/async_mqtt5/error.hpp index d99b208..8ee9e85 100644 --- a/include/async_mqtt5/error.hpp +++ b/include/async_mqtt5/error.hpp @@ -517,7 +517,7 @@ template < > std::pair valid_codes() { static reason_code valid_codes[] = { - success, continue_authentication + success, continue_authentication, reauthenticate }; static size_t len = sizeof(valid_codes) / sizeof(reason_code); return std::make_pair(valid_codes, len); @@ -594,13 +594,13 @@ template < std::pair valid_codes() { static reason_code valid_codes[] = { normal_disconnection, unspecified_error, - malformed_packet,protocol_error, + malformed_packet, protocol_error, implementation_specific_error, not_authorized, server_busy, server_shutting_down, keep_alive_timeout, session_taken_over, topic_filter_invalid, topic_name_invalid, receive_maximum_exceeded, topic_alias_invalid, - packet_too_large,message_rate_too_high, + packet_too_large, message_rate_too_high, quota_exceeded, administrative_action, payload_format_invalid, retain_not_supported, qos_not_supported, use_another_server, diff --git a/include/async_mqtt5/impl/re_auth_op.hpp b/include/async_mqtt5/impl/re_auth_op.hpp index c2c9246..1528591 100644 --- a/include/async_mqtt5/impl/re_auth_op.hpp +++ b/include/async_mqtt5/impl/re_auth_op.hpp @@ -45,6 +45,9 @@ public: } void perform() { + if (_auth.method().empty()) + return; + auto auth_step = auth_step_e::client_initial; return _auth.async_auth( auth_step, "", @@ -55,7 +58,7 @@ public: void perform(decoders::auth_message auth_message) { if (_auth.method().empty()) return on_auth_fail( - "Unexpected AUTH received.", + "Unexpected AUTH received", disconnect_rc_e::protocol_error ); diff --git a/test/include/test_common/packet_util.hpp b/test/include/test_common/packet_util.hpp index b49d25a..e02a5ec 100644 --- a/test/include/test_common/packet_util.hpp +++ b/test/include/test_common/packet_util.hpp @@ -82,7 +82,7 @@ inline std::string to_readable_packet(std::string packet) { std::ostringstream stream; - if (code == control_code_e::connack || code == control_code_e::disconnect) { + if (code == control_code_e::connack || code == control_code_e::auth) { stream << code_to_str(code); return stream.str(); } @@ -99,6 +99,16 @@ inline std::string to_readable_packet(std::string packet) { stream << " props: " << to_readable_props(props); return stream.str(); } + + if (code == control_code_e::disconnect) { + auto disconnect = decoders::decode_disconnect(*varlen, begin); + auto& [rc, props] = *disconnect; + stream << code_to_str(code); + stream << " rc: " << int(rc); + stream << " reason string: " << props[prop::reason_string].value_or(""); + return stream.str(); + } + if (code == control_code_e::publish) { auto publish = decoders::decode_publish( control_byte, *varlen, begin diff --git a/test/include/test_common/test_authenticators.hpp b/test/include/test_common/test_authenticators.hpp new file mode 100644 index 0000000..a920315 --- /dev/null +++ b/test/include/test_common/test_authenticators.hpp @@ -0,0 +1,76 @@ +#ifndef ASYNC_MQTT5_TEST_TEST_AUTHENTICATORS_HPP +#define ASYNC_MQTT5_TEST_TEST_AUTHENTICATORS_HPP + +#include +#include + +#include + +#include + +namespace async_mqtt5::test { + +namespace asio = boost::asio; + +struct test_authenticator { + test_authenticator() = default; + + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using error_code = boost::system::error_code; + using Signature = void (error_code, std::string); + + auto initiate = [](auto handler, auth_step_e, std::string) { + asio::dispatch( + asio::prepend(std::move(handler), error_code {}, "") + ); + }; + + return asio::async_initiate( + initiate, token, step, std::move(data) + ); + } + + std::string_view method() const { + return "method"; + } +}; + +template +struct fail_test_authenticator { + fail_test_authenticator() = default; + + template + decltype(auto) async_auth( + auth_step_e step, std::string data, + CompletionToken&& token + ) { + using error_code = boost::system::error_code; + using Signature = void (error_code, std::string); + + auto initiate = [](auto handler, auth_step_e step, std::string) { + error_code ec; + if (fail_on_step == step) + ec = asio::error::no_recovery; + + asio::dispatch( + asio::prepend(std::move(handler), ec, "") + ); + }; + + return asio::async_initiate( + initiate, token, step, std::move(data) + ); + } + + std::string_view method() const { + return "method"; + } +}; + +} // end namespace async_mqtt5::test + +#endif // ASYNC_MQTT5_TEST_TEST_AUTHENTICATORS_HPP diff --git a/test/integration/re_authentication.cpp b/test/integration/re_authentication.cpp new file mode 100644 index 0000000..f1d7d5a --- /dev/null +++ b/test/integration/re_authentication.cpp @@ -0,0 +1,207 @@ +#include + +#include +#include + +#include + +#include "test_common/message_exchange.hpp" +#include "test_common/test_authenticators.hpp" +#include "test_common/test_stream.hpp" + +using namespace async_mqtt5; + +BOOST_AUTO_TEST_SUITE(re_auth_op/*, *boost::unit_test::disabled()*/) + +struct shared_test_data { + error_code success {}; + error_code fail = asio::error::not_connected; + + const std::string connect = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, init_connect_props(), std::nullopt + ); + const std::string connack = encoders::encode_connack( + true, reason_codes::success.value(), {} + ); + + const std::string auth_challenge = encoders::encode_auth( + reason_codes::reauthenticate.value(), init_auth_props() + ); + const std::string auth_response = encoders::encode_auth( + reason_codes::continue_authentication.value(), init_auth_props() + ); + const std::string auth_success = encoders::encode_auth( + reason_codes::success.value(), init_auth_props() + ); + + connect_props init_connect_props() { + connect_props cprops; + cprops[prop::authentication_method] = "method"; + cprops[prop::authentication_data] = ""; + return cprops; + } + + auth_props init_auth_props() { + auth_props aprops; + aprops[prop::authentication_method] = "method"; + aprops[prop::authentication_data] = ""; + return aprops; + } +}; + +using test::after; +using namespace std::chrono; + +template +void run_test( + test::msg_exchange broker_side, Authenticator&& authenticator = {} +) { + asio::io_context ioc; + auto executor = ioc.get_executor(); + auto& broker = asio::make_service( + ioc, executor, std::move(broker_side) + ); + + using client_type = mqtt_client; + client_type c(executor, ""); + c.brokers("127.0.0.1"); + + if constexpr (!std::is_same_v) + c.authenticator(std::forward(authenticator)); + + c.async_run(asio::detached); + + asio::steady_timer timer(c.get_executor()); + // wait until the connection is established + timer.expires_after(20ms); + timer.async_wait([&](error_code) { + c.re_authenticate(); + + timer.expires_after(150ms); + timer.async_wait([&c](error_code) { c.cancel(); }); + }); + + ioc.run(); + BOOST_CHECK(broker.received_all_expected()); +} + +disconnect_props dprops_with_reason_string(std::string_view reason_string) { + disconnect_props dprops; + dprops[prop::reason_string] = reason_string; + return dprops; +} + +BOOST_FIXTURE_TEST_CASE(successful_re_auth, shared_test_data) { + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(auth_success, after(4ms)); + + run_test(std::move(broker_side), test::test_authenticator()); +} + +BOOST_FIXTURE_TEST_CASE(successful_re_auth_multi_step, shared_test_data) { + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(auth_response, after(4ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(auth_success, after(4ms)); + + run_test(std::move(broker_side), test::test_authenticator()); +} + +BOOST_FIXTURE_TEST_CASE(malformed_auth_rc, shared_test_data) { + auto disconnect = encoders::encode_disconnect( + reason_codes::malformed_packet.value(), + dprops_with_reason_string("Malformed AUTH received: bad reason code") + ); + auto malformed_auth = encoders::encode_auth( + reason_codes::administrative_action.value(), init_auth_props() + ); + + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(malformed_auth, after(4ms)) + .expect(disconnect); + + run_test(std::move(broker_side), test::test_authenticator()); +} + +BOOST_FIXTURE_TEST_CASE(mismatched_auth_method, shared_test_data) { + auth_props aprops; + aprops[prop::authentication_method] = "wrong method"; + + auto mismatched_auth_response = encoders::encode_auth( + reason_codes::continue_authentication.value(), aprops + ); + + auto disconnect = encoders::encode_disconnect( + reason_codes::protocol_error.value(), + dprops_with_reason_string("Malformed AUTH received: wrong authentication method") + ); + + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(mismatched_auth_response, after(4ms)) + .expect(disconnect); + + run_test(std::move(broker_side), test::test_authenticator()); +} + +BOOST_FIXTURE_TEST_CASE(async_auth_fail, shared_test_data) { + auto disconnect = encoders::encode_disconnect( + reason_codes::unspecified_error.value(), + dprops_with_reason_string("Re-authentication: authentication fail") + ); + + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)) + .expect(auth_challenge) + .complete_with(success, after(2ms)) + .reply_with(auth_response, after(4ms)) + .expect(disconnect); + + run_test( + std::move(broker_side), + test::fail_test_authenticator() + ); +} + +BOOST_FIXTURE_TEST_CASE(re_auth_without_authenticator, shared_test_data) { + auto connect_no_auth = encoders::encode_connect( + "", std::nullopt, std::nullopt, 10, false, {}, std::nullopt + ); + + test::msg_exchange broker_side; + broker_side + .expect(connect_no_auth) + .complete_with(success, after(0ms)) + .reply_with(connack, after(1ms)); + + run_test(std::move(broker_side)); +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/test/unit/connect_op.cpp b/test/unit/connect_op.cpp index ac6dda0..759a071 100644 --- a/test/unit/connect_op.cpp +++ b/test/unit/connect_op.cpp @@ -11,6 +11,7 @@ #include +#include "test_common/test_authenticators.hpp" #include "test_common/test_stream.hpp" using namespace async_mqtt5; @@ -30,7 +31,7 @@ struct shared_test_data { }; using test::after; -using std::chrono_literals::operator ""ms; +using namespace std::chrono; void run_unit_test( detail::mqtt_ctx mqtt_ctx, test::msg_exchange broker_side, @@ -58,7 +59,7 @@ void run_unit_test( stream, std::move(handler), mqtt_ctx ).perform(eps, ap); - ioc.run_for(std::chrono::seconds(1)); + ioc.run_for(1s); BOOST_CHECK_EQUAL(handlers_called, expected_handlers_called); BOOST_CHECK(broker.received_all_expected()); } @@ -215,6 +216,7 @@ BOOST_FIXTURE_TEST_CASE(receive_unexpected_auth, shared_test_data) { auth_props aprops; aprops[prop::authentication_method] = "method"; aprops[prop::authentication_data] = "data"; + auto auth = encoders::encode_auth(uint8_t(0x19), aprops); test::msg_exchange broker_side; @@ -232,33 +234,6 @@ BOOST_FIXTURE_TEST_CASE(receive_unexpected_auth, shared_test_data) { // enhanced auth -struct test_authenticator { - test_authenticator() = default; - - template - decltype(auto) async_auth( - auth_step_e step, std::string data, - CompletionToken&& token - ) { - using error_code = boost::system::error_code; - using Signature = void (error_code, std::string); - - auto initiate = [](auto handler, auth_step_e, std::string) { - asio::dispatch( - asio::prepend(std::move(handler), error_code {}, "") - ); - }; - - return asio::async_initiate( - initiate, token, step, std::move(data) - ); - } - - std::string_view method() const { - return "method"; - } -}; - struct shared_test_auth_data { error_code success {}; error_code fail = asio::error::not_connected; @@ -308,12 +283,35 @@ BOOST_FIXTURE_TEST_CASE(successful_auth, shared_test_auth_data) { detail::mqtt_ctx mqtt_ctx; mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test_authenticator(); + mqtt_ctx.authenticator = test::test_authenticator(); + run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); +} + +BOOST_FIXTURE_TEST_CASE(successful_auth_multi_step, shared_test_auth_data) { + test::msg_exchange broker_side; + broker_side + .expect(connect) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(3ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(auth_challenge, after(4ms)) + .expect(auth_response) + .complete_with(success, after(2ms)) + .reply_with(connack, after(4ms)); + + auto handler = [&](error_code ec) { + BOOST_CHECK(ec == success); + }; + + detail::mqtt_ctx mqtt_ctx; + mqtt_ctx.co_props = init_connect_props(); + mqtt_ctx.authenticator = test::test_authenticator(); run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } BOOST_FIXTURE_TEST_CASE(malformed_auth_rc, shared_test_auth_data) { - const std::string malformed_auth_challenge = encoders::encode_auth( + auto malformed_auth_challenge = encoders::encode_auth( reason_codes::server_busy.value(), init_auth_props() ); @@ -329,7 +327,7 @@ BOOST_FIXTURE_TEST_CASE(malformed_auth_rc, shared_test_auth_data) { detail::mqtt_ctx mqtt_ctx; mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test_authenticator(); + mqtt_ctx.authenticator = test::test_authenticator(); run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } @@ -337,7 +335,7 @@ BOOST_FIXTURE_TEST_CASE(mismatched_auth_method, shared_test_auth_data) { auth_props aprops; aprops[prop::authentication_method] = "wrong method"; - const std::string mismatched_auth_challenge = encoders::encode_auth( + auto mismatched_auth_challenge = encoders::encode_auth( reason_codes::continue_authentication.value(), aprops ); @@ -353,7 +351,7 @@ BOOST_FIXTURE_TEST_CASE(mismatched_auth_method, shared_test_auth_data) { detail::mqtt_ctx mqtt_ctx; mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test_authenticator(); + mqtt_ctx.authenticator = test::test_authenticator(); run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } @@ -372,42 +370,10 @@ BOOST_FIXTURE_TEST_CASE(fail_to_send_auth, shared_test_auth_data) { detail::mqtt_ctx mqtt_ctx; mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = test_authenticator(); + mqtt_ctx.authenticator = test::test_authenticator(); run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } -template -struct fail_test_authenticator { - fail_test_authenticator() = default; - - template - decltype(auto) async_auth( - auth_step_e step, std::string data, - CompletionToken&& token - ) { - using error_code = boost::system::error_code; - using Signature = void (error_code, std::string); - - auto initiate = [](auto handler, auth_step_e step, std::string) { - error_code ec; - if (fail_on_step == step) - ec = asio::error::no_recovery; - - asio::dispatch( - asio::prepend(std::move(handler), ec, "") - ); - }; - - return asio::async_initiate( - initiate, token, step, std::move(data) - ); - } - - std::string_view method() const { - return "method"; - } -}; - BOOST_FIXTURE_TEST_CASE(auth_step_client_initial_fail, shared_test_auth_data) { test::msg_exchange broker_side; @@ -417,7 +383,7 @@ BOOST_FIXTURE_TEST_CASE(auth_step_client_initial_fail, shared_test_auth_data) { detail::mqtt_ctx mqtt_ctx; mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = fail_test_authenticator(); + mqtt_ctx.authenticator = test::fail_test_authenticator(); run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } @@ -434,7 +400,7 @@ BOOST_FIXTURE_TEST_CASE(auth_step_server_challenge_fail, shared_test_auth_data) detail::mqtt_ctx mqtt_ctx; mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = fail_test_authenticator(); + mqtt_ctx.authenticator = test::fail_test_authenticator(); run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); } @@ -454,7 +420,7 @@ BOOST_FIXTURE_TEST_CASE(auth_step_server_final_fail, shared_test_auth_data) { detail::mqtt_ctx mqtt_ctx; mqtt_ctx.co_props = init_connect_props(); - mqtt_ctx.authenticator = fail_test_authenticator(); + mqtt_ctx.authenticator = test::fail_test_authenticator(); run_unit_test(std::move(mqtt_ctx), std::move(broker_side), std::move(handler)); }