diff --git a/src/httprpc.cpp b/src/httprpc.cpp index c3b6644090..9b72eb596e 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -76,6 +76,9 @@ static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const Uni nStatus = HTTP_BAD_REQUEST; else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; + else if (code == RPC_PLATFORM_RESTRICTION) { + nStatus = HTTP_FORBIDDEN; + } std::string strReply = JSONRPCReply(NullUniValue, objError, id); diff --git a/src/init.cpp b/src/init.cpp index 19cfff7960..f0c7dc09c7 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -613,6 +613,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageGroup(_("Masternode options:")); strUsage += HelpMessageOpt("-masternodeblsprivkey=", _("Set the masternode BLS private key and enable the client to act as a masternode")); + strUsage += HelpMessageOpt("-platform-user=", _("Set the username for the \"platform user\", a restricted user intended to be used by Dash Platform, to the specified username.")); strUsage += HelpMessageGroup(_("InstantSend options:")); strUsage += HelpMessageOpt("-instantsendnotify=", _("Execute command when a wallet InstantSend transaction is successfully locked (%s in cmd is replaced by TxID)")); diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index da81961450..5c545e0da8 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -57,6 +57,7 @@ enum RPCErrorCode RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Transaction already in chain RPC_IN_WARMUP = -28, //!< Client still warming up RPC_METHOD_DEPRECATED = -32, //!< RPC method is deprecated + RPC_PLATFORM_RESTRICTION = -33, //!< This RPC command cannot be run by platform-user //! Aliases for backward compatibility RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 3667aa9036..25cab87f52 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -34,6 +34,16 @@ static RPCTimerInterface* timerInterface = nullptr; /* Map of name to timer. */ static std::map > deadlineTimers; +// Any commands submitted by this user will have their commands filtered based on the platformAllowedCommands +static const std::string defaultPlatformUser = "platform-user"; + +static const std::map> platformAllowedCommands{ + {"getbestblockhash", {}}, + {"getblockhash", {}}, + {"getblockcount", {}}, + {"getbestchainlock", {}}, +}; + static struct CRPCSignals { boost::signals2::signal Started; @@ -550,6 +560,22 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const if (!pcmd) throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); + // Before executing the RPC Command, filter commands from platform rpc user + if (fMasternodeMode && request.authUser == gArgs.GetArg("-platform-user", defaultPlatformUser)) { + + auto it = platformAllowedCommands.find(request.strMethod); + // If the requested method is not available in platformAllowedCommands + if (it == platformAllowedCommands.end()) { + throw JSONRPCError(RPC_PLATFORM_RESTRICTION, strprintf("Method \"%s\" prohibited", request.strMethod)); + } + + const std::string strFirstParam = !request.params.empty() ? request.params[0].getValStr() : ""; + // If there are any parameter restrictions for the requested method make sure the first paramter is allowed + if (!it->second.empty() && it->second.count(strFirstParam) == 0) { + throw JSONRPCError(RPC_PLATFORM_RESTRICTION, strprintf("Parameter \"%s\" prohibited for method \"%s\"", strFirstParam, request.strMethod)); + } + } + g_rpcSignals.PreCommand(*pcmd); try diff --git a/test/functional/rpc_platform_filter.py b/test/functional/rpc_platform_filter.py new file mode 100755 index 0000000000..df952a77ae --- /dev/null +++ b/test/functional/rpc_platform_filter.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test that commands submitted by the platform user are filtered.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import str_to_b64str, assert_equal + +import http.client +import json +import os +import urllib.parse + + +class HTTPBasicsTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def setup_chain(self): + super().setup_chain() + # Append rpcauth to dash.conf before initialization + rpcauthplatform = "rpcauth=platform-user:dd88fd676186f48553775d6fb5a2d344$bc1f7898698ead19c6ec7ff47055622dd7101478f1ff6444103d3dc03cd77c13" + # rpcuser : platform-user + # rpcpassword : password123 + rpcauthoperator = "rpcauth=operator:e9b45dd0b61a7be72155535435365a3a$8fb7470bc6f74d8ceaf9a23f49b06127723bd563b3ed5d9cea776ef01803d191" + # rpcuser : operator + # rpcpassword : otherpassword + + masternodeblskey="masternodeblsprivkey=58af6e39bb4d86b22bda1a02b134c2f5b71caffa1377540b02f7f1ad122f59e0" + + with open(os.path.join(self.options.tmpdir+"/node0", "dash.conf"), 'a', encoding='utf8') as f: + f.write(masternodeblskey+"\n") + f.write(rpcauthplatform+"\n") + f.write(rpcauthoperator+"\n") + + def run_test(self): + + url = urllib.parse.urlparse(self.nodes[0].url) + + def test_command(method, params, auth, expexted_status): + conn = http.client.HTTPConnection(url.hostname, url.port) + conn.connect() + body = {"method": method} + if len(params): + body["params"] = params + conn.request('POST', '/', json.dumps(body), {"Authorization": "Basic " + str_to_b64str(auth)}) + resp = conn.getresponse() + assert_equal(resp.status, expexted_status) + conn.close() + + rpcuser_authpair_platform = "platform-user:password123" + rpcuser_authpair_operator = "operator:otherpassword" + rpcuser_authpair_wrong = "platform-user:rpcpasswordwrong" + + self.log.info('Try using a incorrect password for platform-user...') + test_command("getbestblockhash", [], rpcuser_authpair_wrong, 401) + + self.log.info('Try using a correct password for platform-user and running all whitelisted commands...') + test_command("getbestblockhash", [], rpcuser_authpair_platform, 200) + test_command("getblockhash", [0], rpcuser_authpair_platform, 200) + test_command("getblockcount", [], rpcuser_authpair_platform, 200) + test_command("getbestchainlock", [], rpcuser_authpair_platform, 500) + + self.log.info('Try running a not whitelisted command...') + test_command("stop", [], rpcuser_authpair_platform, 403) + + self.log.info('Try running a not whitelisted command as the operator...') + test_command("stop", [], rpcuser_authpair_operator, 200) + + +if __name__ == '__main__': + HTTPBasicsTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 2b09a5f0a4..0493b418bd 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -153,6 +153,7 @@ BASE_SCRIPTS= [ 'feature_shutdown.py', 'rpc_privatesend.py', 'p2p_fingerprint.py', + 'rpc_platform_filter.py', 'feature_uacomment.py', 'p2p_unrequested_blocks.py', 'feature_logging.py',