mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-29 13:32:33 +02:00
RPC: Allow rpcauth configs to specify a 4th parameter naming a specific wallet
This commit is contained in:
parent
38698fecf6
commit
03e4671446
@ -102,7 +102,7 @@ static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCReq
|
|||||||
|
|
||||||
//This function checks username and password against -rpcauth
|
//This function checks username and password against -rpcauth
|
||||||
//entries from config file.
|
//entries from config file.
|
||||||
static bool multiUserAuthorized(std::string strUserPass)
|
static bool multiUserAuthorized(std::string strUserPass, std::string& out_wallet_restriction)
|
||||||
{
|
{
|
||||||
if (strUserPass.find(':') == std::string::npos) {
|
if (strUserPass.find(':') == std::string::npos) {
|
||||||
return false;
|
return false;
|
||||||
@ -127,13 +127,14 @@ static bool multiUserAuthorized(std::string strUserPass)
|
|||||||
std::string strHashFromPass = HexStr(hexvec);
|
std::string strHashFromPass = HexStr(hexvec);
|
||||||
|
|
||||||
if (TimingResistantEqual(strHashFromPass, strHash)) {
|
if (TimingResistantEqual(strHashFromPass, strHash)) {
|
||||||
|
out_wallet_restriction = (vFields.size() > 3) ? vFields[3] : "";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut)
|
static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUsernameOut, std::string& out_wallet_restriction)
|
||||||
{
|
{
|
||||||
if (strAuth.substr(0, 6) != "Basic ")
|
if (strAuth.substr(0, 6) != "Basic ")
|
||||||
return false;
|
return false;
|
||||||
@ -149,9 +150,10 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna
|
|||||||
// Check if authorized under single-user field.
|
// Check if authorized under single-user field.
|
||||||
// (strRPCUserColonPass is empty when -norpccookiefile is specified).
|
// (strRPCUserColonPass is empty when -norpccookiefile is specified).
|
||||||
if (!strRPCUserColonPass.empty() && TimingResistantEqual(strUserPass, strRPCUserColonPass)) {
|
if (!strRPCUserColonPass.empty() && TimingResistantEqual(strUserPass, strRPCUserColonPass)) {
|
||||||
|
out_wallet_restriction = "";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return multiUserAuthorized(strUserPass);
|
return multiUserAuthorized(strUserPass, out_wallet_restriction);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
|
static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
|
||||||
@ -172,7 +174,7 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
|
|||||||
JSONRPCRequest jreq;
|
JSONRPCRequest jreq;
|
||||||
jreq.context = context;
|
jreq.context = context;
|
||||||
jreq.peerAddr = req->GetPeer().ToStringAddrPort();
|
jreq.peerAddr = req->GetPeer().ToStringAddrPort();
|
||||||
if (!RPCAuthorized(authHeader.second, jreq.authUser)) {
|
if (!RPCAuthorized(authHeader.second, jreq.authUser, jreq.m_wallet_restriction)) {
|
||||||
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr);
|
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", jreq.peerAddr);
|
||||||
|
|
||||||
/* Deter brute-forcing
|
/* Deter brute-forcing
|
||||||
@ -318,17 +320,26 @@ static bool InitRPCAuthentication()
|
|||||||
LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n");
|
LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n");
|
||||||
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
|
strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
|
||||||
}
|
}
|
||||||
|
constexpr auto AddRPCAuth = [](const std::string& rpcauth) {
|
||||||
|
std::vector<std::string> fields{SplitString(rpcauth, ':')};
|
||||||
|
if (fields.size() < 2 || fields.size() > 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::vector<std::string> salt_hmac{SplitString(fields[1], '$')};
|
||||||
|
if (salt_hmac.size() == 2) {
|
||||||
|
fields.erase(fields.begin() + 1);
|
||||||
|
fields.insert(fields.begin() + 1, salt_hmac.begin(), salt_hmac.end());
|
||||||
|
g_rpcauth.push_back(fields);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
if (!(gArgs.IsArgNegated("-rpcauth") || (gArgs.GetArgs("-rpcauth").empty() && gArgs.GetArgs("-rpcauthfile").empty()))) {
|
if (!(gArgs.IsArgNegated("-rpcauth") || (gArgs.GetArgs("-rpcauth").empty() && gArgs.GetArgs("-rpcauthfile").empty()))) {
|
||||||
LogPrintf("Using rpcauth authentication.\n");
|
LogPrintf("Using rpcauth authentication.\n");
|
||||||
for (const std::string& rpcauth : gArgs.GetArgs("-rpcauth")) {
|
for (const std::string& rpcauth : gArgs.GetArgs("-rpcauth")) {
|
||||||
if (rpcauth.empty()) continue;
|
if (rpcauth.empty()) continue;
|
||||||
std::vector<std::string> fields{SplitString(rpcauth, ':')};
|
if (!AddRPCAuth(rpcauth)) {
|
||||||
const std::vector<std::string> salt_hmac{SplitString(fields.back(), '$')};
|
|
||||||
if (fields.size() == 2 && salt_hmac.size() == 2) {
|
|
||||||
fields.pop_back();
|
|
||||||
fields.insert(fields.end(), salt_hmac.begin(), salt_hmac.end());
|
|
||||||
g_rpcauth.push_back(fields);
|
|
||||||
} else {
|
|
||||||
LogPrintf("Invalid -rpcauth argument.\n");
|
LogPrintf("Invalid -rpcauth argument.\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -338,13 +349,11 @@ static bool InitRPCAuthentication()
|
|||||||
file.open(path);
|
file.open(path);
|
||||||
if (!file.is_open()) continue;
|
if (!file.is_open()) continue;
|
||||||
std::string rpcauth;
|
std::string rpcauth;
|
||||||
|
size_t lineno = 0;
|
||||||
while (std::getline(file, rpcauth)) {
|
while (std::getline(file, rpcauth)) {
|
||||||
std::vector<std::string> fields{SplitString(rpcauth, ':')};
|
++lineno;
|
||||||
const std::vector<std::string> salt_hmac{SplitString(fields.back(), '$')};
|
if (!AddRPCAuth(rpcauth)) {
|
||||||
if (fields.size() == 2 && salt_hmac.size() == 2) {
|
LogPrintf("WARNING: Invalid line %s in -rpcauthfile=%s; ignoring\n", lineno, path);
|
||||||
fields.pop_back();
|
|
||||||
fields.insert(fields.end(), salt_hmac.begin(), salt_hmac.end());
|
|
||||||
g_rpcauth.push_back(fields);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ public:
|
|||||||
enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE;
|
enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE;
|
||||||
std::string URI;
|
std::string URI;
|
||||||
std::string authUser;
|
std::string authUser;
|
||||||
|
std::string m_wallet_restriction{"-"};
|
||||||
std::string peerAddr;
|
std::string peerAddr;
|
||||||
std::any context;
|
std::any context;
|
||||||
JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY;
|
JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY;
|
||||||
|
@ -1389,3 +1389,10 @@ void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj)
|
|||||||
if (warnings.empty()) return;
|
if (warnings.empty()) return;
|
||||||
obj.pushKV("warnings", BilingualStringsToUniValue(warnings));
|
obj.pushKV("warnings", BilingualStringsToUniValue(warnings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GetWalletRestrictionFromJSONRPCRequest(const JSONRPCRequest& request, std::string& out_wallet_allowed)
|
||||||
|
{
|
||||||
|
if (request.m_wallet_restriction.empty()) return false;
|
||||||
|
out_wallet_allowed = request.m_wallet_restriction;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -503,4 +503,6 @@ private:
|
|||||||
void PushWarnings(const UniValue& warnings, UniValue& obj);
|
void PushWarnings(const UniValue& warnings, UniValue& obj);
|
||||||
void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj);
|
void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj);
|
||||||
|
|
||||||
|
bool GetWalletRestrictionFromJSONRPCRequest(const JSONRPCRequest& request, std::string& out_wallet_allowed);
|
||||||
|
|
||||||
#endif // BITCOIN_RPC_UTIL_H
|
#endif // BITCOIN_RPC_UTIL_H
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <wallet/rpc/util.h>
|
#include <wallet/rpc/util.h>
|
||||||
|
|
||||||
#include <common/url.h>
|
#include <common/url.h>
|
||||||
|
#include <httprpc.h>
|
||||||
#include <rpc/util.h>
|
#include <rpc/util.h>
|
||||||
#include <util/any.h>
|
#include <util/any.h>
|
||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
@ -75,18 +76,43 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques
|
|||||||
CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE);
|
CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE);
|
||||||
WalletContext& context = EnsureWalletContext(request.context);
|
WalletContext& context = EnsureWalletContext(request.context);
|
||||||
|
|
||||||
std::string wallet_name;
|
bool have_wallet_restriction;
|
||||||
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
|
std::string authorized_wallet_name;
|
||||||
std::shared_ptr<CWallet> pwallet = GetWallet(context, wallet_name);
|
have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
|
||||||
if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
|
|
||||||
|
bool have_requested_wallet;
|
||||||
|
std::string requested_wallet_name;
|
||||||
|
have_requested_wallet = GetWalletNameFromJSONRPCRequest(request, requested_wallet_name);
|
||||||
|
|
||||||
|
std::shared_ptr<CWallet> pwallet;
|
||||||
|
size_t count{0};
|
||||||
|
|
||||||
|
if (!have_wallet_restriction) {
|
||||||
|
// Any wallet is permitted; select by endpoint, or use the sole wallet
|
||||||
|
if (have_requested_wallet) {
|
||||||
|
pwallet = GetWallet(context, requested_wallet_name);
|
||||||
|
} else {
|
||||||
|
auto wallet = GetDefaultWallet(context, count);
|
||||||
|
if (wallet) pwallet = wallet;
|
||||||
|
}
|
||||||
|
} else if (authorized_wallet_name == "-") {
|
||||||
|
// Block wallet access always
|
||||||
|
} else if ((!have_requested_wallet) || requested_wallet_name == authorized_wallet_name) {
|
||||||
|
// Select specifically the authorized wallet
|
||||||
|
pwallet = GetWallet(context, authorized_wallet_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pwallet) {
|
||||||
return pwallet;
|
return pwallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t count{0};
|
if (have_requested_wallet) {
|
||||||
auto wallet = GetDefaultWallet(context, count);
|
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
|
||||||
if (wallet) return wallet;
|
}
|
||||||
|
if (have_wallet_restriction
|
||||||
if (count == 0) {
|
? (authorized_wallet_name == "-" || !GetWallet(context, authorized_wallet_name))
|
||||||
|
: (count == 0)
|
||||||
|
) {
|
||||||
throw JSONRPCError(
|
throw JSONRPCError(
|
||||||
RPC_WALLET_NOT_FOUND, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)");
|
RPC_WALLET_NOT_FOUND, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)");
|
||||||
}
|
}
|
||||||
|
@ -238,6 +238,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
|
|||||||
keys.push_back(std::move(key));
|
keys.push_back(std::move(key));
|
||||||
JSONRPCRequest request;
|
JSONRPCRequest request;
|
||||||
request.context = &context;
|
request.context = &context;
|
||||||
|
request.m_wallet_restriction = "";
|
||||||
request.params.setArray();
|
request.params.setArray();
|
||||||
request.params.push_back(std::move(keys));
|
request.params.push_back(std::move(keys));
|
||||||
|
|
||||||
@ -293,6 +294,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
|||||||
}
|
}
|
||||||
JSONRPCRequest request;
|
JSONRPCRequest request;
|
||||||
request.context = &context;
|
request.context = &context;
|
||||||
|
request.m_wallet_restriction = "";
|
||||||
request.params.setArray();
|
request.params.setArray();
|
||||||
request.params.push_back(backup_file);
|
request.params.push_back(backup_file);
|
||||||
|
|
||||||
@ -311,6 +313,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
|||||||
context.args = &m_args;
|
context.args = &m_args;
|
||||||
JSONRPCRequest request;
|
JSONRPCRequest request;
|
||||||
request.context = &context;
|
request.context = &context;
|
||||||
|
request.m_wallet_restriction = "";
|
||||||
request.params.setArray();
|
request.params.setArray();
|
||||||
request.params.push_back(backup_file);
|
request.params.push_back(backup_file);
|
||||||
AddWallet(context, wallet);
|
AddWallet(context, wallet);
|
||||||
|
Loading…
Reference in New Issue
Block a user