mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 22:45:52 +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 Include="..\..\src\BeastConfig.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\beast\extras\beast\test\yield_to.hpp">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\amount.hpp">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\beast\extras\beast\unit_test\detail\const_container.hpp">
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
<Filter Include="beast\http\impl">
|
||||
<UniqueIdentifier>{932F732F-F09E-5C50-C8A1-D62342CCAA1F}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="beast\test">
|
||||
<UniqueIdentifier>{0ED4CDBE-296D-2599-04B3-095BFD1668A4}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="beast\unit_test">
|
||||
<UniqueIdentifier>{2762284D-66E5-8B48-1F8E-67116DB1FC6B}</UniqueIdentifier>
|
||||
</Filter>
|
||||
@@ -543,6 +546,9 @@
|
||||
<ClInclude Include="..\..\src\BeastConfig.h">
|
||||
<Filter>.</Filter>
|
||||
</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">
|
||||
<Filter>beast\unit_test</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -64,7 +64,8 @@ protected:
|
||||
bufferSize = 4 * 1024,
|
||||
|
||||
// Max seconds without completing a message
|
||||
timeoutSeconds = 30
|
||||
timeoutSeconds = 30,
|
||||
timeoutSecondsLocal = 3 //used for localhost clients
|
||||
};
|
||||
|
||||
struct buffer
|
||||
@@ -277,7 +278,12 @@ BaseHTTPPeer<Handler, Impl>::
|
||||
start_timer()
|
||||
{
|
||||
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)
|
||||
return fail(ec, "start_timer");
|
||||
timer_.async_wait(strand_.wrap(std::bind(
|
||||
@@ -318,6 +324,7 @@ do_read(yield_context do_yield)
|
||||
{
|
||||
complete_ = false;
|
||||
error_code ec;
|
||||
start_timer();
|
||||
beast::http::async_read(impl().stream_,
|
||||
read_buf_, message_, do_yield[ec]);
|
||||
// VFALCO What if the connection was closed?
|
||||
|
||||
@@ -364,8 +364,11 @@ start_timer()
|
||||
{
|
||||
// Max seconds without completing a message
|
||||
static constexpr std::chrono::seconds timeout{30};
|
||||
static constexpr std::chrono::seconds timeoutLocal{3};
|
||||
error_code ec;
|
||||
timer_.expires_from_now(timeout, ec);
|
||||
timer_.expires_from_now(
|
||||
remote_endpoint().address().is_loopback() ? timeoutLocal : timeout,
|
||||
ec);
|
||||
if(ec)
|
||||
return fail(ec, "start_timer");
|
||||
timer_.async_wait(strand_.wrap(std::bind(
|
||||
|
||||
@@ -109,6 +109,15 @@ class WSClientImpl : public WSClient
|
||||
|
||||
unsigned rpc_version_;
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
error_code ec;
|
||||
ws_.close({}, ec);
|
||||
stream_.close(ec);
|
||||
work_ = boost::none;
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
public:
|
||||
WSClientImpl(Config const& cfg, bool v2, unsigned rpc_version)
|
||||
: work_(ios_)
|
||||
@@ -118,21 +127,26 @@ public:
|
||||
, ws_(stream_)
|
||||
, rpc_version_(rpc_version)
|
||||
{
|
||||
auto const ep = getEndpoint(cfg, v2);
|
||||
stream_.connect(ep);
|
||||
ws_.handshake(ep.address().to_string() +
|
||||
":" + std::to_string(ep.port()), "/");
|
||||
ws_.async_read(op_, rb_,
|
||||
strand_.wrap(std::bind(&WSClientImpl::on_read_msg,
|
||||
this, beast::asio::placeholders::error)));
|
||||
try
|
||||
{
|
||||
auto const ep = getEndpoint(cfg, v2);
|
||||
stream_.connect(ep);
|
||||
ws_.handshake(ep.address().to_string() +
|
||||
":" + std::to_string(ep.port()), "/");
|
||||
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
|
||||
{
|
||||
ws_.close({});
|
||||
stream_.close();
|
||||
work_ = boost::none;
|
||||
thread_.join();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
Json::Value
|
||||
|
||||
@@ -19,22 +19,260 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/rpc/ServerHandler.h>
|
||||
#include <ripple/json/json_reader.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/http.hpp>
|
||||
#include <beast/test/yield_to.hpp>
|
||||
#include <beast/websocket/detail/mask.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
namespace ripple {
|
||||
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:
|
||||
void
|
||||
testUnauthorizedRequest()
|
||||
auto makeConfig(
|
||||
std::string const& proto,
|
||||
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;
|
||||
Env env(*this, []()
|
||||
{
|
||||
@@ -43,61 +281,22 @@ public:
|
||||
p->section("port_ws").set("protocol", "http,https");
|
||||
return p;
|
||||
}());
|
||||
auto const port = env.app().config()["port_ws"].
|
||||
get<std::uint16_t>("port");
|
||||
if(! BEAST_EXPECT(port))
|
||||
return;
|
||||
|
||||
using namespace boost::asio;
|
||||
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
|
||||
//non-secure request
|
||||
{
|
||||
ip::tcp::socket sock{ios};
|
||||
connect(sock, r.resolve(
|
||||
ip::tcp::resolver::query{"127.0.0.1", to_string(*port)}), ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
write(sock, req, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
read(sock, sb, resp, ec);
|
||||
boost::system::error_code ec;
|
||||
beast::http::response_v1<beast::http::string_body> resp;
|
||||
doWSRequest(env, yield, false, resp, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
BEAST_EXPECT(resp.status == 401);
|
||||
}
|
||||
|
||||
// secure socket
|
||||
//secure request
|
||||
{
|
||||
ssl::context ctx{ssl::context::sslv23};
|
||||
ctx.set_verify_mode(ssl::verify_none);
|
||||
ssl::stream<ip::tcp::socket> ss{ios, ctx};
|
||||
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);
|
||||
boost::system::error_code ec;
|
||||
beast::http::response_v1<beast::http::string_body> resp;
|
||||
doWSRequest(env, yield, true, resp, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
BEAST_EXPECT(resp.status == 401);
|
||||
@@ -105,78 +304,244 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testStatusRequest()
|
||||
testStatusRequest(boost::asio::yield_context& yield)
|
||||
{
|
||||
testcase("Status request");
|
||||
using namespace jtx;
|
||||
Env env(*this, []()
|
||||
{
|
||||
auto p = std::make_unique<Config>();
|
||||
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;
|
||||
}());
|
||||
auto const port = env.app().config()["port_ws"].
|
||||
get<std::uint16_t>("port");
|
||||
if(! BEAST_EXPECT(port))
|
||||
return;
|
||||
|
||||
using namespace boost::asio;
|
||||
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
|
||||
//non-secure request
|
||||
{
|
||||
ip::tcp::socket sock{ios};
|
||||
connect(sock, r.resolve(
|
||||
ip::tcp::resolver::query{"127.0.0.1", to_string(*port)}), ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
write(sock, req, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
read(sock, sb, resp, ec);
|
||||
boost::system::error_code ec;
|
||||
beast::http::response_v1<beast::http::string_body> resp;
|
||||
doHTTPRequest(env, yield, false, resp, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
BEAST_EXPECT(resp.status == 200);
|
||||
}
|
||||
|
||||
// Request the status page on a secure socket
|
||||
//secure request
|
||||
{
|
||||
ssl::context ctx{ssl::context::sslv23};
|
||||
ctx.set_verify_mode(ssl::verify_none);
|
||||
ssl::stream<ip::tcp::socket> ss{ios, ctx};
|
||||
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);
|
||||
boost::system::error_code ec;
|
||||
beast::http::response_v1<beast::http::string_body> resp;
|
||||
doHTTPRequest(env, yield, true, resp, ec);
|
||||
if(! BEAST_EXPECTS(! ec, ec.message()))
|
||||
return;
|
||||
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
|
||||
run()
|
||||
{
|
||||
testUnauthorizedRequest();
|
||||
testStatusRequest();
|
||||
yield_to([&](boost::asio::yield_context& yield)
|
||||
{
|
||||
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