RPC: Allow rpcauth configs to specify a 4th parameter naming a specific wallet

This commit is contained in:
Luke Dashjr 2016-09-09 11:15:09 +00:00
parent 38698fecf6
commit 03e4671446
6 changed files with 74 additions and 26 deletions

View File

@ -102,7 +102,7 @@ static void JSONErrorReply(HTTPRequest* req, UniValue objError, const JSONRPCReq
//This function checks username and password against -rpcauth
//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) {
return false;
@ -127,13 +127,14 @@ static bool multiUserAuthorized(std::string strUserPass)
std::string strHashFromPass = HexStr(hexvec);
if (TimingResistantEqual(strHashFromPass, strHash)) {
out_wallet_restriction = (vFields.size() > 3) ? vFields[3] : "";
return true;
}
}
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 ")
return false;
@ -149,9 +150,10 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna
// Check if authorized under single-user field.
// (strRPCUserColonPass is empty when -norpccookiefile is specified).
if (!strRPCUserColonPass.empty() && TimingResistantEqual(strUserPass, strRPCUserColonPass)) {
out_wallet_restriction = "";
return true;
}
return multiUserAuthorized(strUserPass);
return multiUserAuthorized(strUserPass, out_wallet_restriction);
}
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;
jreq.context = context;
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);
/* 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");
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()))) {
LogPrintf("Using rpcauth authentication.\n");
for (const std::string& rpcauth : gArgs.GetArgs("-rpcauth")) {
if (rpcauth.empty()) continue;
std::vector<std::string> fields{SplitString(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 {
if (!AddRPCAuth(rpcauth)) {
LogPrintf("Invalid -rpcauth argument.\n");
return false;
}
@ -338,13 +349,11 @@ static bool InitRPCAuthentication()
file.open(path);
if (!file.is_open()) continue;
std::string rpcauth;
size_t lineno = 0;
while (std::getline(file, rpcauth)) {
std::vector<std::string> fields{SplitString(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);
++lineno;
if (!AddRPCAuth(rpcauth)) {
LogPrintf("WARNING: Invalid line %s in -rpcauthfile=%s; ignoring\n", lineno, path);
}
}
}

View File

@ -41,6 +41,7 @@ public:
enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE;
std::string URI;
std::string authUser;
std::string m_wallet_restriction{"-"};
std::string peerAddr;
std::any context;
JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY;

View File

@ -1389,3 +1389,10 @@ void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj)
if (warnings.empty()) return;
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;
}

View File

@ -503,4 +503,6 @@ private:
void PushWarnings(const UniValue& 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

View File

@ -5,6 +5,7 @@
#include <wallet/rpc/util.h>
#include <common/url.h>
#include <httprpc.h>
#include <rpc/util.h>
#include <util/any.h>
#include <util/translation.h>
@ -75,18 +76,43 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques
CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE);
WalletContext& context = EnsureWalletContext(request.context);
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
std::shared_ptr<CWallet> pwallet = GetWallet(context, wallet_name);
if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
bool have_wallet_restriction;
std::string authorized_wallet_name;
have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
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;
}
size_t count{0};
auto wallet = GetDefaultWallet(context, count);
if (wallet) return wallet;
if (count == 0) {
if (have_requested_wallet) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
}
if (have_wallet_restriction
? (authorized_wallet_name == "-" || !GetWallet(context, authorized_wallet_name))
: (count == 0)
) {
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)");
}

View File

@ -238,6 +238,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
keys.push_back(std::move(key));
JSONRPCRequest request;
request.context = &context;
request.m_wallet_restriction = "";
request.params.setArray();
request.params.push_back(std::move(keys));
@ -293,6 +294,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
}
JSONRPCRequest request;
request.context = &context;
request.m_wallet_restriction = "";
request.params.setArray();
request.params.push_back(backup_file);
@ -311,6 +313,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
context.args = &m_args;
JSONRPCRequest request;
request.context = &context;
request.m_wallet_restriction = "";
request.params.setArray();
request.params.push_back(backup_file);
AddWallet(context, wallet);