diff --git a/example-config.json b/example-config.json index f9bea4dd..504d1ba5 100644 --- a/example-config.json +++ b/example-config.json @@ -66,7 +66,7 @@ // Max number of requests to queue up before rejecting further requests. // Defaults to 0, which disables the limit. "max_queue_size": 500, - // If request contains header with authorization, Clio will check if it matches this value's sha256 hash + // If request contains header with authorization, Clio will check if it matches the prefix 'Password ' + this value's sha256 hash // If matches, the request will be considered as admin request "admin_password": "xrp", // If local_admin is true, Clio will consider requests come from 127.0.0.1 as admin requests diff --git a/src/rpc/handlers/Tx.h b/src/rpc/handlers/Tx.h index ac0294b3..9c04db91 100644 --- a/src/rpc/handlers/Tx.h +++ b/src/rpc/handlers/Tx.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace rpc { @@ -224,8 +225,10 @@ private: if (jsonObject.contains(JS(transaction))) input.transaction = jv.at(JS(transaction)).as_string().c_str(); - if (jsonObject.contains(JS(ctid))) + if (jsonObject.contains(JS(ctid))) { input.ctid = jv.at(JS(ctid)).as_string().c_str(); + input.ctid = util::toUpper(*input.ctid); + } if (jsonObject.contains(JS(binary))) input.binary = jv.at(JS(binary)).as_bool(); diff --git a/src/util/JsonUtils.h b/src/util/JsonUtils.h index 3941078a..803f139e 100644 --- a/src/util/JsonUtils.h +++ b/src/util/JsonUtils.h @@ -37,6 +37,13 @@ toLower(std::string str) return str; } +inline std::string +toUpper(std::string str) +{ + std::transform(std::begin(str), std::end(str), std::begin(str), [](unsigned char c) { return std::toupper(c); }); + return str; +} + /** * @brief Removes any detected secret information from a response JSON object. * diff --git a/src/web/impl/AdminVerificationStrategy.cpp b/src/web/impl/AdminVerificationStrategy.cpp index 76078884..3763f97b 100644 --- a/src/web/impl/AdminVerificationStrategy.cpp +++ b/src/web/impl/AdminVerificationStrategy.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include @@ -39,7 +40,7 @@ PasswordAdminVerificationStrategy::PasswordAdminVerificationStrategy(std::string std::memcpy(sha256.data(), d.data(), d.size()); passwordSha256_ = ripple::to_string(sha256); // make sure it's uppercase - std::transform(passwordSha256_.begin(), passwordSha256_.end(), passwordSha256_.begin(), ::toupper); + passwordSha256_ = util::toUpper(std::move(passwordSha256_)); } bool @@ -55,11 +56,9 @@ PasswordAdminVerificationStrategy::isAdmin(RequestType const& request, std::stri // Invalid Authorization header return false; } + userAuth.remove_prefix(passwordPrefix.size()); - std::string userPasswordHash; - userPasswordHash.reserve(userAuth.size()); - std::transform(userAuth.begin(), userAuth.end(), std::back_inserter(userPasswordHash), ::toupper); - return passwordSha256_ == userPasswordHash; + return passwordSha256_ == util::toUpper(userAuth); } std::shared_ptr diff --git a/unittests/rpc/handlers/TxTests.cpp b/unittests/rpc/handlers/TxTests.cpp index ccde75ce..f22ed5ff 100644 --- a/unittests/rpc/handlers/TxTests.cpp +++ b/unittests/rpc/handlers/TxTests.cpp @@ -913,3 +913,42 @@ TEST_F(RPCTxTest, ViaCTID) EXPECT_EQ(*output, json::parse(OUT)); }); } + +TEST_F(RPCTxTest, ViaLowercaseCTID) +{ + auto const rawBackendPtr = dynamic_cast(mockBackendPtr.get()); + TransactionAndMetadata tx1; + tx1.metadata = CreateMetaDataForCreateOffer(CURRENCY, ACCOUNT, 1, 200, 300).getSerializer().peekData(); + tx1.transaction = + CreateCreateOfferTransactionObject(ACCOUNT, 2, 100, CURRENCY, ACCOUNT2, 200, 300).getSerializer().peekData(); + tx1.date = 123456; + tx1.ledgerSequence = SEQ_FROM_CTID; + + TransactionAndMetadata tx2; + tx2.transaction = CreatePaymentTransactionObject(ACCOUNT, ACCOUNT2, 2, 3, 300).getSerializer().peekData(); + tx2.metadata = CreatePaymentTransactionMetaObject(ACCOUNT, ACCOUNT2, 110, 30).getSerializer().peekData(); + tx2.ledgerSequence = SEQ_FROM_CTID; + + EXPECT_CALL(*rawBackendPtr, fetchAllTransactionsInLedger(SEQ_FROM_CTID, _)).WillOnce(Return(std::vector{tx1, tx2})); + + auto const rawETLPtr = dynamic_cast(mockETLServicePtr.get()); + ASSERT_NE(rawETLPtr, nullptr); + EXPECT_CALL(*rawETLPtr, getETLState).WillOnce(Return(etl::ETLState{.networkID = 2})); + + std::string ctid(CTID); + std::transform(ctid.begin(), ctid.end(), ctid.begin(), ::tolower); + + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{TestTxHandler{mockBackendPtr, mockETLServicePtr}}; + auto const req = json::parse(fmt::format( + R"({{ + "command": "tx", + "ctid": "{}" + }})", + ctid + )); + auto const output = handler.process(req, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(output->at("ctid").as_string(), CTID); + }); +}