/ test / functional / rpc_users.py
rpc_users.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2015-2022 The Bitcoin Core developers
  3  # Distributed under the MIT software license, see the accompanying
  4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
  5  """Test multiple RPC users."""
  6  
  7  from test_framework.test_framework import BitcoinTestFramework
  8  from test_framework.util import (
  9      assert_equal,
 10      str_to_b64str,
 11  )
 12  
 13  import http.client
 14  import urllib.parse
 15  import subprocess
 16  from random import SystemRandom
 17  import string
 18  import configparser
 19  import sys
 20  
 21  
 22  def call_with_auth(node, user, password):
 23      url = urllib.parse.urlparse(node.url)
 24      headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))}
 25  
 26      conn = http.client.HTTPConnection(url.hostname, url.port)
 27      conn.connect()
 28      conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
 29      resp = conn.getresponse()
 30      conn.close()
 31      return resp
 32  
 33  
 34  class HTTPBasicsTest(BitcoinTestFramework):
 35      def set_test_params(self):
 36          self.num_nodes = 2
 37          self.supports_cli = False
 38  
 39      def conf_setup(self):
 40          #Append rpcauth to bitcoin.conf before initialization
 41          self.rtpassword = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM="
 42          rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144"
 43  
 44          self.rpcuser = "rpcuser💻"
 45          self.rpcpassword = "rpcpassword🔑"
 46  
 47          config = configparser.ConfigParser()
 48          config.read_file(open(self.options.configfile))
 49          gen_rpcauth = config['environment']['RPCAUTH']
 50  
 51          # Generate RPCAUTH with specified password
 52          self.rt2password = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI="
 53          p = subprocess.Popen([sys.executable, gen_rpcauth, 'rt2', self.rt2password], stdout=subprocess.PIPE, text=True)
 54          lines = p.stdout.read().splitlines()
 55          rpcauth2 = lines[1]
 56  
 57          # Generate RPCAUTH without specifying password
 58          self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
 59          p = subprocess.Popen([sys.executable, gen_rpcauth, self.user], stdout=subprocess.PIPE, text=True)
 60          lines = p.stdout.read().splitlines()
 61          rpcauth3 = lines[1]
 62          self.password = lines[3]
 63  
 64          with open(self.nodes[0].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f:
 65              f.write(rpcauth + "\n")
 66              f.write(rpcauth2 + "\n")
 67              f.write(rpcauth3 + "\n")
 68          with open(self.nodes[1].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f:
 69              f.write("rpcuser={}\n".format(self.rpcuser))
 70              f.write("rpcpassword={}\n".format(self.rpcpassword))
 71          self.restart_node(0)
 72          self.restart_node(1)
 73  
 74      def test_auth(self, node, user, password):
 75          self.log.info('Correct...')
 76          assert_equal(200, call_with_auth(node, user, password).status)
 77  
 78          self.log.info('Wrong...')
 79          assert_equal(401, call_with_auth(node, user, password + 'wrong').status)
 80  
 81          self.log.info('Wrong...')
 82          assert_equal(401, call_with_auth(node, user + 'wrong', password).status)
 83  
 84          self.log.info('Wrong...')
 85          assert_equal(401, call_with_auth(node, user + 'wrong', password + 'wrong').status)
 86  
 87      def run_test(self):
 88          self.conf_setup()
 89          self.log.info('Check correctness of the rpcauth config option')
 90          url = urllib.parse.urlparse(self.nodes[0].url)
 91  
 92          self.test_auth(self.nodes[0], url.username, url.password)
 93          self.test_auth(self.nodes[0], 'rt', self.rtpassword)
 94          self.test_auth(self.nodes[0], 'rt2', self.rt2password)
 95          self.test_auth(self.nodes[0], self.user, self.password)
 96  
 97          self.log.info('Check correctness of the rpcuser/rpcpassword config options')
 98          url = urllib.parse.urlparse(self.nodes[1].url)
 99  
100          self.test_auth(self.nodes[1], self.rpcuser, self.rpcpassword)
101  
102          init_error = 'Error: Unable to start HTTP server. See debug log for details.'
103  
104          self.log.info('Check -rpcauth are validated')
105          # Empty -rpcauth= are ignored
106          self.restart_node(0, extra_args=['-rpcauth='])
107          self.stop_node(0)
108          self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo'])
109          self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar'])
110          self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar:baz'])
111          self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar:baz'])
112          self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar$baz'])
113  
114          self.log.info('Check that failure to write cookie file will abort the node gracefully')
115          (self.nodes[0].chain_path / ".cookie.tmp").mkdir()
116          self.nodes[0].assert_start_raises_init_error(expected_msg=init_error)
117  
118  
119  if __name__ == '__main__':
120      HTTPBasicsTest().main()