Пример #1
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)
Пример #2
0
    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'))
Пример #3
0
    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())
Пример #4
0
  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'))
Пример #5
0
  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')
Пример #6
0
    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')
Пример #7
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
Пример #8
0
  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)
Пример #9
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)
Пример #10
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))
Пример #11
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))