From 3118110eb8f1fda5b97d16e1d140bc3e49333687 Mon Sep 17 00:00:00 2001 From: cyan317 <120398799+cindyyan317@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:42:51 +0100 Subject: [PATCH] feat: add 'force_forward' field to request (#1647) Fix #1141 --- src/rpc/common/impl/ForwardingProxy.hpp | 11 + tests/unit/rpc/ForwardingProxyTests.cpp | 369 ++++++++++-------------- 2 files changed, 159 insertions(+), 221 deletions(-) diff --git a/src/rpc/common/impl/ForwardingProxy.hpp b/src/rpc/common/impl/ForwardingProxy.hpp index bd3a83b8..cb69d04d 100644 --- a/src/rpc/common/impl/ForwardingProxy.hpp +++ b/src/rpc/common/impl/ForwardingProxy.hpp @@ -69,6 +69,9 @@ public: if (specifiesCurrentOrClosedLedger(request)) return true; + if (isForcedForward(ctx)) + return true; + auto const checkAccountInfoForward = [&]() { return ctx.method == "account_info" and request.contains("queue") and request.at("queue").is_bool() and request.at("queue").as_bool(); @@ -138,6 +141,14 @@ private: { return handlerProvider_->contains(method) || isProxied(method); } + + bool + isForcedForward(web::Context const& ctx) const + { + static constexpr auto FORCE_FORWARD = "force_forward"; + return ctx.isAdmin and ctx.params.contains(FORCE_FORWARD) and ctx.params.at(FORCE_FORWARD).is_bool() and + ctx.params.at(FORCE_FORWARD).as_bool(); + } }; } // namespace rpc::impl diff --git a/tests/unit/rpc/ForwardingProxyTests.cpp b/tests/unit/rpc/ForwardingProxyTests.cpp index 5c37fa3c..4145510a 100644 --- a/tests/unit/rpc/ForwardingProxyTests.cpp +++ b/tests/unit/rpc/ForwardingProxyTests.cpp @@ -23,6 +23,7 @@ #include "util/MockCounters.hpp" #include "util/MockHandlerProvider.hpp" #include "util/MockLoadBalancer.hpp" +#include "util/NameGenerator.hpp" #include "util/Taggable.hpp" #include "util/config/Config.hpp" #include "web/Context.hpp" @@ -32,10 +33,12 @@ #include #include +#include #include #include #include #include +#include using namespace rpc; using namespace testing; @@ -59,235 +62,159 @@ protected: }; }; -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfClioOnly) +struct ShouldForwardParamTestCaseBundle { + std::string testName; + std::uint32_t apiVersion; + std::string method; + std::string testJson; + bool mockedIsClioOnly; + std::uint32_t called; + bool isAdmin; + bool expected; +}; + +struct ShouldForwardParameterTest : public RPCForwardingProxyTest, + WithParamInterface {}; + +static auto +generateTestValuesForParametersTest() { + auto const isClioOnly = true; + auto const isAdmin = true; + auto const shouldForward = true; + + return std::vector{ + {"ShouldForwardReturnsFalseIfClioOnly", 2u, "test", "{}", isClioOnly, 1, !isAdmin, !shouldForward}, + {"ShouldForwardReturnsTrueIfProxied", 2u, "submit", "{}", !isClioOnly, 1, !isAdmin, shouldForward}, + {"ShouldForwardReturnsTrueIfCurrentLedgerSpecified", + 2u, + "anymethod", + R"({"ledger_index": "current"})", + !isClioOnly, + 1, + !isAdmin, + shouldForward}, + {"ShouldForwardReturnsTrueIfClosedLedgerSpecified", + 2u, + "anymethod", + R"({"ledger_index": "closed"})", + !isClioOnly, + 1, + !isAdmin, + shouldForward}, + {"ShouldForwardReturnsTrueIfAccountInfoWithQueueSpecified", + 2u, + "account_info", + R"({"queue": true})", + !isClioOnly, + 1, + !isAdmin, + shouldForward}, + {"ShouldForwardReturnsFalseIfAccountInfoQueueIsFalse", + 2u, + "account_info", + R"({"queue": false})", + !isClioOnly, + 1, + !isAdmin, + !shouldForward}, + {"ShouldForwardReturnsTrueIfLedgerWithQueueSpecified", + 2u, + "ledger", + R"({"queue": true})", + !isClioOnly, + 1, + !isAdmin, + shouldForward}, + {"ShouldForwardReturnsFalseIfLedgerQueueIsFalse", + 2u, + "ledger", + R"({"queue": false})", + !isClioOnly, + 1, + !isAdmin, + !shouldForward}, + {"ShouldNotForwardReturnsTrueIfAPIVersionIsV1", + 1u, + "api_version_check", + "{}", + !isClioOnly, + 1, + !isAdmin, + !shouldForward}, + {"ShouldForwardReturnsFalseIfAPIVersionIsV2", + 2u, + "api_version_check", + "{}", + !isClioOnly, + 1, + !isAdmin, + !shouldForward}, + {"ShouldNeverForwardSubscribe", 1u, "subscribe", "{}", !isClioOnly, 0, !isAdmin, !shouldForward}, + {"ShouldNeverForwardUnsubscribe", 1u, "unsubscribe", "{}", !isClioOnly, 0, !isAdmin, !shouldForward}, + {"ForceForwardTrue", 1u, "any_method", R"({"force_forward": true})", !isClioOnly, 1, isAdmin, shouldForward}, + {"ForceForwardFalse", 1u, "any_method", R"({"force_forward": false})", !isClioOnly, 1, isAdmin, !shouldForward}, + {"ForceForwardNotAdmin", + 1u, + "any_method", + R"({"force_forward": true})", + !isClioOnly, + 1, + !isAdmin, + !shouldForward}, + {"ForceForwardSubscribe", + 1u, + "subscribe", + R"({"force_forward": true})", + !isClioOnly, + 0, + isAdmin, + not shouldForward}, + {"ForceForwardUnsubscribe", + 1u, + "unsubscribe", + R"({"force_forward": true})", + !isClioOnly, + 0, + isAdmin, + !shouldForward}, + {"ForceForwardClioOnly", + 1u, + "clio_only_method", + R"({"force_forward": true})", + isClioOnly, + 1, + isAdmin, + !shouldForward}, + }; +} + +INSTANTIATE_TEST_CASE_P( + ShouldForwardTest, + ShouldForwardParameterTest, + ValuesIn(generateTestValuesForParametersTest()), + tests::util::NameGenerator +); + +TEST_P(ShouldForwardParameterTest, Test) +{ + auto const testBundle = GetParam(); auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "test"; - auto const params = json::parse("{}"); + auto const apiVersion = testBundle.apiVersion; + auto const method = testBundle.method; + auto const params = json::parse(testBundle.testJson); - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(true)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); + ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(testBundle.mockedIsClioOnly)); + EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(testBundle.called); runSpawn([&](auto yield) { auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); + auto const ctx = web::Context( + yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, testBundle.isAdmin + ); auto const res = proxy.shouldForward(ctx); - ASSERT_FALSE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfProxied) -{ - auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "submit"; - auto const params = json::parse("{}"); - - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_TRUE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfCurrentLedgerSpecified) -{ - auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "anymethod"; - auto const params = json::parse(R"({"ledger_index": "current"})"); - - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_TRUE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfClosedLedgerSpecified) -{ - auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "anymethod"; - auto const params = json::parse(R"({"ledger_index": "closed"})"); - - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_TRUE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfAccountInfoWithQueueSpecified) -{ - auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "account_info"; - auto const params = json::parse(R"({"queue": true})"); - - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_TRUE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfAccountInfoQueueIsFalse) -{ - auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "account_info"; - auto const params = json::parse(R"({"queue": false})"); - - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_FALSE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsTrueIfLedgerWithQueueSpecified) -{ - auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "ledger"; - auto const params = json::parse(R"({"queue": true})"); - - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_TRUE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfLedgerQueueIsFalse) -{ - auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "ledger"; - auto const params = json::parse(R"({"queue": false})"); - - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_FALSE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldNotForwardReturnsTrueIfAPIVersionIsV1) -{ - auto const apiVersion = 1u; - auto const method = "api_version_check"; - auto const params = json::parse("{}"); - - auto const rawHandlerProviderPtr = handlerProvider.get(); - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_FALSE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldForwardReturnsFalseIfAPIVersionIsV2) -{ - auto const rawHandlerProviderPtr = handlerProvider.get(); - auto const apiVersion = 2u; - auto const method = "api_version_check"; - auto const params = json::parse("{}"); - - ON_CALL(*rawHandlerProviderPtr, isClioOnly(_)).WillByDefault(Return(false)); - EXPECT_CALL(*rawHandlerProviderPtr, isClioOnly(method)).Times(1); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_FALSE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldNeverForwardSubscribe) -{ - auto const apiVersion = 1u; - auto const method = "subscribe"; - auto const params = json::parse("{}"); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_FALSE(res); - }); -} - -TEST_F(RPCForwardingProxyTest, ShouldNeverForwardUnsubscribe) -{ - auto const apiVersion = 1u; - auto const method = "unsubscribe"; - auto const params = json::parse("{}"); - - runSpawn([&](auto yield) { - auto const range = backend->fetchLedgerRange(); - auto const ctx = - web::Context(yield, method, apiVersion, params.as_object(), nullptr, tagFactory, *range, CLIENT_IP, true); - - auto const res = proxy.shouldForward(ctx); - ASSERT_FALSE(res); + ASSERT_EQ(res, testBundle.expected); }); }