Unit test Connection
Boost asio provides a test stream object that we can use to begin unit
testing the connection object. This patchset uses it to re-enable
some simple http1.1 tests. There's some features that have snuck into
the connection class that aren't compatible with a stream (like ip
address getting), so unfortunately we do need the connection class to
be aware if it's in test mode, but that tradeoff seems worthwhile.
Tested: Unit test pass.
Change-Id: Id8b1f8866582b58502dbafe6139f841bf64b8ef3
Signed-off-by: Ed Tanous <edtanous@google.com>
diff --git a/http/app.hpp b/http/app.hpp
index d9c88b9..1a7af83 100644
--- a/http/app.hpp
+++ b/http/app.hpp
@@ -10,10 +10,8 @@
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
-#ifdef BMCWEB_ENABLE_SSL
#include <boost/asio/ssl/context.hpp>
#include <boost/beast/ssl/ssl_stream.hpp>
-#endif
#include <chrono>
#include <cstdint>
@@ -29,19 +27,13 @@
namespace crow
{
-#ifdef BMCWEB_ENABLE_SSL
-using ssl_context_t = boost::asio::ssl::context;
-#endif
class App
{
public:
-#ifdef BMCWEB_ENABLE_SSL
using ssl_socket_t = boost::beast::ssl_stream<boost::asio::ip::tcp::socket>;
using ssl_server_t = Server<App, ssl_socket_t>;
-#else
using socket_t = boost::asio::ip::tcp::socket;
using server_t = Server<App, socket_t>;
-#endif
explicit App(std::shared_ptr<boost::asio::io_context> ioIn =
std::make_shared<boost::asio::io_context>()) :
@@ -94,12 +86,6 @@
return *this;
}
- App& bindaddr(std::string bindaddr)
- {
- bindaddrStr = std::move(bindaddr);
- return *this;
- }
-
void validate()
{
router.validate();
@@ -111,8 +97,8 @@
#ifdef BMCWEB_ENABLE_SSL
if (-1 == socketFd)
{
- sslServer = std::make_unique<ssl_server_t>(
- this, bindaddrStr, portUint, sslContext, io);
+ sslServer = std::make_unique<ssl_server_t>(this, portUint,
+ sslContext, io);
}
else
{
@@ -125,8 +111,7 @@
if (-1 == socketFd)
{
- server = std::make_unique<server_t>(this, bindaddrStr, portUint,
- nullptr, io);
+ server = std::make_unique<server_t>(this, portUint, nullptr, io);
}
else
{
@@ -158,7 +143,6 @@
return router.getRoutes(parent);
}
-#ifdef BMCWEB_ENABLE_SSL
App& ssl(std::shared_ptr<boost::asio::ssl::context>&& ctx)
{
sslContext = std::move(ctx);
@@ -167,21 +151,7 @@
return *this;
}
- std::shared_ptr<ssl_context_t> sslContext = nullptr;
-
-#else
- template <typename T>
- App& ssl(T&&)
- {
- // We can't call .ssl() member function unless BMCWEB_ENABLE_SSL is
- // defined.
- static_assert(
- // make static_assert dependent to T; always false
- std::is_base_of<T, void>::value,
- "Define BMCWEB_ENABLE_SSL to enable ssl support.");
- return *this;
- }
-#endif
+ std::shared_ptr<boost::asio::ssl::context> sslContext = nullptr;
boost::asio::io_context& ioContext()
{
@@ -195,7 +165,6 @@
#else
uint16_t portUint = 80;
#endif
- std::string bindaddrStr = "0.0.0.0";
int socketFd = -1;
Router router;
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index 1ec80ae..c64d511 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -17,6 +17,7 @@
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/steady_timer.hpp>
+#include <boost/beast/_experimental/test/stream.hpp>
#include <boost/beast/core/buffers_generator.hpp>
#include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/beast/http/error.hpp>
@@ -44,6 +45,14 @@
constexpr uint32_t httpHeaderLimit = 8192;
+template <typename>
+struct IsTls : std::false_type
+{};
+
+template <typename T>
+struct IsTls<boost::beast::ssl_stream<T>> : std::true_type
+{};
+
template <typename Adaptor, typename Handler>
class Connection :
public std::enable_shared_from_this<Connection<Adaptor, Handler>>
@@ -112,31 +121,34 @@
void prepareMutualTls()
{
- std::error_code error;
- std::filesystem::path caPath(ensuressl::trustStorePath);
- auto caAvailable = !std::filesystem::is_empty(caPath, error);
- caAvailable = caAvailable && !error;
- if (caAvailable && persistent_data::SessionStore::getInstance()
- .getAuthMethodsConfig()
- .tls)
+ if constexpr (IsTls<Adaptor>::value)
{
- adaptor.set_verify_mode(boost::asio::ssl::verify_peer);
- std::string id = "bmcweb";
-
- const char* cStr = id.c_str();
- // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
- const auto* idC = reinterpret_cast<const unsigned char*>(cStr);
- int ret = SSL_set_session_id_context(
- adaptor.native_handle(), idC,
- static_cast<unsigned int>(id.length()));
- if (ret == 0)
+ std::error_code error;
+ std::filesystem::path caPath(ensuressl::trustStorePath);
+ auto caAvailable = !std::filesystem::is_empty(caPath, error);
+ caAvailable = caAvailable && !error;
+ if (caAvailable && persistent_data::SessionStore::getInstance()
+ .getAuthMethodsConfig()
+ .tls)
{
- BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
- }
- }
+ adaptor.set_verify_mode(boost::asio::ssl::verify_peer);
+ std::string id = "bmcweb";
- adaptor.set_verify_callback(
- std::bind_front(&self_type::tlsVerifyCallback, this));
+ const char* cStr = id.c_str();
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ const auto* idC = reinterpret_cast<const unsigned char*>(cStr);
+ int ret = SSL_set_session_id_context(
+ adaptor.native_handle(), idC,
+ static_cast<unsigned int>(id.length()));
+ if (ret == 0)
+ {
+ BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
+ }
+ }
+
+ adaptor.set_verify_callback(
+ std::bind_front(&self_type::tlsVerifyCallback, this));
+ }
}
Adaptor& socket()
@@ -157,9 +169,7 @@
// TODO(ed) Abstract this to a more clever class with the idea of an
// asynchronous "start"
- if constexpr (std::is_same_v<Adaptor,
- boost::beast::ssl_stream<
- boost::asio::ip::tcp::socket>>)
+ if constexpr (IsTls<Adaptor>::value)
{
adaptor.async_handshake(boost::asio::ssl::stream_base::server,
[this, self(shared_from_this())](
@@ -252,20 +262,23 @@
return;
}
keepAlive = thisReq.keepAlive();
-#ifndef BMCWEB_INSECURE_DISABLE_AUTHX
- if (!crow::authentication::isOnAllowlist(req->url().path(),
- req->method()) &&
- thisReq.session == nullptr)
+ if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
{
- BMCWEB_LOG_WARNING("Authentication failed");
- forward_unauthorized::sendUnauthorized(
- req->url().encoded_path(),
- req->getHeaderValue("X-Requested-With"),
- req->getHeaderValue("Accept"), res);
- completeRequest(res);
- return;
- }
+#ifndef BMCWEB_INSECURE_DISABLE_AUTHX
+ if (!crow::authentication::isOnAllowlist(req->url().path(),
+ req->method()) &&
+ thisReq.session == nullptr)
+ {
+ BMCWEB_LOG_WARNING("Authentication failed");
+ forward_unauthorized::sendUnauthorized(
+ req->url().encoded_path(),
+ req->getHeaderValue("X-Requested-With"),
+ req->getHeaderValue("Accept"), res);
+ completeRequest(res);
+ return;
+ }
#endif // BMCWEB_INSECURE_DISABLE_AUTHX
+ }
auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
BMCWEB_LOG_DEBUG("Setting completion handler");
asyncResp->res.setCompleteRequestHandler(
@@ -309,12 +322,14 @@
bool isAlive()
{
- if constexpr (std::is_same_v<Adaptor,
- boost::beast::ssl_stream<
- boost::asio::ip::tcp::socket>>)
+ if constexpr (IsTls<Adaptor>::value)
{
return adaptor.next_layer().is_open();
}
+ else if constexpr (std::is_same_v<Adaptor, boost::beast::test::stream>)
+ {
+ return true;
+ }
else
{
return adaptor.is_open();
@@ -322,9 +337,7 @@
}
void close()
{
- if constexpr (std::is_same_v<Adaptor,
- boost::beast::ssl_stream<
- boost::asio::ip::tcp::socket>>)
+ if constexpr (IsTls<Adaptor>::value)
{
adaptor.next_layer().close();
if (mtlsSession != nullptr)
@@ -384,18 +397,22 @@
{
boost::system::error_code ec;
BMCWEB_LOG_DEBUG("Fetch the client IP address");
- boost::asio::ip::tcp::endpoint endpoint =
- boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
- if (ec)
+ if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
{
- // If remote endpoint fails keep going. "ClientOriginIPAddress"
- // will be empty.
- BMCWEB_LOG_ERROR("Failed to get the client's IP Address. ec : {}",
- ec);
- return ec;
+ boost::asio::ip::tcp::endpoint endpoint =
+ boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
+
+ if (ec)
+ {
+ // If remote endpoint fails keep going. "ClientOriginIPAddress"
+ // will be empty.
+ BMCWEB_LOG_ERROR(
+ "Failed to get the client's IP Address. ec : {}", ec);
+ return ec;
+ }
+ ip = endpoint.address();
}
- ip = endpoint.address();
return ec;
}
@@ -457,27 +474,31 @@
{
BMCWEB_LOG_DEBUG("Unable to get client IP");
}
-#ifndef BMCWEB_INSECURE_DISABLE_AUTHX
- boost::beast::http::verb method = parser->get().method();
- userSession = crow::authentication::authenticate(
- ip, res, method, parser->get().base(), mtlsSession);
-
- bool loggedIn = userSession != nullptr;
- if (!loggedIn)
+ if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
{
- const boost::optional<uint64_t> contentLength =
- parser->content_length();
- if (contentLength && *contentLength > loggedOutPostBodyLimit)
- {
- BMCWEB_LOG_DEBUG("Content length greater than limit {}",
- *contentLength);
- close();
- return;
- }
+#ifndef BMCWEB_INSECURE_DISABLE_AUTHX
+ boost::beast::http::verb method = parser->get().method();
+ userSession = crow::authentication::authenticate(
+ ip, res, method, parser->get().base(), mtlsSession);
- BMCWEB_LOG_DEBUG("Starting quick deadline");
- }
+ bool loggedIn = userSession != nullptr;
+ if (!loggedIn)
+ {
+ const boost::optional<uint64_t> contentLength =
+ parser->content_length();
+ if (contentLength &&
+ *contentLength > loggedOutPostBodyLimit)
+ {
+ BMCWEB_LOG_DEBUG("Content length greater than limit {}",
+ *contentLength);
+ close();
+ return;
+ }
+
+ BMCWEB_LOG_DEBUG("Starting quick deadline");
+ }
#endif // BMCWEB_INSECURE_DISABLE_AUTHX
+ }
if (parser->is_done())
{
diff --git a/http/http_server.hpp b/http/http_server.hpp
index b290ad7..2a6bd9f 100644
--- a/http/http_server.hpp
+++ b/http/http_server.hpp
@@ -38,14 +38,14 @@
adaptorCtx(std::move(adaptorCtxIn))
{}
- Server(Handler* handlerIn, const std::string& bindaddr, uint16_t port,
+ Server(Handler* handlerIn, uint16_t port,
const std::shared_ptr<boost::asio::ssl::context>& adaptorCtxIn,
const std::shared_ptr<boost::asio::io_context>& io =
std::make_shared<boost::asio::io_context>()) :
Server(handlerIn,
std::make_unique<boost::asio::ip::tcp::acceptor>(
*io, boost::asio::ip::tcp::endpoint(
- boost::asio::ip::make_address(bindaddr), port)),
+ boost::asio::ip::make_address("0.0.0.0"), port)),
adaptorCtxIn, io)
{}