mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-30 16:05:51 +00:00
Add server/connection tests (RIPD-1336):
Migrate tests in uniport-test.js to cpp/jtx. Handle exceptions in WSClient and JSONRPClient constructors. Use shorter timeout for HTTP and WS Peers when client is localhost. Add missing call to start_timer in HTTP Peer. Add incomplete WS Upgrade request test to prove that server timeout is working.
This commit is contained in:
committed by
Nik Bougalis
parent
7ff243ade9
commit
8d83aa5c07
@@ -236,6 +236,8 @@
|
|||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\..\src\BeastConfig.h">
|
<ClInclude Include="..\..\src\BeastConfig.h">
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\src\beast\extras\beast\test\yield_to.hpp">
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp">
|
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp">
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\detail\const_container.hpp">
|
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\detail\const_container.hpp">
|
||||||
|
|||||||
@@ -25,6 +25,9 @@
|
|||||||
<Filter Include="beast\http\impl">
|
<Filter Include="beast\http\impl">
|
||||||
<UniqueIdentifier>{932F732F-F09E-5C50-C8A1-D62342CCAA1F}</UniqueIdentifier>
|
<UniqueIdentifier>{932F732F-F09E-5C50-C8A1-D62342CCAA1F}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
|
<Filter Include="beast\test">
|
||||||
|
<UniqueIdentifier>{0ED4CDBE-296D-2599-04B3-095BFD1668A4}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
<Filter Include="beast\unit_test">
|
<Filter Include="beast\unit_test">
|
||||||
<UniqueIdentifier>{2762284D-66E5-8B48-1F8E-67116DB1FC6B}</UniqueIdentifier>
|
<UniqueIdentifier>{2762284D-66E5-8B48-1F8E-67116DB1FC6B}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
@@ -543,6 +546,9 @@
|
|||||||
<ClInclude Include="..\..\src\BeastConfig.h">
|
<ClInclude Include="..\..\src\BeastConfig.h">
|
||||||
<Filter>.</Filter>
|
<Filter>.</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\src\beast\extras\beast\test\yield_to.hpp">
|
||||||
|
<Filter>beast\test</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp">
|
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp">
|
||||||
<Filter>beast\unit_test</Filter>
|
<Filter>beast\unit_test</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ protected:
|
|||||||
bufferSize = 4 * 1024,
|
bufferSize = 4 * 1024,
|
||||||
|
|
||||||
// Max seconds without completing a message
|
// Max seconds without completing a message
|
||||||
timeoutSeconds = 30
|
timeoutSeconds = 30,
|
||||||
|
timeoutSecondsLocal = 3 //used for localhost clients
|
||||||
};
|
};
|
||||||
|
|
||||||
struct buffer
|
struct buffer
|
||||||
@@ -277,7 +278,12 @@ BaseHTTPPeer<Handler, Impl>::
|
|||||||
start_timer()
|
start_timer()
|
||||||
{
|
{
|
||||||
error_code ec;
|
error_code ec;
|
||||||
timer_.expires_from_now(std::chrono::seconds(timeoutSeconds), ec);
|
timer_.expires_from_now(
|
||||||
|
std::chrono::seconds(
|
||||||
|
remote_address_.address().is_loopback() ?
|
||||||
|
timeoutSecondsLocal :
|
||||||
|
timeoutSeconds),
|
||||||
|
ec);
|
||||||
if(ec)
|
if(ec)
|
||||||
return fail(ec, "start_timer");
|
return fail(ec, "start_timer");
|
||||||
timer_.async_wait(strand_.wrap(std::bind(
|
timer_.async_wait(strand_.wrap(std::bind(
|
||||||
@@ -318,6 +324,7 @@ do_read(yield_context do_yield)
|
|||||||
{
|
{
|
||||||
complete_ = false;
|
complete_ = false;
|
||||||
error_code ec;
|
error_code ec;
|
||||||
|
start_timer();
|
||||||
beast::http::async_read(impl().stream_,
|
beast::http::async_read(impl().stream_,
|
||||||
read_buf_, message_, do_yield[ec]);
|
read_buf_, message_, do_yield[ec]);
|
||||||
// VFALCO What if the connection was closed?
|
// VFALCO What if the connection was closed?
|
||||||
|
|||||||
@@ -364,8 +364,11 @@ start_timer()
|
|||||||
{
|
{
|
||||||
// Max seconds without completing a message
|
// Max seconds without completing a message
|
||||||
static constexpr std::chrono::seconds timeout{30};
|
static constexpr std::chrono::seconds timeout{30};
|
||||||
|
static constexpr std::chrono::seconds timeoutLocal{3};
|
||||||
error_code ec;
|
error_code ec;
|
||||||
timer_.expires_from_now(timeout, ec);
|
timer_.expires_from_now(
|
||||||
|
remote_endpoint().address().is_loopback() ? timeoutLocal : timeout,
|
||||||
|
ec);
|
||||||
if(ec)
|
if(ec)
|
||||||
return fail(ec, "start_timer");
|
return fail(ec, "start_timer");
|
||||||
timer_.async_wait(strand_.wrap(std::bind(
|
timer_.async_wait(strand_.wrap(std::bind(
|
||||||
|
|||||||
@@ -109,6 +109,15 @@ class WSClientImpl : public WSClient
|
|||||||
|
|
||||||
unsigned rpc_version_;
|
unsigned rpc_version_;
|
||||||
|
|
||||||
|
void cleanup()
|
||||||
|
{
|
||||||
|
error_code ec;
|
||||||
|
ws_.close({}, ec);
|
||||||
|
stream_.close(ec);
|
||||||
|
work_ = boost::none;
|
||||||
|
thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WSClientImpl(Config const& cfg, bool v2, unsigned rpc_version)
|
WSClientImpl(Config const& cfg, bool v2, unsigned rpc_version)
|
||||||
: work_(ios_)
|
: work_(ios_)
|
||||||
@@ -118,21 +127,26 @@ public:
|
|||||||
, ws_(stream_)
|
, ws_(stream_)
|
||||||
, rpc_version_(rpc_version)
|
, rpc_version_(rpc_version)
|
||||||
{
|
{
|
||||||
auto const ep = getEndpoint(cfg, v2);
|
try
|
||||||
stream_.connect(ep);
|
{
|
||||||
ws_.handshake(ep.address().to_string() +
|
auto const ep = getEndpoint(cfg, v2);
|
||||||
":" + std::to_string(ep.port()), "/");
|
stream_.connect(ep);
|
||||||
ws_.async_read(op_, rb_,
|
ws_.handshake(ep.address().to_string() +
|
||||||
strand_.wrap(std::bind(&WSClientImpl::on_read_msg,
|
":" + std::to_string(ep.port()), "/");
|
||||||
this, beast::asio::placeholders::error)));
|
ws_.async_read(op_, rb_,
|
||||||
|
strand_.wrap(std::bind(&WSClientImpl::on_read_msg,
|
||||||
|
this, beast::asio::placeholders::error)));
|
||||||
|
}
|
||||||
|
catch(std::exception&)
|
||||||
|
{
|
||||||
|
cleanup();
|
||||||
|
Rethrow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~WSClientImpl() override
|
~WSClientImpl() override
|
||||||
{
|
{
|
||||||
ws_.close({});
|
cleanup();
|
||||||
stream_.close();
|
|
||||||
work_ = boost::none;
|
|
||||||
thread_.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value
|
Json::Value
|
||||||
|
|||||||
@@ -19,22 +19,260 @@
|
|||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
#include <ripple/rpc/ServerHandler.h>
|
#include <ripple/rpc/ServerHandler.h>
|
||||||
|
#include <ripple/json/json_reader.h>
|
||||||
#include <ripple/test/jtx.h>
|
#include <ripple/test/jtx.h>
|
||||||
|
#include <ripple/test/WSClient.h>
|
||||||
|
#include <ripple/test/JSONRPCClient.h>
|
||||||
|
#include <ripple/core/DeadlineTimer.h>
|
||||||
#include <beast/core/to_string.hpp>
|
#include <beast/core/to_string.hpp>
|
||||||
#include <beast/http.hpp>
|
#include <beast/http.hpp>
|
||||||
|
#include <beast/test/yield_to.hpp>
|
||||||
#include <beast/websocket/detail/mask.hpp>
|
#include <beast/websocket/detail/mask.hpp>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/asio/ssl.hpp>
|
#include <boost/asio/ssl.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
class ServerStatus_test : public beast::unit_test::suite
|
class ServerStatus_test :
|
||||||
|
public beast::unit_test::suite, public beast::test::enable_yield_to
|
||||||
{
|
{
|
||||||
public:
|
auto makeConfig(
|
||||||
void
|
std::string const& proto,
|
||||||
testUnauthorizedRequest()
|
bool admin = true,
|
||||||
|
bool credentials = false)
|
||||||
{
|
{
|
||||||
|
auto const section_name =
|
||||||
|
boost::starts_with(proto, "h") ? "port_rpc" : "port_ws";
|
||||||
|
auto p = std::make_unique<Config>();
|
||||||
|
setupConfigForUnitTests(*p);
|
||||||
|
|
||||||
|
p->overwrite(section_name, "protocol", proto);
|
||||||
|
if(! admin)
|
||||||
|
p->overwrite(section_name, "admin", "");
|
||||||
|
|
||||||
|
if(credentials)
|
||||||
|
{
|
||||||
|
(*p)[section_name].set("admin_password", "p");
|
||||||
|
(*p)[section_name].set("admin_user", "u");
|
||||||
|
}
|
||||||
|
|
||||||
|
p->overwrite(
|
||||||
|
boost::starts_with(proto, "h") ? "port_ws" : "port_rpc",
|
||||||
|
"protocol",
|
||||||
|
boost::starts_with(proto, "h") ? "ws" : "http");
|
||||||
|
|
||||||
|
if(proto == "https")
|
||||||
|
{
|
||||||
|
// this port is here to allow the env to create its internal client,
|
||||||
|
// which requires an http endpoint to talk to. In the connection
|
||||||
|
// failure test, this endpoint should never be used
|
||||||
|
(*p)["server"].append("port_alt");
|
||||||
|
(*p)["port_alt"].set("ip", "127.0.0.1");
|
||||||
|
(*p)["port_alt"].set("port", "8099");
|
||||||
|
(*p)["port_alt"].set("protocol", "http");
|
||||||
|
(*p)["port_alt"].set("admin", "127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto makeWSUpgrade(
|
||||||
|
std::string const& host,
|
||||||
|
uint16_t port)
|
||||||
|
{
|
||||||
|
using namespace boost::asio;
|
||||||
|
using namespace beast::http;
|
||||||
|
request_v1<string_body> req;
|
||||||
|
|
||||||
|
req.url = "/";
|
||||||
|
req.version = 11;
|
||||||
|
req.headers.insert("Host", host + ":" + to_string(port));
|
||||||
|
req.headers.insert("User-Agent", "test");
|
||||||
|
req.method = "GET";
|
||||||
|
req.headers.insert("Upgrade", "websocket");
|
||||||
|
beast::websocket::detail::maskgen maskgen;
|
||||||
|
std::string key = beast::websocket::detail::make_sec_ws_key(maskgen);
|
||||||
|
req.headers.insert("Sec-WebSocket-Key", key);
|
||||||
|
req.headers.insert("Sec-WebSocket-Version", "13");
|
||||||
|
prepare(req, connection::upgrade);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto makeHTTPRequest(
|
||||||
|
std::string const& host,
|
||||||
|
uint16_t port,
|
||||||
|
std::string const& body)
|
||||||
|
{
|
||||||
|
using namespace boost::asio;
|
||||||
|
using namespace beast::http;
|
||||||
|
request_v1<string_body> req;
|
||||||
|
|
||||||
|
req.url = "/";
|
||||||
|
req.version = 11;
|
||||||
|
req.headers.insert("Host", host + ":" + to_string(port));
|
||||||
|
req.headers.insert("User-Agent", "test");
|
||||||
|
if(body.empty())
|
||||||
|
{
|
||||||
|
req.method = "GET";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
req.method = "POST";
|
||||||
|
req.headers.insert("Content-Type", "application/json; charset=UTF-8");
|
||||||
|
req.body = body;
|
||||||
|
}
|
||||||
|
prepare(req);
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doRequest(
|
||||||
|
boost::asio::yield_context& yield,
|
||||||
|
beast::http::request_v1<beast::http::string_body> const& req,
|
||||||
|
std::string const& host,
|
||||||
|
uint16_t port,
|
||||||
|
bool secure,
|
||||||
|
beast::http::response_v1<beast::http::string_body>& resp,
|
||||||
|
boost::system::error_code& ec)
|
||||||
|
{
|
||||||
|
using namespace boost::asio;
|
||||||
|
using namespace beast::http;
|
||||||
|
io_service& ios = get_io_service();
|
||||||
|
ip::tcp::resolver r{ios};
|
||||||
|
beast::streambuf sb;
|
||||||
|
|
||||||
|
auto it =
|
||||||
|
r.async_resolve(
|
||||||
|
ip::tcp::resolver::query{host, to_string(port)}, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(secure)
|
||||||
|
{
|
||||||
|
ssl::context ctx{ssl::context::sslv23};
|
||||||
|
ctx.set_verify_mode(ssl::verify_none);
|
||||||
|
ssl::stream<ip::tcp::socket> ss{ios, ctx};
|
||||||
|
async_connect(ss.next_layer(), it, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
ss.async_handshake(ssl::stream_base::client, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
async_write(ss, req, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
async_read(ss, sb, resp, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ip::tcp::socket sock{ios};
|
||||||
|
async_connect(sock, it, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
async_write(sock, req, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
async_read(sock, sb, resp, yield[ec]);
|
||||||
|
if(ec)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doWSRequest(
|
||||||
|
test::jtx::Env& env,
|
||||||
|
boost::asio::yield_context& yield,
|
||||||
|
bool secure,
|
||||||
|
beast::http::response_v1<beast::http::string_body>& resp,
|
||||||
|
boost::system::error_code& ec)
|
||||||
|
{
|
||||||
|
auto const port = env.app().config()["port_ws"].
|
||||||
|
get<std::uint16_t>("port");
|
||||||
|
auto const ip = env.app().config()["port_ws"].
|
||||||
|
get<std::string>("ip");
|
||||||
|
doRequest(
|
||||||
|
yield, makeWSUpgrade(*ip, *port), *ip, *port, secure, resp, ec);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doHTTPRequest(
|
||||||
|
test::jtx::Env& env,
|
||||||
|
boost::asio::yield_context& yield,
|
||||||
|
bool secure,
|
||||||
|
beast::http::response_v1<beast::http::string_body>& resp,
|
||||||
|
boost::system::error_code& ec,
|
||||||
|
std::string const& body = "")
|
||||||
|
{
|
||||||
|
auto const port = env.app().config()["port_rpc"].
|
||||||
|
get<std::uint16_t>("port");
|
||||||
|
auto const ip = env.app().config()["port_rpc"].
|
||||||
|
get<std::string>("ip");
|
||||||
|
doRequest(
|
||||||
|
yield,
|
||||||
|
makeHTTPRequest(*ip, *port, body),
|
||||||
|
*ip,
|
||||||
|
*port,
|
||||||
|
secure,
|
||||||
|
resp,
|
||||||
|
ec);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto makeAdminRequest(
|
||||||
|
jtx::Env & env,
|
||||||
|
std::string const& proto,
|
||||||
|
std::string const& user,
|
||||||
|
std::string const& password,
|
||||||
|
bool subobject = false)
|
||||||
|
{
|
||||||
|
Json::Value jrr;
|
||||||
|
|
||||||
|
Json::Value jp = Json::objectValue;
|
||||||
|
if(! user.empty())
|
||||||
|
{
|
||||||
|
jp["admin_user"] = user;
|
||||||
|
if(subobject)
|
||||||
|
{
|
||||||
|
//special case of bad password..passed as object
|
||||||
|
Json::Value jpi = Json::objectValue;
|
||||||
|
jpi["admin_password"] = password;
|
||||||
|
jp["admin_password"] = jpi;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jp["admin_password"] = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(boost::starts_with(proto, "h"))
|
||||||
|
{
|
||||||
|
auto jrc = makeJSONRPCClient(env.app().config());
|
||||||
|
jrr = jrc->invoke("ledger_accept", jp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto wsc = makeWSClient(env.app().config(), proto == "ws2");
|
||||||
|
jrr = wsc->invoke("ledger_accept", jp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jrr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------
|
||||||
|
// Test Cases
|
||||||
|
// ------------
|
||||||
|
void
|
||||||
|
testWSClientToHttpServer(boost::asio::yield_context& yield)
|
||||||
|
{
|
||||||
|
testcase("WS client to http server fails");
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
Env env(*this, []()
|
Env env(*this, []()
|
||||||
{
|
{
|
||||||
@@ -43,61 +281,22 @@ public:
|
|||||||
p->section("port_ws").set("protocol", "http,https");
|
p->section("port_ws").set("protocol", "http,https");
|
||||||
return p;
|
return p;
|
||||||
}());
|
}());
|
||||||
auto const port = env.app().config()["port_ws"].
|
|
||||||
get<std::uint16_t>("port");
|
|
||||||
if(! BEAST_EXPECT(port))
|
|
||||||
return;
|
|
||||||
|
|
||||||
using namespace boost::asio;
|
//non-secure request
|
||||||
using namespace beast::http;
|
|
||||||
io_service ios;
|
|
||||||
ip::tcp::resolver r{ios};
|
|
||||||
beast::streambuf sb;
|
|
||||||
response_v1<string_body> resp;
|
|
||||||
boost::system::error_code ec;
|
|
||||||
|
|
||||||
beast::websocket::detail::maskgen maskgen;
|
|
||||||
request_v1<empty_body> req;
|
|
||||||
req.url = "/";
|
|
||||||
req.version = 11;
|
|
||||||
req.method = "GET";
|
|
||||||
req.headers.insert("Host", "127.0.0.1:" + to_string(*port));
|
|
||||||
req.headers.insert("Upgrade", "websocket");
|
|
||||||
std::string key = beast::websocket::detail::make_sec_ws_key(maskgen);
|
|
||||||
req.headers.insert("Sec-WebSocket-Key", key);
|
|
||||||
req.headers.insert("Sec-WebSocket-Version", "13");
|
|
||||||
prepare(req, connection::upgrade);
|
|
||||||
|
|
||||||
// non secure socket
|
|
||||||
{
|
{
|
||||||
ip::tcp::socket sock{ios};
|
boost::system::error_code ec;
|
||||||
connect(sock, r.resolve(
|
beast::http::response_v1<beast::http::string_body> resp;
|
||||||
ip::tcp::resolver::query{"127.0.0.1", to_string(*port)}), ec);
|
doWSRequest(env, yield, false, resp, ec);
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
return;
|
|
||||||
write(sock, req, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
return;
|
|
||||||
read(sock, sb, resp, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||||
return;
|
return;
|
||||||
BEAST_EXPECT(resp.status == 401);
|
BEAST_EXPECT(resp.status == 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
// secure socket
|
//secure request
|
||||||
{
|
{
|
||||||
ssl::context ctx{ssl::context::sslv23};
|
boost::system::error_code ec;
|
||||||
ctx.set_verify_mode(ssl::verify_none);
|
beast::http::response_v1<beast::http::string_body> resp;
|
||||||
ssl::stream<ip::tcp::socket> ss{ios, ctx};
|
doWSRequest(env, yield, true, resp, ec);
|
||||||
connect(ss.next_layer(), r.resolve(
|
|
||||||
ip::tcp::resolver::query{"127.0.0.1", to_string(*port)}));
|
|
||||||
ss.handshake(ssl::stream_base::client, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
return;
|
|
||||||
write(ss, req, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
return;
|
|
||||||
read(ss, sb, resp, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||||
return;
|
return;
|
||||||
BEAST_EXPECT(resp.status == 401);
|
BEAST_EXPECT(resp.status == 401);
|
||||||
@@ -105,78 +304,244 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testStatusRequest()
|
testStatusRequest(boost::asio::yield_context& yield)
|
||||||
{
|
{
|
||||||
|
testcase("Status request");
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
Env env(*this, []()
|
Env env(*this, []()
|
||||||
{
|
{
|
||||||
auto p = std::make_unique<Config>();
|
auto p = std::make_unique<Config>();
|
||||||
setupConfigForUnitTests(*p);
|
setupConfigForUnitTests(*p);
|
||||||
p->section("port_ws").set("protocol", "ws2,wss2");
|
p->section("port_rpc").set("protocol", "ws2,wss2");
|
||||||
|
p->section("port_ws").set("protocol", "http");
|
||||||
return p;
|
return p;
|
||||||
}());
|
}());
|
||||||
auto const port = env.app().config()["port_ws"].
|
|
||||||
get<std::uint16_t>("port");
|
|
||||||
if(! BEAST_EXPECT(port))
|
|
||||||
return;
|
|
||||||
|
|
||||||
using namespace boost::asio;
|
//non-secure request
|
||||||
using namespace beast::http;
|
|
||||||
io_service ios;
|
|
||||||
ip::tcp::resolver r{ios};
|
|
||||||
beast::streambuf sb;
|
|
||||||
response_v1<string_body> resp;
|
|
||||||
boost::system::error_code ec;
|
|
||||||
|
|
||||||
request_v1<empty_body> req;
|
|
||||||
req.url = "/";
|
|
||||||
req.version = 11;
|
|
||||||
req.method = "GET";
|
|
||||||
req.headers.insert("Host", "127.0.0.1:" + to_string(*port));
|
|
||||||
req.headers.insert("User-Agent", "test");
|
|
||||||
prepare(req);
|
|
||||||
|
|
||||||
// Request the status page on a non secure socket
|
|
||||||
{
|
{
|
||||||
ip::tcp::socket sock{ios};
|
boost::system::error_code ec;
|
||||||
connect(sock, r.resolve(
|
beast::http::response_v1<beast::http::string_body> resp;
|
||||||
ip::tcp::resolver::query{"127.0.0.1", to_string(*port)}), ec);
|
doHTTPRequest(env, yield, false, resp, ec);
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
return;
|
|
||||||
write(sock, req, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
return;
|
|
||||||
read(sock, sb, resp, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||||
return;
|
return;
|
||||||
BEAST_EXPECT(resp.status == 200);
|
BEAST_EXPECT(resp.status == 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the status page on a secure socket
|
//secure request
|
||||||
{
|
{
|
||||||
ssl::context ctx{ssl::context::sslv23};
|
boost::system::error_code ec;
|
||||||
ctx.set_verify_mode(ssl::verify_none);
|
beast::http::response_v1<beast::http::string_body> resp;
|
||||||
ssl::stream<ip::tcp::socket> ss{ios, ctx};
|
doHTTPRequest(env, yield, true, resp, ec);
|
||||||
connect(ss.next_layer(), r.resolve(
|
|
||||||
ip::tcp::resolver::query{"127.0.0.1", to_string(*port)}));
|
|
||||||
ss.handshake(ssl::stream_base::client, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
return;
|
|
||||||
write(ss, req, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
|
||||||
return;
|
|
||||||
read(ss, sb, resp, ec);
|
|
||||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||||
return;
|
return;
|
||||||
BEAST_EXPECT(resp.status == 200);
|
BEAST_EXPECT(resp.status == 200);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
testTruncatedWSUpgrade(boost::asio::yield_context& yield)
|
||||||
|
{
|
||||||
|
testcase("Partial WS upgrade request");
|
||||||
|
using namespace jtx;
|
||||||
|
using namespace boost::asio;
|
||||||
|
using namespace beast::http;
|
||||||
|
Env env(*this, []()
|
||||||
|
{
|
||||||
|
auto p = std::make_unique<Config>();
|
||||||
|
setupConfigForUnitTests(*p);
|
||||||
|
p->section("port_ws").set("protocol", "ws2");
|
||||||
|
return p;
|
||||||
|
}());
|
||||||
|
|
||||||
|
auto const port = env.app().config()["port_ws"].
|
||||||
|
get<std::uint16_t>("port");
|
||||||
|
auto const ip = env.app().config()["port_ws"].
|
||||||
|
get<std::string>("ip");
|
||||||
|
|
||||||
|
boost::system::error_code ec;
|
||||||
|
response_v1<string_body> resp;
|
||||||
|
auto req = makeWSUpgrade(*ip, *port);
|
||||||
|
|
||||||
|
//truncate the request message to near the value of the version header
|
||||||
|
auto req_string = boost::lexical_cast<std::string>(req);
|
||||||
|
req_string.erase(req_string.find_last_of("13"), std::string::npos);
|
||||||
|
|
||||||
|
io_service& ios = get_io_service();
|
||||||
|
ip::tcp::resolver r{ios};
|
||||||
|
beast::streambuf sb;
|
||||||
|
|
||||||
|
auto it =
|
||||||
|
r.async_resolve(
|
||||||
|
ip::tcp::resolver::query{*ip, to_string(*port)}, yield[ec]);
|
||||||
|
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ip::tcp::socket sock{ios};
|
||||||
|
async_connect(sock, it, yield[ec]);
|
||||||
|
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||||
|
return;
|
||||||
|
async_write(sock, boost::asio::buffer(req_string), yield[ec]);
|
||||||
|
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||||
|
return;
|
||||||
|
// since we've sent an incomplete request, the server will
|
||||||
|
// keep trying to read until it gives up (by timeout)
|
||||||
|
async_read(sock, sb, resp, yield[ec]);
|
||||||
|
BEAST_EXPECT(ec);
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
testCantConnect(
|
||||||
|
std::string const& client_protocol,
|
||||||
|
std::string const& server_protocol,
|
||||||
|
boost::asio::yield_context& yield)
|
||||||
|
{
|
||||||
|
testcase << "Connect fails: " << client_protocol << " client to " <<
|
||||||
|
server_protocol << " server";
|
||||||
|
using namespace jtx;
|
||||||
|
Env env {*this, makeConfig(server_protocol)};
|
||||||
|
|
||||||
|
beast::http::response_v1<beast::http::string_body> resp;
|
||||||
|
boost::system::error_code ec;
|
||||||
|
// The essence of this test is to have a client and server configured
|
||||||
|
// out-of-phase with respect to ssl (secure client and insecure server
|
||||||
|
// or vice-versa) - as such, here is a config to pass to
|
||||||
|
// WSClient/JSONRPCClient that configures it for a protocol that
|
||||||
|
// doesn't match the actual server
|
||||||
|
auto cfg = makeConfig(client_protocol);
|
||||||
|
if(boost::starts_with(client_protocol, "h"))
|
||||||
|
{
|
||||||
|
doHTTPRequest(
|
||||||
|
env,
|
||||||
|
yield,
|
||||||
|
client_protocol == "https",
|
||||||
|
resp,
|
||||||
|
ec);
|
||||||
|
BEAST_EXPECT(ec);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
doWSRequest(
|
||||||
|
env,
|
||||||
|
yield,
|
||||||
|
client_protocol == "wss" || client_protocol == "wss2",
|
||||||
|
resp,
|
||||||
|
ec);
|
||||||
|
BEAST_EXPECT(ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testAdminRequest(std::string const& proto, bool admin, bool credentials)
|
||||||
|
{
|
||||||
|
testcase << "Admin request over " << proto <<
|
||||||
|
", config " << (admin ? "enabled" : "disabled") <<
|
||||||
|
", credentials " << (credentials ? "" : "not ") << "set";
|
||||||
|
using namespace jtx;
|
||||||
|
Env env {*this, makeConfig(proto, admin, credentials)};
|
||||||
|
|
||||||
|
auto const user = env.app().config()
|
||||||
|
[boost::starts_with(proto, "h") ? "port_rpc" : "port_ws"].
|
||||||
|
get<std::string>("admin_user");
|
||||||
|
|
||||||
|
auto const password = env.app().config()
|
||||||
|
[boost::starts_with(proto, "h") ? "port_rpc" : "port_ws"].
|
||||||
|
get<std::string>("admin_password");
|
||||||
|
|
||||||
|
Json::Value jrr;
|
||||||
|
|
||||||
|
// the set of checks we do are different depending
|
||||||
|
// on how the admin config options are set
|
||||||
|
|
||||||
|
if(admin && credentials)
|
||||||
|
{
|
||||||
|
//1 - FAILS with wrong pass
|
||||||
|
jrr = makeAdminRequest(env, proto, *user, *password + "_")[jss::result];
|
||||||
|
BEAST_EXPECT(jrr["error"] ==
|
||||||
|
boost::starts_with(proto, "h") ? "noPermission" : "forbidden");
|
||||||
|
BEAST_EXPECT(jrr["error_message"] ==
|
||||||
|
boost::starts_with(proto, "h") ?
|
||||||
|
"You don't have permission for this command." :
|
||||||
|
"Bad credentials.");
|
||||||
|
|
||||||
|
//2 - FAILS with password in an object
|
||||||
|
jrr = makeAdminRequest(env, proto, *user, *password, true)[jss::result];
|
||||||
|
BEAST_EXPECT(jrr["error"] ==
|
||||||
|
boost::starts_with(proto, "h") ? "noPermission" : "forbidden");
|
||||||
|
BEAST_EXPECT(jrr["error_message"] ==
|
||||||
|
boost::starts_with(proto, "h") ?
|
||||||
|
"You don't have permission for this command." :
|
||||||
|
"Bad credentials.");
|
||||||
|
|
||||||
|
//3 - FAILS with wrong user
|
||||||
|
jrr = makeAdminRequest(env, proto, *user + "_", *password)[jss::result];
|
||||||
|
BEAST_EXPECT(jrr["error"] ==
|
||||||
|
boost::starts_with(proto, "h") ? "noPermission" : "forbidden");
|
||||||
|
BEAST_EXPECT(jrr["error_message"] ==
|
||||||
|
boost::starts_with(proto, "h") ?
|
||||||
|
"You don't have permission for this command." :
|
||||||
|
"Bad credentials.");
|
||||||
|
|
||||||
|
//4 - FAILS no credentials
|
||||||
|
jrr = makeAdminRequest(env, proto, "", "")[jss::result];
|
||||||
|
BEAST_EXPECT(jrr["error"] ==
|
||||||
|
boost::starts_with(proto, "h") ? "noPermission" : "forbidden");
|
||||||
|
BEAST_EXPECT(jrr["error_message"] ==
|
||||||
|
boost::starts_with(proto, "h") ?
|
||||||
|
"You don't have permission for this command." :
|
||||||
|
"Bad credentials.");
|
||||||
|
|
||||||
|
//5 - SUCCEEDS with proper credentials
|
||||||
|
jrr = makeAdminRequest(env, proto, *user, *password)[jss::result];
|
||||||
|
BEAST_EXPECT(jrr["status"] == "success");
|
||||||
|
}
|
||||||
|
else if(admin)
|
||||||
|
{
|
||||||
|
//1 - SUCCEEDS with proper credentials
|
||||||
|
jrr = makeAdminRequest(env, proto, "u", "p")[jss::result];
|
||||||
|
BEAST_EXPECT(jrr["status"] == "success");
|
||||||
|
|
||||||
|
//2 - SUCCEEDS without proper credentials
|
||||||
|
jrr = makeAdminRequest(env, proto, "", "")[jss::result];
|
||||||
|
BEAST_EXPECT(jrr["status"] == "success");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//1 - FAILS - admin disabled
|
||||||
|
jrr = makeAdminRequest(env, proto, "", "")[jss::result];
|
||||||
|
BEAST_EXPECT(jrr["error"] ==
|
||||||
|
boost::starts_with(proto, "h") ? "noPermission" : "forbidden");
|
||||||
|
BEAST_EXPECT(jrr["error_message"] ==
|
||||||
|
boost::starts_with(proto, "h") ?
|
||||||
|
"You don't have permission for this command." :
|
||||||
|
"Bad credentials.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
void
|
void
|
||||||
run()
|
run()
|
||||||
{
|
{
|
||||||
testUnauthorizedRequest();
|
yield_to([&](boost::asio::yield_context& yield)
|
||||||
testStatusRequest();
|
{
|
||||||
|
testWSClientToHttpServer(yield);
|
||||||
|
testStatusRequest(yield);
|
||||||
|
testTruncatedWSUpgrade(yield);
|
||||||
|
// these are secure/insecure protocol pairs, i.e. for
|
||||||
|
// each item, the second value is the secure or insecure equivalent
|
||||||
|
testCantConnect("ws", "wss", yield);
|
||||||
|
testCantConnect("ws2", "wss2", yield);
|
||||||
|
testCantConnect("http", "https", yield);
|
||||||
|
//THIS HANGS - testCantConnect("wss", "ws", yield);
|
||||||
|
testCantConnect("wss2", "ws2", yield);
|
||||||
|
testCantConnect("https", "http", yield);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto it : {"http", "ws", "ws2"})
|
||||||
|
{
|
||||||
|
testAdminRequest(it, true, true);
|
||||||
|
testAdminRequest(it, true, false);
|
||||||
|
testAdminRequest(it, false, false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user