mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-12 19:20:42 +02:00
Merge 10615 via multiwallet_rpc-28+knots
This commit is contained in:
commit
962511f17b
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -676,7 +676,14 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||
|
||||
argsman.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid values for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0), a network/CIDR (e.g. 1.2.3.4/24), all ipv4 (0.0.0.0/0), or all ipv6 (::/0). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcauth=<userpw>[:wallet]",
|
||||
"Username and HMAC-SHA-256 hashed password for JSON-RPC connections. "
|
||||
"The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. "
|
||||
"A canonical python script is included in share/rpcauth. "
|
||||
"The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. "
|
||||
"A single wallet name can also be specified to restrict access to only that wallet, or '-' to deny all wallet access. "
|
||||
"This option can be specified multiple times",
|
||||
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcauthfile=<userpw>", "A file with a single lines with same format as rpcauth. This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC);
|
||||
argsman.AddArg("-rpcdoccheck", strprintf("Throw a non-fatal error at runtime if the documentation for an RPC is incorrect (default: %u)", DEFAULT_RPC_DOC_CHECK), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
|
||||
|
@ -343,6 +343,7 @@ public:
|
||||
req.params = params;
|
||||
req.strMethod = command;
|
||||
req.URI = uri;
|
||||
req.m_wallet_restriction.clear();
|
||||
return ::tableRPC.execute(req);
|
||||
}
|
||||
std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); }
|
||||
|
@ -3311,6 +3311,8 @@ static RPCHelpMan dumptxoutset()
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
EnsureNotWalletRestricted(request);
|
||||
|
||||
// handle optional ASCII parameters
|
||||
const bool is_human_readable = !request.params[1].isNull();
|
||||
const bool show_header = request.params[2].isNull() || request.params[2].get_bool();
|
||||
|
@ -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;
|
||||
|
@ -1455,3 +1455,19 @@ 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;
|
||||
}
|
||||
|
||||
void EnsureNotWalletRestricted(const JSONRPCRequest& request)
|
||||
{
|
||||
std::string authorized_wallet_name;
|
||||
const bool have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
|
||||
if (have_wallet_restriction) {
|
||||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not available for wallet-restricted RPC users");
|
||||
}
|
||||
}
|
||||
|
@ -545,4 +545,7 @@ std::vector<RPCResult> ScriptPubKeyDoc();
|
||||
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);
|
||||
void EnsureNotWalletRestricted(const JSONRPCRequest& request);
|
||||
|
||||
#endif // BITCOIN_RPC_UTIL_H
|
||||
|
@ -545,6 +545,8 @@ RPCHelpMan importwallet()
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
EnsureNotWalletRestricted(request);
|
||||
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return UniValue::VNULL;
|
||||
|
||||
@ -784,6 +786,8 @@ RPCHelpMan dumpwallet()
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
EnsureNotWalletRestricted(request);
|
||||
|
||||
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return UniValue::VNULL;
|
||||
|
||||
@ -1995,6 +1999,8 @@ RPCHelpMan backupwallet()
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
EnsureNotWalletRestricted(request);
|
||||
|
||||
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return UniValue::VNULL;
|
||||
|
||||
@ -2045,6 +2051,7 @@ RPCHelpMan restorewallet()
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
EnsureNotWalletRestricted(request);
|
||||
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
|
||||
|
@ -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>
|
||||
@ -64,18 +65,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)");
|
||||
}
|
||||
|
@ -245,6 +245,14 @@ static RPCHelpMan loadwallet()
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
const std::string name(request.params[0].get_str());
|
||||
|
||||
{
|
||||
std::string authorized_wallet_name;
|
||||
const bool have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
|
||||
if (have_wallet_restriction && authorized_wallet_name != name) {
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet usage is restricted.");
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
ReadDatabaseArgs(*context.args, options);
|
||||
@ -385,6 +393,14 @@ static RPCHelpMan createwallet()
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
{
|
||||
std::string authorized_wallet_name;
|
||||
const bool have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
|
||||
if (have_wallet_restriction && authorized_wallet_name != request.params[0].get_str()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet usage is restricted.");
|
||||
}
|
||||
}
|
||||
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
uint64_t flags = 0;
|
||||
if (!request.params[1].isNull() && request.params[1].get_bool()) {
|
||||
@ -487,7 +503,14 @@ static RPCHelpMan unloadwallet()
|
||||
}
|
||||
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
|
||||
std::shared_ptr<CWallet> wallet;
|
||||
{
|
||||
std::string authorized_wallet_name;
|
||||
const bool have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
|
||||
if ((!have_wallet_restriction) || authorized_wallet_name == wallet_name) {
|
||||
wallet = GetWallet(context, wallet_name);
|
||||
}
|
||||
}
|
||||
if (!wallet) {
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
|
||||
}
|
||||
@ -789,6 +812,9 @@ static RPCHelpMan migratewallet()
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
// New wallets do not necessarily have the same name as the migrated wallet
|
||||
EnsureNotWalletRestricted(request);
|
||||
|
||||
std::string wallet_name;
|
||||
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
|
||||
if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
|
||||
|
@ -239,6 +239,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));
|
||||
|
||||
@ -294,6 +295,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);
|
||||
|
||||
@ -312,6 +314,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);
|
||||
|
@ -23,19 +23,23 @@ import sys
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def call_with_auth(node, user, password, method="getbestblockhash"):
|
||||
def call_with_auth(node, user, password, *, uripath='/', method='getbestblockhash'):
|
||||
url = urllib.parse.urlparse(node.url)
|
||||
headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))}
|
||||
|
||||
conn = http.client.HTTPConnection(url.hostname, url.port)
|
||||
conn.connect()
|
||||
conn.request('POST', '/', f'{{"method": "{method}"}}', headers)
|
||||
conn.request('POST', uripath, f'{{"method": "{method}"}}', headers)
|
||||
resp = conn.getresponse()
|
||||
resp.data = resp.read()
|
||||
conn.close()
|
||||
return resp
|
||||
|
||||
|
||||
class HTTPBasicsTest(BitcoinTestFramework):
|
||||
def add_options(self, parser):
|
||||
self.add_wallet_options(parser)
|
||||
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 2
|
||||
self.supports_cli = False
|
||||
@ -86,15 +90,34 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
lines = p.stdout.read().splitlines()
|
||||
self.authinfo.append( (username, lines[1]) )
|
||||
|
||||
def gen_userpass(username_prefix, wallet_restrictions=None):
|
||||
username = username_prefix + '_' + ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
|
||||
p = subprocess.Popen([sys.executable, gen_rpcauth, username], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
lines = p.stdout.read().splitlines()
|
||||
assert "\n" not in lines[1]
|
||||
assert lines[1][:8] == 'rpcauth='
|
||||
config_line = lines[1]
|
||||
self.authinfo.append( (username, lines[3], wallet_restrictions) )
|
||||
if not (wallet_restrictions is None):
|
||||
config_line += ":" + wallet_restrictions
|
||||
return config_line + "\n"
|
||||
|
||||
# Hand-generated rpcauthfile with one entry and no newline
|
||||
username = 'rpcauth_nonewline_' + ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
|
||||
p = subprocess.Popen([sys.executable, gen_rpcauth, username], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
lines = p.stdout.read().splitlines()
|
||||
assert "\n" not in lines[1]
|
||||
assert lines[1][:8] == 'rpcauth='
|
||||
with open(Path(self.options.tmpdir) / 'rpcauth_nonewline', "a", encoding='utf8') as f:
|
||||
f.write(lines[1][8:])
|
||||
self.authinfo.append( (username, lines[3]) )
|
||||
f.write(gen_userpass('rpcauth_nonewline')[8:-1])
|
||||
|
||||
if self.is_wallet_compiled():
|
||||
# Hand-generated rpcauthfile with wallet restrictions
|
||||
with open(Path(self.options.tmpdir) / 'rpcauth_walletrestricted', "a", encoding='utf8') as f:
|
||||
f.write(gen_userpass('rpcauth_walletrestricted_allow_all', '')[8:])
|
||||
f.write(gen_userpass('rpcauth_walletrestricted_allow_none', '-')[8:])
|
||||
f.write(gen_userpass('rpcauth_walletrestricted_allow_one', 'limitedwallet1')[8:])
|
||||
# Uses the same username as a privileged one, but with different passwords:
|
||||
f.write(gen_userpass('rpcauth_walletrestricted_allow_all', 'limitedwallet1')[8:])
|
||||
f.write(gen_userpass('rpcauth_walletrestricted_allow_all', 'limitedwallet2')[8:])
|
||||
f.write(gen_userpass('rpcauth_walletrestricted_allow_all', '-')[8:])
|
||||
f.write(gen_userpass('rpcauth_walletrestricted_allow_one', 'limitedwallet2')[8:])
|
||||
f.write(gen_userpass('rpcauth_walletrestricted_allow_one', '-')[8:])
|
||||
|
||||
with open(self.nodes[0].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f:
|
||||
f.write(rpcauth + "\n")
|
||||
@ -103,14 +126,25 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
f.write("rpcauthfile=rpcauth_single\n")
|
||||
f.write("rpcauthfile=rpcauth_multi\n")
|
||||
f.write("rpcauthfile=rpcauth_nonewline\n")
|
||||
if self.is_wallet_compiled():
|
||||
f.write("rpcauthfile=rpcauth_walletrestricted\n")
|
||||
f.write(gen_userpass('rpcauth_walletrestricted2_allow_all', '') + "\n")
|
||||
f.write(gen_userpass('rpcauth_walletrestricted2_allow_none', '-') + "\n")
|
||||
f.write(gen_userpass('rpcauth_walletrestricted2_allow_one', 'limitedwallet1') + "\n")
|
||||
# Uses the same username as a privileged one, but with different passwords:
|
||||
f.write(gen_userpass('rpcauth_walletrestricted2_allow_all', 'limitedwallet1') + "\n")
|
||||
f.write(gen_userpass('rpcauth_walletrestricted2_allow_all', 'limitedwallet2') + "\n")
|
||||
f.write(gen_userpass('rpcauth_walletrestricted2_allow_all', '-') + "\n")
|
||||
f.write(gen_userpass('rpcauth_walletrestricted2_allow_one', 'limitedwallet2') + "\n")
|
||||
f.write(gen_userpass('rpcauth_walletrestricted2_allow_one', '-') + "\n")
|
||||
with open(self.nodes[1].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f:
|
||||
f.write("rpcuser={}\n".format(self.rpcuser))
|
||||
f.write("rpcpassword={}\n".format(self.rpcpassword))
|
||||
self.restart_node(0)
|
||||
self.restart_node(1)
|
||||
|
||||
def test_auth(self, node, user, password):
|
||||
self.log.info('Correct...')
|
||||
def test_auth(self, node, user, password, wallet_restrictions=None):
|
||||
self.log.info('Correct... %s (wallet_restrictions=%s)' % (user, wallet_restrictions))
|
||||
assert_equal(200, call_with_auth(node, user, password).status)
|
||||
|
||||
self.log.info('Wrong...')
|
||||
@ -122,6 +156,17 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
self.log.info('Wrong...')
|
||||
assert_equal(401, call_with_auth(node, user + 'wrong', password + 'wrong').status)
|
||||
|
||||
if not (wallet_restrictions is None):
|
||||
for n in range(1, 3):
|
||||
wallet_name = f'limitedwallet{n}'
|
||||
self.log.info(f'{wallet_name}...')
|
||||
resp = call_with_auth(node, user, password, uripath=f'/wallet/{wallet_name}', method='getwalletinfo')
|
||||
if wallet_restrictions in ('', f'{wallet_name}'):
|
||||
assert_equal(200, resp.status)
|
||||
else:
|
||||
assert_equal(500, resp.status)
|
||||
assert b'"Requested wallet does not exist or is not loaded"' in resp.data
|
||||
|
||||
def test_rpccookieperms(self):
|
||||
p = {"owner": 0o600, "group": 0o640, "all": 0o644}
|
||||
|
||||
@ -180,6 +225,10 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
self.log.info('Check correctness of the rpcauth config option')
|
||||
url = urllib.parse.urlparse(self.nodes[0].url)
|
||||
|
||||
if self.is_wallet_compiled():
|
||||
self.nodes[0].createwallet('limitedwallet1')
|
||||
self.nodes[0].createwallet('limitedwallet2')
|
||||
|
||||
self.test_auth(self.nodes[0], url.username, url.password)
|
||||
self.test_auth(self.nodes[0], 'rt', self.rtpassword)
|
||||
self.test_auth(self.nodes[0], 'rt2', self.rt2password)
|
||||
@ -204,33 +253,33 @@ class HTTPBasicsTest(BitcoinTestFramework):
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info).status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info[:2]).status)
|
||||
|
||||
self.log.info('Check -norpcauth disables all previous -rpcauth* params')
|
||||
self.restart_node(0, extra_args=[rpcauth_def, '-norpcauth'])
|
||||
assert_equal(401, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(401, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(401, call_with_auth(self.nodes[0], *info).status)
|
||||
assert_equal(401, call_with_auth(self.nodes[0], *info[:2]).status)
|
||||
|
||||
self.log.info('Check -norpcauth can be reversed with -rpcauth')
|
||||
self.restart_node(0, extra_args=[rpcauth_def, '-norpcauth', '-rpcauth'])
|
||||
# FIXME: assert_equal(200, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info).status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info[:2]).status)
|
||||
|
||||
self.log.info('Check -norpcauth followed by a specific -rpcauth=* restores config file -rpcauth=* values too')
|
||||
self.restart_node(0, extra_args=[rpcauth_def, '-norpcauth', rpcauth_abc])
|
||||
assert_equal(401, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info).status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info[:2]).status)
|
||||
self.restart_node(0, extra_args=[rpcauth_def, '-norpcauth', '-rpcauth='])
|
||||
assert_equal(401, call_with_auth(self.nodes[0], 'def', 'abc').status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], 'rt', self.rtpassword).status)
|
||||
for info in self.authinfo:
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info).status)
|
||||
assert_equal(200, call_with_auth(self.nodes[0], *info[:2]).status)
|
||||
|
||||
self.log.info('Check -rpcauth are validated')
|
||||
self.stop_node(0)
|
||||
|
Loading…
Reference in New Issue
Block a user