Exemple #1
0
    async def test_with_get_protocolinfo(self, authenticate_none_mock,
                                         get_protocolinfo_mock):
        """
    Tests the authenticate() function when it needs to make a get_protocolinfo.
    """

        # tests where get_protocolinfo succeeds

        authenticate_none_mock.side_effect = coro_func_returning_value(None)

        protocolinfo_message = ControlMessage.from_str(
            '250-PROTOCOLINFO 1\r\n250 OK\r\n', 'PROTOCOLINFO')
        protocolinfo_message.auth_methods = (stem.connection.AuthMethod.NONE, )
        get_protocolinfo_mock.side_effect = coro_func_returning_value(
            protocolinfo_message)

        await stem.connection.authenticate(None)

        # tests where get_protocolinfo raises an exception

        get_protocolinfo_mock.side_effect = stem.ProtocolError
        with self.assertRaises(stem.connection.IncorrectSocketType):
            await stem.connection.authenticate(None)

        get_protocolinfo_mock.side_effect = stem.SocketError
        with self.assertRaises(stem.connection.AuthenticationFailure):
            await stem.connection.authenticate(None)
Exemple #2
0
    def test_get_info_without_fingerprint(self, get_conf_mock, msg_mock):
        message = ControlMessage.from_str('551 Not running in server mode\r\n')
        msg_mock.side_effect = coro_func_returning_value(message)
        get_conf_mock.side_effect = coro_func_returning_value(None)

        self.assertEqual(None, self.controller._last_fingerprint_exc)
        self.assertRaisesWith(stem.OperationFailed,
                              'Not running in server mode',
                              self.controller.get_info, 'fingerprint')
        self.assertEqual('Not running in server mode',
                         str(self.controller._last_fingerprint_exc))
        self.assertEqual(1, msg_mock.call_count)

        # now that we have a cached failure we should provide that back

        self.assertRaisesWith(stem.OperationFailed,
                              'Not running in server mode',
                              self.controller.get_info, 'fingerprint')
        self.assertEqual(1, msg_mock.call_count)

        # ... but if we become a relay we'll call it again

        get_conf_mock.side_effect = coro_func_returning_value('443')
        self.assertRaisesWith(stem.OperationFailed,
                              'Not running in server mode',
                              self.controller.get_info, 'fingerprint')
        self.assertEqual(2, msg_mock.call_count)
Exemple #3
0
  def test_get_uptime_by_getinfo(self, getinfo_mock):
    """
    Exercise the get_uptime() resolution via a GETINFO query.
    """

    getinfo_mock.side_effect = coro_func_returning_value('321')
    self.assertEqual(321.0, self.controller.get_uptime())
    self.controller.clear_cache()

    getinfo_mock.side_effect = coro_func_returning_value('abc')
    self.assertRaisesWith(ValueError, "'GETINFO uptime' did not provide a valid numeric response: abc", self.controller.get_uptime)
Exemple #4
0
    def test_get_version(self, get_info_mock):
        """
    Exercises the get_version() method.
    """

        try:
            # Use one version for first check.
            version_2_1 = '0.2.1.32'
            version_2_1_object = stem.version.Version(version_2_1)
            get_info_mock.side_effect = coro_func_returning_value(version_2_1)

            # Return a version with a cold cache.
            self.assertEqual(version_2_1_object, self.controller.get_version())

            # Use a different version for second check.
            version_2_2 = '0.2.2.39'
            version_2_2_object = stem.version.Version(version_2_2)
            get_info_mock.side_effect = coro_func_returning_value(version_2_2)

            # Return a version with a hot cache, so it will be the old version.
            self.assertEqual(version_2_1_object, self.controller.get_version())

            # Turn off caching.
            self.controller._is_caching_enabled = False
            # Return a version without caching, so it will be the new version.
            self.assertEqual(version_2_2_object, self.controller.get_version())

            # Spec says the getinfo response may optionally be prefixed by 'Tor '. In
            # practice it doesn't but we should accept that.
            get_info_mock.side_effect = coro_func_returning_value(
                'Tor 0.2.1.32')
            self.assertEqual(version_2_1_object, self.controller.get_version())

            # Raise an exception in the get_info() call.
            get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments)

            # Get a default value when the call fails.
            self.assertEqual(
                'default returned',
                self.controller.get_version(default='default returned'))

            # No default value, accept the error.
            self.assertRaises(InvalidArguments, self.controller.get_version)

            # Give a bad version.  The stem.version.Version ValueError should bubble up.
            version_A_42 = '0.A.42.spam'
            get_info_mock.side_effect = coro_func_returning_value(version_A_42)
            self.assertRaises(ValueError, self.controller.get_version)
        finally:
            # Turn caching back on before we leave.
            self.controller._is_caching_enabled = True
Exemple #5
0
    def test_mockability(self):
        """
    Check that method mocks are respected for both previously constructed
    instances and those made after the mock.
    """

        pre_constructed = Demo()

        with patch('test.unit.util.asyncio.Demo.async_method',
                   Mock(side_effect=coro_func_returning_value('mocked call'))):
            post_constructed = Demo()

            self.assertEqual('mocked call', pre_constructed.async_method())
            self.assertEqual('mocked call', post_constructed.async_method())

        self.assertEqual('async call', pre_constructed.async_method())
        self.assertEqual('async call', post_constructed.async_method())

        # synchronous methods are unaffected

        with patch('test.unit.util.asyncio.Demo.sync_method',
                   Mock(return_value='mocked call')):
            self.assertEqual('mocked call', pre_constructed.sync_method())

        self.assertEqual('sync call', pre_constructed.sync_method())

        pre_constructed.stop()
        post_constructed.stop()
Exemple #6
0
    def test_get_protocolinfo(self, get_protocolinfo_mock):
        """
    Exercises the get_protocolinfo() method.
    """

        # use the handy mocked protocolinfo response

        protocolinfo_msg = ControlMessage.from_str(
            '250-PROTOCOLINFO 1\r\n250 OK\r\n', 'PROTOCOLINFO')
        get_protocolinfo_mock.side_effect = coro_func_returning_value(
            protocolinfo_msg)

        # compare the str representation of these object, because the class
        # does not have, nor need, a direct comparison operator

        self.assertEqual(str(protocolinfo_msg),
                         str(self.controller.get_protocolinfo()))

        # raise an exception in the stem.connection.get_protocolinfo() call

        get_protocolinfo_mock.side_effect = coro_func_raising_exc(
            ProtocolError)

        # get a default value when the call fails

        self.assertEqual(
            'default returned',
            self.controller.get_protocolinfo(default='default returned'))

        # no default value, accept the error

        self.assertRaises(ProtocolError, self.controller.get_protocolinfo)
Exemple #7
0
def mock_download(descriptor, encoding = 'identity', response_code_header = None):
  if response_code_header is None:
    response_code_header = b'HTTP/1.0 200 OK\r\n'

  data = response_code_header + stem.util.str_tools._to_bytes(HEADER % encoding) + b'\r\n\r\n' + descriptor

  return patch('stem.descriptor.remote.Query._download_from', Mock(side_effect = coro_func_returning_value(data)))
Exemple #8
0
    def test_get_pid_by_pid_file(self, open_mock, get_conf_mock):
        """
    Exercise the get_pid() resolution via a PidFile.
    """

        get_conf_mock.side_effect = coro_func_returning_value('/tmp/pid_file')
        open_mock.return_value = io.BytesIO(b'432')

        self.assertEqual(432, self.controller.get_pid())
        open_mock.assert_called_once_with('/tmp/pid_file')
Exemple #9
0
    def test_getting_a_control_port(self, port_mock):
        port_connect_mock = port_mock.return_value.connect
        port_connect_mock.side_effect = coro_func_returning_value(None)
        stem.connection.connect()
        port_mock.assert_called_once_with('127.0.0.1', 9051)
        port_connect_mock.assert_called_once()
        port_mock.reset_mock()

        stem.connection.connect(control_port=('255.0.0.10', 80),
                                control_socket=None)
        port_mock.assert_called_once_with('255.0.0.10', 80)
Exemple #10
0
    def test_getting_a_control_socket(self, socket_mock):
        socket_connect_mock = socket_mock.return_value.connect
        socket_connect_mock.side_effect = coro_func_returning_value(None)
        stem.connection.connect()
        socket_mock.assert_called_once_with('/var/run/tor/control')
        socket_connect_mock.assert_called_once()
        socket_mock.reset_mock()

        stem.connection.connect(control_port=None,
                                control_socket='/tmp/my_socket')
        socket_mock.assert_called_once_with('/tmp/my_socket')
Exemple #11
0
  def test_attach_stream(self):
    """
    Exercises the attach_stream() method.
    """

    # Response when the stream is in a state where it can't be attached (for
    # instance, it's already open).

    response = stem.response.ControlMessage.from_str('555 Connection is not managed by controller.\r\n')
    msg_mock = Mock(side_effect = coro_func_returning_value(response))

    with patch('stem.control.Controller.msg', msg_mock):
      self.assertRaises(UnsatisfiableRequest, self.controller.attach_stream, 'stream_id', 'circ_id')
Exemple #12
0
    async def test_auth_success(self, authenticate_mock):
        authenticate_mock.side_effect = coro_func_returning_value(None)
        control_socket = Mock()

        await stem.connection._connect_auth(control_socket, None, False, None,
                                            None)
        authenticate_mock.assert_called_with(control_socket, None, None)
        authenticate_mock.reset_mock()

        await stem.connection._connect_auth(control_socket, 's3krit!!!', False,
                                            '/my/chroot', None)
        authenticate_mock.assert_called_with(control_socket, 's3krit!!!',
                                             '/my/chroot')
Exemple #13
0
    def test_get_network_status(self, get_info_mock):
        """
    Exercises the get_network_status() method.
    """

        # build a single router status entry

        nickname = 'Beaver'
        fingerprint = '/96bKo4soysolMgKn5Hex2nyFSY'
        desc = NS_DESC % (nickname, fingerprint)
        router = stem.descriptor.router_status_entry.RouterStatusEntryV3(desc)

        # always return the same router status entry

        get_info_mock.side_effect = coro_func_returning_value(desc)

        # pretend to get the router status entry with its name

        self.assertEqual(router, self.controller.get_network_status(nickname))

        # pretend to get the router status entry with its fingerprint

        hex_fingerprint = stem.descriptor.router_status_entry._base64_to_hex(
            fingerprint, False)
        self.assertEqual(router,
                         self.controller.get_network_status(hex_fingerprint))

        # mangle hex fingerprint and try again

        hex_fingerprint = hex_fingerprint[2:]
        self.assertRaises(ValueError, self.controller.get_network_status,
                          hex_fingerprint)

        # raise an exception in the get_info() call

        get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments)

        # get a default value when the call fails

        self.assertEqual(
            'default returned',
            self.controller.get_network_status(nickname,
                                               default='default returned'))

        # no default value, accept the error

        self.assertRaises(InvalidArguments, self.controller.get_network_status,
                          nickname)
Exemple #14
0
  def setUp(self):
    socket = stem.socket.ControlSocket()

    # When initially constructing a controller we need to suppress msg, so our
    # constructor's SETEVENTS requests pass.

    with patch('stem.control.BaseController.msg', Mock(side_effect = coro_func_returning_value(None))):
      self.controller = Controller(socket)

      self.circ_listener = Mock()
      self.controller.add_event_listener(self.circ_listener, EventType.CIRC)

      self.bw_listener = Mock()
      self.controller.add_event_listener(self.bw_listener, EventType.BW)

      self.malformed_listener = Mock()
      self.controller.add_event_listener(self.malformed_listener, MALFORMED_EVENTS)
Exemple #15
0
    def test_get_streams(self):
        """
    Exercises the get_streams() method.
    """

        # get a list of fake, but good looking, streams
        valid_streams = (('1', 'NEW', '4', '10.10.10.1:80'),
                         ('2', 'SUCCEEDED', '4', '10.10.10.1:80'),
                         ('3', 'SUCCEEDED', '4', '10.10.10.1:80'))

        response = ''.join(
            ['%s\r\n' % ' '.join(entry) for entry in valid_streams])
        get_info_mock = Mock(side_effect=coro_func_returning_value(response))

        with patch('stem.control.Controller.get_info', get_info_mock):
            streams = self.controller.get_streams()
            self.assertEqual(len(valid_streams), len(streams))

            for index, stream in enumerate(streams):
                self.assertEqual(valid_streams[index][0], stream.id)
                self.assertEqual(valid_streams[index][1], stream.status)
                self.assertEqual(valid_streams[index][2], stream.circ_id)
                self.assertEqual(valid_streams[index][3], stream.target)
Exemple #16
0
 def set_version(version_str):
     version = stem.version.Version(version_str)
     get_version_mock.side_effect = coro_func_returning_value(version)
Exemple #17
0
class TestControl(unittest.TestCase):
    def setUp(self):
        socket = stem.socket.ControlSocket()

        # When initially constructing a controller we need to suppress msg, so our
        # constructor's SETEVENTS requests pass.

        with patch('stem.control.BaseController.msg',
                   Mock(side_effect=coro_func_returning_value(None))):
            self.controller = Controller(socket)

            self.circ_listener = Mock()
            self.controller.add_event_listener(self.circ_listener,
                                               EventType.CIRC)

            self.bw_listener = Mock()
            self.controller.add_event_listener(self.bw_listener, EventType.BW)

            self.malformed_listener = Mock()
            self.controller.add_event_listener(self.malformed_listener,
                                               MALFORMED_EVENTS)

    def tearDown(self):
        self.controller.close()

    def test_event_description(self):
        self.assertEqual(
            "Logging at the debug runlevel. This is low level, high volume information about tor's internals that generally isn't useful to users.",
            stem.control.event_description('DEBUG'))
        self.assertEqual(
            'Event emitted every second with the bytes sent and received by tor.',
            stem.control.event_description('BW'))
        self.assertEqual(
            'Event emitted every second with the bytes sent and received by tor.',
            stem.control.event_description('bw'))

    def test_event_description_includes_all_events(self):
        self.assertEqual(None, stem.control.event_description('NO_SUCH_EVENT'))

        for event in stem.control.EventType:
            self.assertTrue(stem.control.event_description(event) is not None)

    @patch('stem.control.Controller.msg')
    def test_get_info(self, msg_mock):
        message = ControlMessage.from_str(
            '250-hello=hi right back!\r\n250 OK\r\n', 'GETINFO')
        msg_mock.side_effect = coro_func_returning_value(message)
        self.assertEqual('hi right back!', self.controller.get_info('hello'))

    @patch('stem.control.Controller.msg')
    def test_get_info_address_caching(self, msg_mock):
        def set_message(*args):
            message = ControlMessage.from_str(*args)
            msg_mock.side_effect = coro_func_returning_value(message)

        set_message('551 Address unknown\r\n')

        self.assertEqual(None, self.controller._last_address_exc)
        self.assertRaisesWith(stem.OperationFailed, 'Address unknown',
                              self.controller.get_info, 'address')
        self.assertEqual('Address unknown',
                         str(self.controller._last_address_exc))
        self.assertEqual(1, msg_mock.call_count)

        # now that we have a cached failure we should provide that back

        self.assertRaisesWith(stem.OperationFailed, 'Address unknown',
                              self.controller.get_info, 'address')
        self.assertEqual(1, msg_mock.call_count)

        # invalidates the cache, transitioning from no address to having one

        set_message('250-address=17.2.89.80\r\n250 OK\r\n', 'GETINFO')
        self.assertRaisesWith(stem.OperationFailed, 'Address unknown',
                              self.controller.get_info, 'address')
        self.controller._handle_event(
            ControlMessage.from_str(
                '650 STATUS_SERVER NOTICE EXTERNAL_ADDRESS ADDRESS=17.2.89.80 METHOD=DIRSERV\r\n'
            ))
        self.assertEqual('17.2.89.80', self.controller.get_info('address'))

        # invalidates the cache, transitioning from one address to another

        set_message('250-address=80.89.2.17\r\n250 OK\r\n', 'GETINFO')
        self.assertEqual('17.2.89.80', self.controller.get_info('address'))
        self.controller._handle_event(
            ControlMessage.from_str(
                '650 STATUS_SERVER NOTICE EXTERNAL_ADDRESS ADDRESS=80.89.2.17 METHOD=DIRSERV\r\n'
            ))
        self.assertEqual('80.89.2.17', self.controller.get_info('address'))

    @patch('stem.control.Controller.msg')
    @patch('stem.control.Controller.get_conf')
    def test_get_info_without_fingerprint(self, get_conf_mock, msg_mock):
        message = ControlMessage.from_str('551 Not running in server mode\r\n')
        msg_mock.side_effect = coro_func_returning_value(message)
        get_conf_mock.side_effect = coro_func_returning_value(None)

        self.assertEqual(None, self.controller._last_fingerprint_exc)
        self.assertRaisesWith(stem.OperationFailed,
                              'Not running in server mode',
                              self.controller.get_info, 'fingerprint')
        self.assertEqual('Not running in server mode',
                         str(self.controller._last_fingerprint_exc))
        self.assertEqual(1, msg_mock.call_count)

        # now that we have a cached failure we should provide that back

        self.assertRaisesWith(stem.OperationFailed,
                              'Not running in server mode',
                              self.controller.get_info, 'fingerprint')
        self.assertEqual(1, msg_mock.call_count)

        # ... but if we become a relay we'll call it again

        get_conf_mock.side_effect = coro_func_returning_value('443')
        self.assertRaisesWith(stem.OperationFailed,
                              'Not running in server mode',
                              self.controller.get_info, 'fingerprint')
        self.assertEqual(2, msg_mock.call_count)

    @patch('stem.control.Controller.get_info')
    def test_get_version(self, get_info_mock):
        """
    Exercises the get_version() method.
    """

        try:
            # Use one version for first check.
            version_2_1 = '0.2.1.32'
            version_2_1_object = stem.version.Version(version_2_1)
            get_info_mock.side_effect = coro_func_returning_value(version_2_1)

            # Return a version with a cold cache.
            self.assertEqual(version_2_1_object, self.controller.get_version())

            # Use a different version for second check.
            version_2_2 = '0.2.2.39'
            version_2_2_object = stem.version.Version(version_2_2)
            get_info_mock.side_effect = coro_func_returning_value(version_2_2)

            # Return a version with a hot cache, so it will be the old version.
            self.assertEqual(version_2_1_object, self.controller.get_version())

            # Turn off caching.
            self.controller._is_caching_enabled = False
            # Return a version without caching, so it will be the new version.
            self.assertEqual(version_2_2_object, self.controller.get_version())

            # Spec says the getinfo response may optionally be prefixed by 'Tor '. In
            # practice it doesn't but we should accept that.
            get_info_mock.side_effect = coro_func_returning_value(
                'Tor 0.2.1.32')
            self.assertEqual(version_2_1_object, self.controller.get_version())

            # Raise an exception in the get_info() call.
            get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments)

            # Get a default value when the call fails.
            self.assertEqual(
                'default returned',
                self.controller.get_version(default='default returned'))

            # No default value, accept the error.
            self.assertRaises(InvalidArguments, self.controller.get_version)

            # Give a bad version.  The stem.version.Version ValueError should bubble up.
            version_A_42 = '0.A.42.spam'
            get_info_mock.side_effect = coro_func_returning_value(version_A_42)
            self.assertRaises(ValueError, self.controller.get_version)
        finally:
            # Turn caching back on before we leave.
            self.controller._is_caching_enabled = True

    @patch('stem.control.Controller.get_info')
    def test_get_exit_policy(self, get_info_mock):
        """
    Exercises the get_exit_policy() method.
    """
        async def get_info_mock_side_effect(self, param, default=None):
            return {
                'exit-policy/full':
                'reject *:25,reject *:119,reject *:135-139,reject *:445,reject *:563,reject *:1214,reject *:4661-4666,reject *:6346-6429,reject *:6699,reject *:6881-6999,accept *:*',
            }[param]

        get_info_mock.side_effect = get_info_mock_side_effect

        expected = ExitPolicy(
            'reject *:25',
            'reject *:119',
            'reject *:135-139',
            'reject *:445',
            'reject *:563',
            'reject *:1214',
            'reject *:4661-4666',
            'reject *:6346-6429',
            'reject *:6699',
            'reject *:6881-6999',
            'accept *:*',
        )

        self.assertEqual(str(expected), str(self.controller.get_exit_policy()))

    @patch('stem.control.Controller.get_info')
    @patch('stem.control.Controller.get_conf')
    def test_get_ports(self, get_conf_mock, get_info_mock):
        """
    Exercises the get_ports() and get_listeners() methods.
    """

        # Exercise as an old version of tor that doesn't support the 'GETINFO
        # net/listeners/*' options.

        get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments)

        async def get_conf_mock_side_effect(self, param, *args, **kwargs):
            return {
                'ControlPort': '9050',
                'ControlListenAddress': ['127.0.0.1'],
            }[param]

        get_conf_mock.side_effect = get_conf_mock_side_effect

        self.assertEqual([('127.0.0.1', 9050)],
                         self.controller.get_listeners(Listener.CONTROL))
        self.assertEqual(set([9050]),
                         self.controller.get_ports(Listener.CONTROL))
        self.controller.clear_cache()

        # non-local addresss
        async def get_conf_mock_side_effect(self, param, *args, **kwargs):
            return {
                'ControlPort': '9050',
                'ControlListenAddress': ['27.4.4.1'],
            }[param]

        get_conf_mock.side_effect = get_conf_mock_side_effect

        self.assertEqual([('27.4.4.1', 9050)],
                         self.controller.get_listeners(Listener.CONTROL))
        self.assertEqual(set(), self.controller.get_ports(Listener.CONTROL))
        self.controller.clear_cache()

        # exercise via the GETINFO option

        listeners = '"127.0.0.1:1112" "127.0.0.1:1114"'
        get_info_mock.side_effect = coro_func_returning_value(listeners)

        self.assertEqual([('127.0.0.1', 1112), ('127.0.0.1', 1114)],
                         self.controller.get_listeners(Listener.CONTROL))

        self.assertEqual(set([1112, 1114]),
                         self.controller.get_ports(Listener.CONTROL))
        self.controller.clear_cache()

        # with all localhost addresses, including a couple that aren't

        listeners = '"27.4.4.1:1113" "127.0.0.5:1114" "0.0.0.0:1115" "[::]:1116" "[::1]:1117" "[10::]:1118"'
        get_info_mock.side_effect = coro_func_returning_value(listeners)

        self.assertEqual(set([1114, 1115, 1116, 1117]),
                         self.controller.get_ports(Listener.OR))
        self.controller.clear_cache()

        # IPv6 address

        listeners = '"0.0.0.0:9001" "[fe80:0000:0000:0000:0202:b3ff:fe1e:8329]:9001"'
        get_info_mock.side_effect = coro_func_returning_value(listeners)

        self.assertEqual([('0.0.0.0', 9001),
                          ('fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 9001)],
                         self.controller.get_listeners(Listener.CONTROL))

        # unix socket file

        self.controller.clear_cache()
        get_info_mock.side_effect = coro_func_returning_value(
            '"unix:/tmp/tor/socket"')

        self.assertEqual([], self.controller.get_listeners(Listener.CONTROL))
        self.assertEqual(set(), self.controller.get_ports(Listener.CONTROL))

    @patch('stem.control.Controller.get_info')
    @patch('time.time', Mock(return_value=1410723598.276578))
    def test_get_accounting_stats(self, get_info_mock):
        """
    Exercises the get_accounting_stats() method.
    """
        async def get_info_mock_side_effect(self, param, **kwargs):
            return {
                'accounting/enabled': '1',
                'accounting/hibernating': 'awake',
                'accounting/interval-end': '2014-09-14 19:41:00',
                'accounting/bytes': '4837 2050',
                'accounting/bytes-left': '102944 7440',
            }[param]

        get_info_mock.side_effect = get_info_mock_side_effect

        expected = stem.control.AccountingStats(
            1410723598.276578,
            'awake',
            datetime.datetime(2014, 9, 14, 19, 41),
            62,
            4837,
            102944,
            107781,
            2050,
            7440,
            9490,
        )

        self.assertEqual(expected, self.controller.get_accounting_stats())

        get_info_mock.side_effect = coro_func_raising_exc(
            ControllerError('nope, too bad'))
        self.assertRaises(ControllerError,
                          self.controller.get_accounting_stats)
        self.assertEqual('my default',
                         self.controller.get_accounting_stats('my default'))

    @patch('stem.connection.get_protocolinfo')
    def test_get_protocolinfo(self, get_protocolinfo_mock):
        """
    Exercises the get_protocolinfo() method.
    """

        # use the handy mocked protocolinfo response

        protocolinfo_msg = ControlMessage.from_str(
            '250-PROTOCOLINFO 1\r\n250 OK\r\n', 'PROTOCOLINFO')
        get_protocolinfo_mock.side_effect = coro_func_returning_value(
            protocolinfo_msg)

        # compare the str representation of these object, because the class
        # does not have, nor need, a direct comparison operator

        self.assertEqual(str(protocolinfo_msg),
                         str(self.controller.get_protocolinfo()))

        # raise an exception in the stem.connection.get_protocolinfo() call

        get_protocolinfo_mock.side_effect = coro_func_raising_exc(
            ProtocolError)

        # get a default value when the call fails

        self.assertEqual(
            'default returned',
            self.controller.get_protocolinfo(default='default returned'))

        # no default value, accept the error

        self.assertRaises(ProtocolError, self.controller.get_protocolinfo)

    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=False))
    @patch('stem.control.Controller.get_info',
           Mock(side_effect=coro_func_returning_value(None)))
    def test_get_user_remote(self):
        """
    Exercise the get_user() method for a non-local socket.
    """

        self.assertRaises(ValueError, self.controller.get_user)
        self.assertEqual(123, self.controller.get_user(123))

    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=True))
    @patch('stem.control.Controller.get_info',
           Mock(side_effect=coro_func_returning_value('atagar')))
    def test_get_user_by_getinfo(self):
        """
    Exercise the get_user() resolution via its getinfo option.
    """

        self.assertEqual('atagar', self.controller.get_user())

    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=True))
    @patch('stem.control.Controller.get_info',
           Mock(side_effect=coro_func_returning_value(None)))
    @patch('stem.control.Controller.get_pid',
           Mock(side_effect=coro_func_returning_value(432)))
    @patch('stem.util.system.user', Mock(return_value='atagar'))
    def test_get_user_by_system(self):
        """
    Exercise the get_user() resolution via the system module.
    """

        self.assertEqual('atagar', self.controller.get_user())

    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=False))
    @patch('stem.control.Controller.get_info',
           Mock(side_effect=coro_func_returning_value(None)))
    def test_get_pid_remote(self):
        """
    Exercise the get_pid() method for a non-local socket.
    """

        self.assertRaises(ValueError, self.controller.get_pid)
        self.assertEqual(123, self.controller.get_pid(123))

    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=True))
    @patch('stem.control.Controller.get_info',
           Mock(side_effect=coro_func_returning_value('321')))
    def test_get_pid_by_getinfo(self):
        """
    Exercise the get_pid() resolution via its getinfo option.
    """

        self.assertEqual(321, self.controller.get_pid())

    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=True))
    @patch('stem.control.Controller.get_info',
           Mock(side_effect=coro_func_returning_value(None)))
    @patch('stem.control.Controller.get_conf')
    @patch('stem.control.open', create=True)
    def test_get_pid_by_pid_file(self, open_mock, get_conf_mock):
        """
    Exercise the get_pid() resolution via a PidFile.
    """

        get_conf_mock.side_effect = coro_func_returning_value('/tmp/pid_file')
        open_mock.return_value = io.BytesIO(b'432')

        self.assertEqual(432, self.controller.get_pid())
        open_mock.assert_called_once_with('/tmp/pid_file')

    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=True))
    @patch('stem.control.Controller.get_info',
           Mock(side_effect=coro_func_returning_value(None)))
    @patch('stem.control.Controller.get_conf',
           Mock(side_effect=coro_func_returning_value(None)))
    @patch('stem.util.system.pid_by_name', Mock(return_value=432))
    def test_get_pid_by_name(self):
        """
    Exercise the get_pid() resolution via the process name.
    """

        self.assertEqual(432, self.controller.get_pid())

    @patch('stem.control.Controller.get_version',
           Mock(side_effect=coro_func_returning_value(
               stem.version.Version('0.5.0.14'))))
    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=False))
    @patch('stem.control.Controller.get_info')
    @patch('time.time', Mock(return_value=1000.0))
    def test_get_uptime_by_getinfo(self, getinfo_mock):
        """
    Exercise the get_uptime() resolution via a GETINFO query.
    """

        getinfo_mock.side_effect = coro_func_returning_value('321')
        self.assertEqual(321.0, self.controller.get_uptime())
        self.controller.clear_cache()

        getinfo_mock.side_effect = coro_func_returning_value('abc')
        self.assertRaisesWith(
            ValueError,
            "'GETINFO uptime' did not provide a valid numeric response: abc",
            self.controller.get_uptime)

    @patch('stem.socket.ControlSocket.is_localhost', Mock(return_value=True))
    @patch('stem.control.Controller.get_info',
           Mock(side_effect=coro_func_returning_value(None)))
    @patch('stem.control.Controller.get_version',
           Mock(side_effect=coro_func_returning_value(
               stem.version.Version('0.1.0.14'))))
    @patch('stem.control.Controller.get_pid',
           Mock(side_effect=coro_func_returning_value('12')))
    @patch('stem.util.system.start_time', Mock(return_value=5000.0))
    @patch('time.time', Mock(return_value=5200.0))
    def test_get_uptime_by_process(self):
        """
    Exercise the get_uptime() resolution via process age.
    """

        self.assertEqual(200.0, self.controller.get_uptime())

    @patch('stem.control.Controller.get_info')
    def test_get_network_status_for_ourselves(self, get_info_mock):
        """
    Exercises the get_network_status() method for getting our own relay.
    """

        # when there's an issue getting our fingerprint

        get_info_mock.side_effect = coro_func_raising_exc(
            ControllerError('nope, too bad'))

        exc_msg = 'Unable to determine our own fingerprint: nope, too bad'
        self.assertRaisesWith(ControllerError, exc_msg,
                              self.controller.get_network_status)
        self.assertEqual('boom',
                         self.controller.get_network_status(default='boom'))

        # successful request

        desc = NS_DESC % ('moria1', '/96bKo4soysolMgKn5Hex2nyFSY')

        async def get_info_mock_side_effect(self, param, **kwargs):
            return {
                'fingerprint': '9695DFC35FFEB861329B9F1AB04C46397020CE31',
                'ns/id/9695DFC35FFEB861329B9F1AB04C46397020CE31': desc,
            }[param]

        get_info_mock.side_effect = get_info_mock_side_effect

        self.assertEqual(
            stem.descriptor.router_status_entry.RouterStatusEntryV3(desc),
            self.controller.get_network_status())

    @patch('stem.control.Controller.get_info')
    def test_get_network_status_when_unavailable(self, get_info_mock):
        """
    Exercises the get_network_status() method.
    """

        exc = InvalidArguments(
            None,
            'GETINFO request contained unrecognized keywords: ns/id/5AC9C5AA75BA1F18D8459B326B4B8111A856D290'
        )
        get_info_mock.side_effect = coro_func_raising_exc(exc)

        exc_msg = "Tor was unable to provide the descriptor for '5AC9C5AA75BA1F18D8459B326B4B8111A856D290'"
        self.assertRaisesWith(DescriptorUnavailable, exc_msg,
                              self.controller.get_network_status,
                              '5AC9C5AA75BA1F18D8459B326B4B8111A856D290')

    @patch('stem.control.Controller.get_info')
    def test_get_network_status(self, get_info_mock):
        """
    Exercises the get_network_status() method.
    """

        # build a single router status entry

        nickname = 'Beaver'
        fingerprint = '/96bKo4soysolMgKn5Hex2nyFSY'
        desc = NS_DESC % (nickname, fingerprint)
        router = stem.descriptor.router_status_entry.RouterStatusEntryV3(desc)

        # always return the same router status entry

        get_info_mock.side_effect = coro_func_returning_value(desc)

        # pretend to get the router status entry with its name

        self.assertEqual(router, self.controller.get_network_status(nickname))

        # pretend to get the router status entry with its fingerprint

        hex_fingerprint = stem.descriptor.router_status_entry._base64_to_hex(
            fingerprint, False)
        self.assertEqual(router,
                         self.controller.get_network_status(hex_fingerprint))

        # mangle hex fingerprint and try again

        hex_fingerprint = hex_fingerprint[2:]
        self.assertRaises(ValueError, self.controller.get_network_status,
                          hex_fingerprint)

        # raise an exception in the get_info() call

        get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments)

        # get a default value when the call fails

        self.assertEqual(
            'default returned',
            self.controller.get_network_status(nickname,
                                               default='default returned'))

        # no default value, accept the error

        self.assertRaises(InvalidArguments, self.controller.get_network_status,
                          nickname)

    @patch('stem.control.Controller.is_authenticated', Mock(return_value=True))
    @patch('stem.control.Controller._attach_listeners',
           Mock(side_effect=coro_func_returning_value(([], []))))
    @patch('stem.control.Controller.get_version')
    def test_add_event_listener(self, get_version_mock):
        """
    Exercises the add_event_listener and remove_event_listener methods.
    """
        def set_version(version_str):
            version = stem.version.Version(version_str)
            get_version_mock.side_effect = coro_func_returning_value(version)

        # set up for failure to create any events

        set_version('0.1.0.14')
        self.assertRaises(InvalidRequest, self.controller.add_event_listener,
                          Mock(), EventType.BW)

        # set up to only fail newer events

        set_version('0.2.0.35')

        # EventType.BW is one of the earliest events

        self.controller.add_event_listener(Mock(), EventType.BW)

        # EventType.SIGNAL was added in tor version 0.2.3.1-alpha

        self.assertRaises(InvalidRequest, self.controller.add_event_listener,
                          Mock(), EventType.SIGNAL)

    def test_events_get_received(self):
        """
    Trigger an event, checking that our listeners get notified.
    """

        self._emit_event(CIRC_EVENT)
        self.circ_listener.assert_called_once_with(CIRC_EVENT)
        self.bw_listener.assert_not_called()
        self.malformed_listener.assert_not_called()

        self._emit_event(BW_EVENT)
        self.bw_listener.assert_called_once_with(BW_EVENT)

    @patch('stem.util.log.warn', Mock())
    def test_event_listing_with_error(self):
        """
    Raise an exception in an event listener to confirm it doesn't break our
    event thread.
    """

        self.circ_listener.side_effect = coro_func_raising_exc(
            ValueError('boom'))

        self._emit_event(CIRC_EVENT)
        self.circ_listener.assert_called_once_with(CIRC_EVENT)
        self.bw_listener.assert_not_called()
        self.malformed_listener.assert_not_called()

        self._emit_event(BW_EVENT)
        self.bw_listener.assert_called_once_with(BW_EVENT)

    @patch('stem.util.log.error', Mock())
    def test_event_listing_with_malformed_event(self):
        """
    Attempt to parse a malformed event emitted from Tor. It's important this
    doesn't break our event thread.
    """

        # When stem.response.convert() encounters malformed content we still recast
        # the message.

        expected_bad_event = ControlMessage.from_str(BAD_EVENT.raw_content())
        setattr(expected_bad_event, 'arrived_at', TEST_TIMESTAMP)
        expected_bad_event.__class__ = stem.response.events.BandwidthEvent

        self._emit_event(BAD_EVENT)
        self.circ_listener.assert_not_called()
        self.bw_listener.assert_not_called()
        self.malformed_listener.assert_called_once_with(expected_bad_event)

        self._emit_event(BW_EVENT)
        self.bw_listener.assert_called_once_with(BW_EVENT)

    @patch('stem.control.Controller.get_version',
           Mock(side_effect=coro_func_returning_value(
               stem.version.Version('0.5.0.14'))))
    @patch('stem.control.Controller.msg',
           Mock(side_effect=coro_func_returning_value(
               ControlMessage.from_str('250 OK\r\n'))))
    @patch('stem.control.Controller.add_event_listener',
           Mock(side_effect=coro_func_returning_value(None)))
    @patch('stem.control.Controller.remove_event_listener',
           Mock(side_effect=coro_func_returning_value(None)))
    def test_timeout(self):
        """
    Methods that have an 'await' argument also have an optional timeout. Check
    that we raise a Timeout exception when it's elapsed.
    """

        self.assertRaisesWith(stem.Timeout,
                              'Reached our 0.1 second timeout',
                              self.controller.get_hidden_service_descriptor,
                              '5g2upl4pq6kufc4m',
                              await_result=True,
                              timeout=0.1)

    def test_get_streams(self):
        """
    Exercises the get_streams() method.
    """

        # get a list of fake, but good looking, streams
        valid_streams = (('1', 'NEW', '4', '10.10.10.1:80'),
                         ('2', 'SUCCEEDED', '4', '10.10.10.1:80'),
                         ('3', 'SUCCEEDED', '4', '10.10.10.1:80'))

        response = ''.join(
            ['%s\r\n' % ' '.join(entry) for entry in valid_streams])
        get_info_mock = Mock(side_effect=coro_func_returning_value(response))

        with patch('stem.control.Controller.get_info', get_info_mock):
            streams = self.controller.get_streams()
            self.assertEqual(len(valid_streams), len(streams))

            for index, stream in enumerate(streams):
                self.assertEqual(valid_streams[index][0], stream.id)
                self.assertEqual(valid_streams[index][1], stream.status)
                self.assertEqual(valid_streams[index][2], stream.circ_id)
                self.assertEqual(valid_streams[index][3], stream.target)

    def test_attach_stream(self):
        """
    Exercises the attach_stream() method.
    """

        # Response when the stream is in a state where it can't be attached (for
        # instance, it's already open).

        response = stem.response.ControlMessage.from_str(
            '555 Connection is not managed by controller.\r\n')
        msg_mock = Mock(side_effect=coro_func_returning_value(response))

        with patch('stem.control.Controller.msg', msg_mock):
            self.assertRaises(UnsatisfiableRequest,
                              self.controller.attach_stream, 'stream_id',
                              'circ_id')

    def test_parse_circ_path(self):
        """
    Exercises the _parse_circ_path() helper function.
    """

        # empty input

        self.assertEqual([], _parse_circ_path(None))
        self.assertEqual([], _parse_circ_path(''))

        # check the pydoc examples

        pydoc_examples = {
            '$999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz':
            [('999A226EBED397F331B612FE1E4CFAE5C1F201BA', 'piyaz')],
            '$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14':
            [
                ('E57A476CD4DFBD99B4EE52A100A58610AD6E80B9', None),
                (None, 'hamburgerphone'),
                (None, 'PrivacyRepublic14'),
            ],
        }

        for test_input, expected in pydoc_examples.items():
            self.assertEqual(expected, _parse_circ_path(test_input))

        # exercise with some invalid inputs

        malformed_inputs = [
            '=piyaz',  # no fingerprint
            '999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz',  # fingerprint missing prefix
            '$999A226EBED397F331B612FE1E4CFAE5C1F201BAA=piyaz',  # fingerprint too long
            '$999A226EBED397F331B612FE1E4CFAE5C1F201B=piyaz',  # fingerprint too short
            '$999A226EBED397F331B612FE1E4CFAE5C1F201Bz=piyaz',  # invalid character in fingerprint
            '$999A226EBED397F331B612FE1E4CFAE5C1F201BA=',  # no nickname
        ]

        for test_input in malformed_inputs:
            self.assertRaises(ProtocolError, _parse_circ_path, test_input)

    @patch('stem.control.Controller.get_conf')
    def test_get_effective_rate(self, get_conf_mock):
        """
    Exercise the get_effective_rate() method.
    """

        # check default if nothing was set
        async def get_conf_mock_side_effect(self, param, *args, **kwargs):
            return {
                'BandwidthRate': '1073741824',
                'BandwidthBurst': '1073741824',
                'RelayBandwidthRate': '0',
                'RelayBandwidthBurst': '0',
                'MaxAdvertisedBandwidth': '1073741824',
            }[param]

        get_conf_mock.side_effect = get_conf_mock_side_effect

        self.assertEqual(1073741824, self.controller.get_effective_rate())
        self.assertEqual(1073741824,
                         self.controller.get_effective_rate(burst=True))

        get_conf_mock.side_effect = coro_func_raising_exc(
            ControllerError('nope, too bad'))
        self.assertRaises(ControllerError, self.controller.get_effective_rate)
        self.assertEqual('my_default',
                         self.controller.get_effective_rate('my_default'))

    def _emit_event(self, event):
        # Spins up our Controller's thread pool, emits an event, then shuts it
        # down. This last part is important for a couple reasons...
        #
        #   1. So we don't leave any lingering threads.
        #
        #   2. To ensure our event handlers are done being executed. Events are
        #      processed asynchronously, so the only way to endsure it's done
        #      with its work is to join on the thread.

        with patch('time.time', Mock(return_value=TEST_TIMESTAMP)):
            with patch('stem.control.Controller.is_alive') as is_alive_mock:
                is_alive_mock.return_value = True
                loop = self.controller._loop
                asyncio.run_coroutine_threadsafe(
                    Controller._event_loop(self.controller), loop)

                try:
                    # Converting an event back into an uncast ControlMessage, then feeding it
                    # into our controller's event queue.

                    uncast_event = ControlMessage.from_str(event.raw_content())
                    event_queue = self.controller._event_queue
                    asyncio.run_coroutine_threadsafe(
                        event_queue.put(uncast_event), loop).result()
                    asyncio.run_coroutine_threadsafe(
                        event_queue.join(),
                        loop).result()  # block until the event is consumed
                finally:
                    is_alive_mock.return_value = False
                    self.controller._close()
Exemple #18
0
    async def test_all_use_cases(self, authenticate_safecookie_mock,
                                 authenticate_cookie_mock,
                                 authenticate_password_mock,
                                 authenticate_none_mock):
        """
    Does basic validation that all valid use cases for the PROTOCOLINFO input
    and dependent functions result in either success or a AuthenticationFailed
    subclass being raised.
    """

        # mute the logger for this test since otherwise the output is overwhelming

        stem_logger = log.get_logger()
        stem_logger.setLevel(log.logging_level(None))

        # exceptions that the authentication functions are documented to raise

        all_auth_none_exc = (None, stem.connection.OpenAuthRejected(None),
                             stem.ControllerError(None))

        all_auth_password_exc = (None,
                                 stem.connection.PasswordAuthRejected(None),
                                 stem.connection.IncorrectPassword(None),
                                 stem.ControllerError(None))

        all_auth_cookie_exc = (None,
                               stem.connection.CookieAuthRejected(
                                   None, False, None),
                               stem.connection.IncorrectCookieValue(
                                   None, False, None),
                               stem.connection.IncorrectCookieSize(
                                   None, False, None),
                               stem.connection.UnreadableCookieFile(
                                   None, False, None),
                               stem.connection.AuthChallengeFailed(None, None),
                               stem.ControllerError(None))

        auth_method_combinations = test.get_all_combinations(
            [
                stem.connection.AuthMethod.NONE,
                stem.connection.AuthMethod.PASSWORD,
                stem.connection.AuthMethod.COOKIE,
                stem.connection.AuthMethod.SAFECOOKIE,
                stem.connection.AuthMethod.UNKNOWN,
            ],
            include_empty=True)

        protocolinfo = ControlMessage.from_str(
            '250-PROTOCOLINFO 1\r\n250 OK\r\n', 'PROTOCOLINFO')
        protocolinfo.cookie_path = '/tmp/blah'

        for auth_methods in auth_method_combinations:
            for auth_none_exc in all_auth_none_exc:
                for auth_password_exc in all_auth_password_exc:
                    for auth_cookie_exc in all_auth_cookie_exc:
                        # Skip iteration if it's to test exceptions for authentication
                        # we're not using.

                        if auth_none_exc and stem.connection.AuthMethod.NONE not in auth_methods:
                            continue
                        elif auth_password_exc and stem.connection.AuthMethod.PASSWORD not in auth_methods:
                            continue
                        elif auth_cookie_exc and stem.connection.AuthMethod.COOKIE not in auth_methods and stem.connection.AuthMethod.SAFECOOKIE not in auth_methods:
                            continue

                        # Determine if the authenticate() call will succeed and mock each
                        # of the authenticate_* function to raise its given exception.
                        #
                        # This implementation is slightly inaccurate in a couple regards...
                        # a. it raises safecookie exceptions from authenticate_cookie()
                        # b. exceptions raised by authenticate_cookie() and
                        #    authenticate_safecookie() are always the same
                        #
                        # However, adding another loop for safe_cookie exceptions means
                        # multiplying our runtime many fold. This exercises everything that
                        # matters so the above inaccuracies seem fine.

                        expect_success = False
                        protocolinfo.auth_methods = auth_methods

                        for auth_method in auth_methods:
                            if auth_method == stem.connection.AuthMethod.NONE:
                                auth_mock, raised_exc = authenticate_none_mock, auth_none_exc
                            elif auth_method == stem.connection.AuthMethod.PASSWORD:
                                auth_mock, raised_exc = authenticate_password_mock, auth_password_exc
                            elif auth_method == stem.connection.AuthMethod.COOKIE:
                                auth_mock, raised_exc = authenticate_cookie_mock, auth_cookie_exc
                            elif auth_method == stem.connection.AuthMethod.SAFECOOKIE:
                                auth_mock, raised_exc = authenticate_safecookie_mock, auth_cookie_exc

                            if raised_exc:
                                auth_mock.side_effect = coro_func_raising_exc(
                                    raised_exc)
                            else:
                                auth_mock.side_effect = coro_func_returning_value(
                                    None)
                                expect_success = True

                        if expect_success:
                            await stem.connection.authenticate(
                                None, 'blah', None, protocolinfo)
                        else:
                            with self.assertRaises(
                                    stem.connection.AuthenticationFailure):
                                await stem.connection.authenticate(
                                    None, 'blah', None, protocolinfo)

        # revert logging back to normal
        stem_logger.setLevel(log.logging_level(log.TRACE))
Exemple #19
0
 def test_get_info(self, msg_mock):
     message = ControlMessage.from_str(
         '250-hello=hi right back!\r\n250 OK\r\n', 'GETINFO')
     msg_mock.side_effect = coro_func_returning_value(message)
     self.assertEqual('hi right back!', self.controller.get_info('hello'))
Exemple #20
0
    def test_get_ports(self, get_conf_mock, get_info_mock):
        """
    Exercises the get_ports() and get_listeners() methods.
    """

        # Exercise as an old version of tor that doesn't support the 'GETINFO
        # net/listeners/*' options.

        get_info_mock.side_effect = coro_func_raising_exc(InvalidArguments)

        async def get_conf_mock_side_effect(self, param, *args, **kwargs):
            return {
                'ControlPort': '9050',
                'ControlListenAddress': ['127.0.0.1'],
            }[param]

        get_conf_mock.side_effect = get_conf_mock_side_effect

        self.assertEqual([('127.0.0.1', 9050)],
                         self.controller.get_listeners(Listener.CONTROL))
        self.assertEqual(set([9050]),
                         self.controller.get_ports(Listener.CONTROL))
        self.controller.clear_cache()

        # non-local addresss
        async def get_conf_mock_side_effect(self, param, *args, **kwargs):
            return {
                'ControlPort': '9050',
                'ControlListenAddress': ['27.4.4.1'],
            }[param]

        get_conf_mock.side_effect = get_conf_mock_side_effect

        self.assertEqual([('27.4.4.1', 9050)],
                         self.controller.get_listeners(Listener.CONTROL))
        self.assertEqual(set(), self.controller.get_ports(Listener.CONTROL))
        self.controller.clear_cache()

        # exercise via the GETINFO option

        listeners = '"127.0.0.1:1112" "127.0.0.1:1114"'
        get_info_mock.side_effect = coro_func_returning_value(listeners)

        self.assertEqual([('127.0.0.1', 1112), ('127.0.0.1', 1114)],
                         self.controller.get_listeners(Listener.CONTROL))

        self.assertEqual(set([1112, 1114]),
                         self.controller.get_ports(Listener.CONTROL))
        self.controller.clear_cache()

        # with all localhost addresses, including a couple that aren't

        listeners = '"27.4.4.1:1113" "127.0.0.5:1114" "0.0.0.0:1115" "[::]:1116" "[::1]:1117" "[10::]:1118"'
        get_info_mock.side_effect = coro_func_returning_value(listeners)

        self.assertEqual(set([1114, 1115, 1116, 1117]),
                         self.controller.get_ports(Listener.OR))
        self.controller.clear_cache()

        # IPv6 address

        listeners = '"0.0.0.0:9001" "[fe80:0000:0000:0000:0202:b3ff:fe1e:8329]:9001"'
        get_info_mock.side_effect = coro_func_returning_value(listeners)

        self.assertEqual([('0.0.0.0', 9001),
                          ('fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 9001)],
                         self.controller.get_listeners(Listener.CONTROL))

        # unix socket file

        self.controller.clear_cache()
        get_info_mock.side_effect = coro_func_returning_value(
            '"unix:/tmp/tor/socket"')

        self.assertEqual([], self.controller.get_listeners(Listener.CONTROL))
        self.assertEqual(set(), self.controller.get_ports(Listener.CONTROL))
Exemple #21
0
 def set_message(*args):
     message = ControlMessage.from_str(*args)
     msg_mock.side_effect = coro_func_returning_value(message)
Exemple #22
0
class TestConnect(unittest.TestCase):
    @patch('sys.stdout', new_callable=io.StringIO)
    @patch('stem.util.system.is_running')
    @patch('os.path.exists', Mock(return_value=True))
    @patch('stem.socket.ControlSocketFile',
           Mock(side_effect=stem.SocketError('failed')))
    @patch('stem.socket.ControlPort',
           Mock(side_effect=stem.SocketError('failed')))
    @patch('stem.connection._connect_auth',
           Mock(side_effect=coro_func_returning_value(None)))
    def test_failue_with_the_default_endpoint(self, is_running_mock,
                                              stdout_mock):
        is_running_mock.return_value = False
        self._assert_connect_fails_with(
            {}, stdout_mock,
            "Unable to connect to tor. Are you sure it's running?")

        is_running_mock.return_value = True
        self._assert_connect_fails_with(
            {}, stdout_mock,
            "Unable to connect to tor. Maybe it's running without a ControlPort?"
        )

    @patch('sys.stdout', new_callable=io.StringIO)
    @patch('os.path.exists')
    @patch('stem.util.system.is_running', Mock(return_value=True))
    @patch('stem.socket.ControlSocketFile',
           Mock(side_effect=stem.SocketError('failed')))
    @patch('stem.socket.ControlPort',
           Mock(side_effect=stem.SocketError('failed')))
    @patch('stem.connection._connect_auth',
           Mock(side_effect=coro_func_returning_value(None)))
    def test_failure_with_a_custom_endpoint(self, path_exists_mock,
                                            stdout_mock):
        path_exists_mock.return_value = True
        self._assert_connect_fails_with(
            {
                'control_port': ('127.0.0.1', 80),
                'control_socket': None
            }, stdout_mock, "Unable to connect to 127.0.0.1:80: failed")
        self._assert_connect_fails_with(
            {
                'control_port': None,
                'control_socket': '/tmp/my_socket'
            }, stdout_mock, "Unable to connect to '/tmp/my_socket': failed")

        path_exists_mock.return_value = False
        self._assert_connect_fails_with(
            {
                'control_port': ('127.0.0.1', 80),
                'control_socket': None
            }, stdout_mock, "Unable to connect to 127.0.0.1:80: failed")
        self._assert_connect_fails_with(
            {
                'control_port': None,
                'control_socket': '/tmp/my_socket'
            }, stdout_mock,
            "The socket file you specified (/tmp/my_socket) doesn't exist")

    @patch('stem.socket.ControlPort')
    @patch('os.path.exists', Mock(return_value=False))
    @patch('stem.connection._connect_auth',
           Mock(side_effect=coro_func_returning_value(None)))
    def test_getting_a_control_port(self, port_mock):
        port_connect_mock = port_mock.return_value.connect
        port_connect_mock.side_effect = coro_func_returning_value(None)
        stem.connection.connect()
        port_mock.assert_called_once_with('127.0.0.1', 9051)
        port_connect_mock.assert_called_once()
        port_mock.reset_mock()

        stem.connection.connect(control_port=('255.0.0.10', 80),
                                control_socket=None)
        port_mock.assert_called_once_with('255.0.0.10', 80)

    @patch('stem.socket.ControlSocketFile')
    @patch('os.path.exists', Mock(return_value=True))
    @patch('stem.connection._connect_auth',
           Mock(side_effect=coro_func_returning_value(None)))
    def test_getting_a_control_socket(self, socket_mock):
        socket_connect_mock = socket_mock.return_value.connect
        socket_connect_mock.side_effect = coro_func_returning_value(None)
        stem.connection.connect()
        socket_mock.assert_called_once_with('/var/run/tor/control')
        socket_connect_mock.assert_called_once()
        socket_mock.reset_mock()

        stem.connection.connect(control_port=None,
                                control_socket='/tmp/my_socket')
        socket_mock.assert_called_once_with('/tmp/my_socket')

    def _assert_connect_fails_with(self, args, stdout_mock, msg):
        result = stem.connection.connect(**args)

        if result is not None:
            self.fail()

        # Python 3.x seems to have an oddity where StringIO has prefixed null
        # characters (\x00) after we call truncate(). This could be addressed
        # a couple ways...
        #
        #   * Don't use a stdout mock more than once.
        #   * Strip the null characters.
        #
        # Opting for the second (which is admittedly a hack) so the tests are a
        # little nicer.

        stdout_output = stdout_mock.getvalue()
        stdout_mock.truncate(0)
        self.assertEqual(msg, stdout_output.strip().lstrip('\x00'))

    @patch('stem.connection.authenticate')
    @async_test
    async def test_auth_success(self, authenticate_mock):
        authenticate_mock.side_effect = coro_func_returning_value(None)
        control_socket = Mock()

        await stem.connection._connect_auth(control_socket, None, False, None,
                                            None)
        authenticate_mock.assert_called_with(control_socket, None, None)
        authenticate_mock.reset_mock()

        await stem.connection._connect_auth(control_socket, 's3krit!!!', False,
                                            '/my/chroot', None)
        authenticate_mock.assert_called_with(control_socket, 's3krit!!!',
                                             '/my/chroot')

    @patch('getpass.getpass')
    @patch('stem.connection.authenticate')
    @async_test
    async def test_auth_success_with_password_prompt(self, authenticate_mock,
                                                     getpass_mock):
        control_socket = Mock()

        async def authenticate_mock_func(controller, password, *args):
            if password is None:
                raise stem.connection.MissingPassword('no password')
            elif password == 'my_password':
                return None  # success
            else:
                raise ValueError('Unexpected authenticate_mock input: %s' %
                                 password)

        authenticate_mock.side_effect = authenticate_mock_func
        getpass_mock.return_value = 'my_password'

        await stem.connection._connect_auth(control_socket, None, True, None,
                                            None)
        authenticate_mock.assert_any_call(control_socket, None, None)
        authenticate_mock.assert_any_call(control_socket, 'my_password', None)

    @patch('sys.stdout', new_callable=io.StringIO)
    @patch('stem.connection.authenticate')
    def test_auth_failure(self, authenticate_mock, stdout_mock):
        control_socket = stem.socket.ControlPort()

        authenticate_mock.side_effect = coro_func_raising_exc(
            stem.connection.IncorrectSocketType('unable to connect to socket'))
        self._assert_authenticate_fails_with(
            control_socket, stdout_mock,
            'Please check in your torrc that 9051 is the ControlPort.')

        control_socket = stem.socket.ControlSocketFile()

        self._assert_authenticate_fails_with(
            control_socket, stdout_mock,
            'Are you sure the interface you specified belongs to')

        authenticate_mock.side_effect = coro_func_raising_exc(
            stem.connection.UnrecognizedAuthMethods('unable to connect',
                                                    ['telepathy']))
        self._assert_authenticate_fails_with(
            control_socket, stdout_mock,
            'Tor is using a type of authentication we do not recognize...\n\n  telepathy'
        )

        authenticate_mock.side_effect = coro_func_raising_exc(
            stem.connection.IncorrectPassword('password rejected'))
        self._assert_authenticate_fails_with(control_socket, stdout_mock,
                                             'Incorrect password')

        authenticate_mock.side_effect = coro_func_raising_exc(
            stem.connection.UnreadableCookieFile('permission denied',
                                                 '/tmp/my_cookie', False))
        self._assert_authenticate_fails_with(
            control_socket, stdout_mock,
            "We were unable to read tor's authentication cookie...\n\n  Path: /tmp/my_cookie\n  Issue: permission denied"
        )

        authenticate_mock.side_effect = coro_func_raising_exc(
            stem.connection.OpenAuthRejected('crazy failure'))
        self._assert_authenticate_fails_with(
            control_socket, stdout_mock,
            'Unable to authenticate: crazy failure')

    @async_test
    async def _assert_authenticate_fails_with(self, control_socket,
                                              stdout_mock, msg):
        result = await stem.connection._connect_auth(control_socket, None,
                                                     False, None, None)

        if result is not None:
            self.fail()  # _connect_auth() was successful

        stdout_output = stdout_mock.getvalue()
        stdout_mock.truncate(0)

        if msg not in stdout_output:
            self.fail(
                "Expected...\n\n%s\n\n... which couldn't be found in...\n\n%s"
                % (msg, stdout_output))