commit b65341c6a69d93ebdca6b35975821426268b6319 Author: Damian Johnson atagar@torproject.org Date: Sun Dec 29 14:08:25 2013 -0800
Moving function for connecting and authenticating to util
Most of our starter's functions are pretty trivial but there were two notable exceptions: connecting and authenticating to tor. Moving these to our util allow our starter to be quite a bit shorter. --- arm/starter.py | 72 +++++------------------------------ arm/util/__init__.py | 81 ++++++++++++++++++++++++++++++++++++---- test/starter/authenticate.py | 79 --------------------------------------- test/starter/get_controller.py | 67 --------------------------------- test/util/authenticate.py | 73 ++++++++++++++++++++++++++++++++++++ test/util/init_controller.py | 67 +++++++++++++++++++++++++++++++++ 6 files changed, 223 insertions(+), 216 deletions(-)
diff --git a/arm/starter.py b/arm/starter.py index 0970b35..2cd45ce 100644 --- a/arm/starter.py +++ b/arm/starter.py @@ -5,7 +5,6 @@ connection. """
import curses -import getpass import locale import logging import os @@ -19,17 +18,16 @@ import arm.arguments import arm.controller import arm.util.panel import arm.util.torConfig +import arm.util.torTools import arm.util.tracker import arm.util.uiTools
import stem -import stem.connection -import stem.control import stem.util.conf import stem.util.log import stem.util.system
-from arm.util import BASE_DIR, init_controller, msg, trace, info, notice, warn, load_settings +from arm.util import BASE_DIR, init_controller, authenticate, msg, trace, info, notice, warn, load_settings
CONFIG = stem.util.conf.get_config('arm')
@@ -68,9 +66,13 @@ def main(): _load_user_armrc(args.config)
try: - controller = _get_controller(args) - _authenticate(controller, CONFIG.get('tor.password', None)) - init_controller(controller) + controller = init_controller(args) + authenticate(controller, CONFIG.get('tor.password', None), CONFIG.get('tor.chroot', '')) + + # TODO: Our tor_controller() method will gradually replace the torTools + # module, but until that we need to initialize it too. + + arm.util.torTools.getConn().init(controller) except ValueError as exc: print exc exit(1) @@ -99,62 +101,6 @@ def main(): _shutdown_daemons(controller)
-def _get_controller(args): - """ - Provides a Controller for the endpoint specified in the given arguments. - """ - - if os.path.exists(args.control_socket): - try: - return stem.control.Controller.from_socket_file(args.control_socket) - except stem.SocketError as exc: - if args.user_provided_socket: - raise ValueError(msg('connect.unable_to_use_socket', path = args.control_socket, error = exc)) - elif args.user_provided_socket: - raise ValueError(msg('connect.socket_doesnt_exist', path = args.control_socket)) - - try: - return stem.control.Controller.from_port(args.control_address, args.control_port) - except stem.SocketError as exc: - if args.user_provided_port: - raise ValueError(msg('connect.unable_to_use_port', address = args.control_address, port = args.control_port, error = exc)) - - if not stem.util.system.is_running('tor'): - raise ValueError(msg('connect.tor_isnt_running')) - else: - raise ValueError(msg('connect.no_control_port')) - - -def _authenticate(controller, password): - """ - Authenticates to the given Controller. - """ - - try: - controller.authenticate(password = password, chroot_path = CONFIG.get('tor.chroot', '')) - except stem.connection.IncorrectSocketType: - control_socket = controller.get_socket() - - if isinstance(control_socket, stem.socket.ControlPort): - raise ValueError(msg('connect.wrong_port_type', port = control_socket.get_port())) - else: - raise ValueError(msg('connect.wrong_socket_type')) - except stem.connection.UnrecognizedAuthMethods as exc: - raise ValueError(msg('uncrcognized_auth_type', auth_methods = ', '.join(exc.unknown_auth_methods))) - except stem.connection.IncorrectPassword: - raise ValueError(msg('connect.incorrect_password')) - except stem.connection.MissingPassword: - if password: - raise ValueError(msg('connect.missing_password_bug')) - - password = getpass.getpass(msg('connect.password_prompt') + ' ') - return _authenticate(controller, password) - except stem.connection.UnreadableCookieFile as exc: - raise ValueError(msg('connect.unreadable_cookie_file', path = exc.cookie_path, issue = str(exc))) - except stem.connection.AuthenticationFailure as exc: - raise ValueError(msg('connect.general_auth_failure', error = exc)) - - def _setup_debug_logging(args): """ Configures us to log at stem's trace level to debug log path, and notes some diff --git a/arm/util/__init__.py b/arm/util/__init__.py index 2603349..bd43761 100644 --- a/arm/util/__init__.py +++ b/arm/util/__init__.py @@ -6,10 +6,14 @@ and safely working with curses (hiding some of the gory details).
__all__ = ["connections", "panel", "sysTools", "textInput", "torConfig", "torTools", "tracker", "uiTools"]
+import getpass import os
import arm.util.torTools
+import stem +import stem.connection +import stem.control import stem.util.conf import stem.util.log
@@ -27,20 +31,57 @@ def tor_controller(): return TOR_CONTROLLER
-def init_controller(controller): +def init_controller(args): """ - Registers an initialized tor controller. + Provides a Controller for the endpoint specified in the given arguments.
- :param stem.control.Controller controller: tor controller for arm to use + :param namedtuple args: arguments that arm was started with + + :returns: :class:`~stem.control.Controller` for the given arguments + + :raises: **ValueError** if unable to acquire a controller connection """
global TOR_CONTROLLER - TOR_CONTROLLER = controller + TOR_CONTROLLER = _get_controller(args) + return TOR_CONTROLLER +
- # TODO: Our controller() method will gradually replace the torTools module, - # but until that we need to initialize it too. +def authenticate(controller, password, chroot_path = ''): + """ + Authenticates to the given Controller. + + :param stem.control.Controller controller: controller to be authenticated + :param str password: password to authenticate with, **None** if nothing was + provided + :param str chroot_path: chroot tor resides within + + :raises: **ValueError** if unable to authenticate + """
- arm.util.torTools.getConn().init(controller) + try: + controller.authenticate(password = password, chroot_path = chroot_path) + except stem.connection.IncorrectSocketType: + control_socket = controller.get_socket() + + if isinstance(control_socket, stem.socket.ControlPort): + raise ValueError(msg('connect.wrong_port_type', port = control_socket.get_port())) + else: + raise ValueError(msg('connect.wrong_socket_type')) + except stem.connection.UnrecognizedAuthMethods as exc: + raise ValueError(msg('uncrcognized_auth_type', auth_methods = ', '.join(exc.unknown_auth_methods))) + except stem.connection.IncorrectPassword: + raise ValueError(msg('connect.incorrect_password')) + except stem.connection.MissingPassword: + if password: + raise ValueError(msg('connect.missing_password_bug')) + + password = getpass.getpass(msg('connect.password_prompt') + ' ') + return authenticate(controller, password) + except stem.connection.UnreadableCookieFile as exc: + raise ValueError(msg('connect.unreadable_cookie_file', path = exc.cookie_path, issue = str(exc))) + except stem.connection.AuthenticationFailure as exc: + raise ValueError(msg('connect.general_auth_failure', error = exc))
def msg(message, **attr): @@ -116,3 +157,29 @@ def _log(runlevel, message, **attr): """
stem.util.log.log(runlevel, msg(message, **attr)) + + +def _get_controller(args): + """ + Provides a Controller for the endpoint specified in the given arguments. + """ + + if os.path.exists(args.control_socket): + try: + return stem.control.Controller.from_socket_file(args.control_socket) + except stem.SocketError as exc: + if args.user_provided_socket: + raise ValueError(msg('connect.unable_to_use_socket', path = args.control_socket, error = exc)) + elif args.user_provided_socket: + raise ValueError(msg('connect.socket_doesnt_exist', path = args.control_socket)) + + try: + return stem.control.Controller.from_port(args.control_address, args.control_port) + except stem.SocketError as exc: + if args.user_provided_port: + raise ValueError(msg('connect.unable_to_use_port', address = args.control_address, port = args.control_port, error = exc)) + + if not stem.util.system.is_running('tor'): + raise ValueError(msg('connect.tor_isnt_running')) + else: + raise ValueError(msg('connect.no_control_port')) diff --git a/test/starter/__init__.py b/test/starter/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/starter/authenticate.py b/test/starter/authenticate.py deleted file mode 100644 index 1b228b5..0000000 --- a/test/starter/authenticate.py +++ /dev/null @@ -1,79 +0,0 @@ -import unittest - -from mock import Mock, patch - -from arm.starter import ( - _get_controller, - _authenticate, -) - -import stem -import stem.connection -import stem.socket -import stem.util.conf - - -class TestAuthenticate(unittest.TestCase): - def setUp(self): - arm_conf = stem.util.conf.get_config('arm') - arm_conf.set('tor.chroot', '') # no chroot - - def test_success(self): - controller = Mock() - - _authenticate(controller, None) - controller.authenticate.assert_called_with(password = None, chroot_path = '') - controller.authenticate.reset_mock() - - stem.util.conf.get_config('arm').set('tor.chroot', '/my/chroot') - _authenticate(controller, 's3krit!!!') - controller.authenticate.assert_called_with(password = 's3krit!!!', chroot_path = '/my/chroot') - - @patch('getpass.getpass') - def test_success_with_password_prompt(self, getpass_mock): - controller = Mock() - - def authenticate_mock(password, **kwargs): - 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) - - controller.authenticate.side_effect = authenticate_mock - getpass_mock.return_value = 'my_password' - - _authenticate(controller, None) - controller.authenticate.assert_any_call(password = None, chroot_path = '') - controller.authenticate.assert_any_call(password = 'my_password', chroot_path = '') - - def test_failure(self): - controller = Mock() - - controller.authenticate.side_effect = stem.connection.IncorrectSocketType('unable to connect to socket') - controller.get_socket.return_value = stem.socket.ControlPort(connect = False) - self._assert_authenticate_fails_with(controller, 'Please check in your torrc that 9051 is the ControlPort.') - - controller.get_socket.return_value = stem.socket.ControlSocketFile(connect = False) - self._assert_authenticate_fails_with(controller, 'Are you sure the interface you specified belongs to') - - controller.authenticate.side_effect = stem.connection.UnrecognizedAuthMethods('unable to connect', ['telepathy']) - self._assert_authenticate_fails_with(controller, 'Tor is using a type of authentication we do not recognize...\n\n telepathy') - - controller.authenticate.side_effect = stem.connection.IncorrectPassword('password rejected') - self._assert_authenticate_fails_with(controller, 'Incorrect password') - - controller.authenticate.side_effect = stem.connection.UnreadableCookieFile('permission denied', '/tmp/my_cookie', False) - self._assert_authenticate_fails_with(controller, "We were unable to read tor's authentication cookie...\n\n Path: /tmp/my_cookie\n Issue: permission denied") - - controller.authenticate.side_effect = stem.connection.OpenAuthRejected('crazy failure') - self._assert_authenticate_fails_with(controller, 'Unable to authenticate: crazy failure') - - def _assert_authenticate_fails_with(self, controller, msg): - try: - _get_controller(_authenticate(controller, None)) - self.fail() - except ValueError, exc: - if not msg in str(exc): - self.fail("Expected...\n\n%s\n\n... which couldn't be found in...\n\n%s" % (msg, exc)) diff --git a/test/starter/get_controller.py b/test/starter/get_controller.py deleted file mode 100644 index dcd5f0c..0000000 --- a/test/starter/get_controller.py +++ /dev/null @@ -1,67 +0,0 @@ -import unittest - -from mock import Mock, patch - -from arm.arguments import parse -from arm.starter import _get_controller - -import stem -import stem.connection -import stem.socket - - -class TestGetController(unittest.TestCase): - @patch('os.path.exists', Mock(return_value = True)) - @patch('stem.util.system.is_running') - @patch('stem.control.Controller.from_socket_file', Mock(side_effect = stem.SocketError('failed'))) - @patch('stem.control.Controller.from_port', Mock(side_effect = stem.SocketError('failed'))) - def test_failue_with_the_default_endpoint(self, is_running_mock): - is_running_mock.return_value = False - self._assert_get_controller_fails_with([], "Unable to connect to tor. Are you sure it's running?") - - is_running_mock.return_value = True - self._assert_get_controller_fails_with([], "Unable to connect to tor. Maybe it's running without a ControlPort?") - - @patch('os.path.exists') - @patch('stem.util.system.is_running', Mock(return_value = True)) - @patch('stem.control.Controller.from_socket_file', Mock(side_effect = stem.SocketError('failed'))) - @patch('stem.control.Controller.from_port', Mock(side_effect = stem.SocketError('failed'))) - def test_failure_with_a_custom_endpoint(self, path_exists_mock): - path_exists_mock.return_value = True - self._assert_get_controller_fails_with(['--interface', '80'], "Unable to connect to 127.0.0.1:80: failed") - self._assert_get_controller_fails_with(['--socket', '/tmp/my_socket'], "Unable to connect to '/tmp/my_socket': failed") - - path_exists_mock.return_value = False - self._assert_get_controller_fails_with(['--interface', '80'], "Unable to connect to 127.0.0.1:80: failed") - self._assert_get_controller_fails_with(['--socket', '/tmp/my_socket'], "The socket file you specified (/tmp/my_socket) doesn't exist") - - @patch('os.path.exists', Mock(return_value = False)) - @patch('stem.control.Controller.from_port') - def test_getting_a_control_port(self, from_port_mock): - from_port_mock.return_value = 'success' - - self.assertEqual('success', _get_controller(parse([]))) - from_port_mock.assert_called_once_with('127.0.0.1', 9051) - from_port_mock.reset_mock() - - self.assertEqual('success', _get_controller(parse(['--interface', '255.0.0.10:80']))) - from_port_mock.assert_called_once_with('255.0.0.10', 80) - - @patch('os.path.exists', Mock(return_value = True)) - @patch('stem.control.Controller.from_socket_file') - def test_getting_a_control_socket(self, from_socket_file_mock): - from_socket_file_mock.return_value = 'success' - - self.assertEqual('success', _get_controller(parse([]))) - from_socket_file_mock.assert_called_once_with('/var/run/tor/control') - from_socket_file_mock.reset_mock() - - self.assertEqual('success', _get_controller(parse(['--socket', '/tmp/my_socket']))) - from_socket_file_mock.assert_called_once_with('/tmp/my_socket') - - def _assert_get_controller_fails_with(self, args, msg): - try: - _get_controller(parse(args)) - self.fail() - except ValueError, exc: - self.assertEqual(msg, str(exc)) diff --git a/test/util/__init__.py b/test/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/util/authenticate.py b/test/util/authenticate.py new file mode 100644 index 0000000..11bd92b --- /dev/null +++ b/test/util/authenticate.py @@ -0,0 +1,73 @@ +import unittest + +from mock import Mock, patch + +from arm.util import ( + init_controller, + authenticate, +) + +import stem +import stem.connection +import stem.socket + + +class TestAuthenticate(unittest.TestCase): + def test_success(self): + controller = Mock() + + authenticate(controller, None) + controller.authenticate.assert_called_with(password = None, chroot_path = '') + controller.authenticate.reset_mock() + + authenticate(controller, 's3krit!!!', '/my/chroot') + controller.authenticate.assert_called_with(password = 's3krit!!!', chroot_path = '/my/chroot') + + @patch('getpass.getpass') + def test_success_with_password_prompt(self, getpass_mock): + controller = Mock() + + def authenticate_mock(password, **kwargs): + 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) + + controller.authenticate.side_effect = authenticate_mock + getpass_mock.return_value = 'my_password' + + authenticate(controller, None) + controller.authenticate.assert_any_call(password = None, chroot_path = '') + controller.authenticate.assert_any_call(password = 'my_password', chroot_path = '') + + def test_failure(self): + controller = Mock() + + controller.authenticate.side_effect = stem.connection.IncorrectSocketType('unable to connect to socket') + controller.get_socket.return_value = stem.socket.ControlPort(connect = False) + self._assert_authenticate_fails_with(controller, 'Please check in your torrc that 9051 is the ControlPort.') + + controller.get_socket.return_value = stem.socket.ControlSocketFile(connect = False) + self._assert_authenticate_fails_with(controller, 'Are you sure the interface you specified belongs to') + + controller.authenticate.side_effect = stem.connection.UnrecognizedAuthMethods('unable to connect', ['telepathy']) + self._assert_authenticate_fails_with(controller, 'Tor is using a type of authentication we do not recognize...\n\n telepathy') + + controller.authenticate.side_effect = stem.connection.IncorrectPassword('password rejected') + self._assert_authenticate_fails_with(controller, 'Incorrect password') + + controller.authenticate.side_effect = stem.connection.UnreadableCookieFile('permission denied', '/tmp/my_cookie', False) + self._assert_authenticate_fails_with(controller, "We were unable to read tor's authentication cookie...\n\n Path: /tmp/my_cookie\n Issue: permission denied") + + controller.authenticate.side_effect = stem.connection.OpenAuthRejected('crazy failure') + self._assert_authenticate_fails_with(controller, 'Unable to authenticate: crazy failure') + + def _assert_authenticate_fails_with(self, controller, msg): + try: + init_controller(authenticate(controller, None)) + self.fail() + except ValueError, exc: + if not msg in str(exc): + self.fail("Expected...\n\n%s\n\n... which couldn't be found in...\n\n%s" % (msg, exc)) diff --git a/test/util/init_controller.py b/test/util/init_controller.py new file mode 100644 index 0000000..a106a06 --- /dev/null +++ b/test/util/init_controller.py @@ -0,0 +1,67 @@ +import unittest + +from mock import Mock, patch + +from arm.arguments import parse +from arm.util import init_controller + +import stem +import stem.connection +import stem.socket + + +class TestGetController(unittest.TestCase): + @patch('os.path.exists', Mock(return_value = True)) + @patch('stem.util.system.is_running') + @patch('stem.control.Controller.from_socket_file', Mock(side_effect = stem.SocketError('failed'))) + @patch('stem.control.Controller.from_port', Mock(side_effect = stem.SocketError('failed'))) + def test_failue_with_the_default_endpoint(self, is_running_mock): + is_running_mock.return_value = False + self._assert_init_controller_fails_with([], "Unable to connect to tor. Are you sure it's running?") + + is_running_mock.return_value = True + self._assert_init_controller_fails_with([], "Unable to connect to tor. Maybe it's running without a ControlPort?") + + @patch('os.path.exists') + @patch('stem.util.system.is_running', Mock(return_value = True)) + @patch('stem.control.Controller.from_socket_file', Mock(side_effect = stem.SocketError('failed'))) + @patch('stem.control.Controller.from_port', Mock(side_effect = stem.SocketError('failed'))) + def test_failure_with_a_custom_endpoint(self, path_exists_mock): + path_exists_mock.return_value = True + self._assert_init_controller_fails_with(['--interface', '80'], "Unable to connect to 127.0.0.1:80: failed") + self._assert_init_controller_fails_with(['--socket', '/tmp/my_socket'], "Unable to connect to '/tmp/my_socket': failed") + + path_exists_mock.return_value = False + self._assert_init_controller_fails_with(['--interface', '80'], "Unable to connect to 127.0.0.1:80: failed") + self._assert_init_controller_fails_with(['--socket', '/tmp/my_socket'], "The socket file you specified (/tmp/my_socket) doesn't exist") + + @patch('os.path.exists', Mock(return_value = False)) + @patch('stem.control.Controller.from_port') + def test_getting_a_control_port(self, from_port_mock): + from_port_mock.return_value = 'success' + + self.assertEqual('success', init_controller(parse([]))) + from_port_mock.assert_called_once_with('127.0.0.1', 9051) + from_port_mock.reset_mock() + + self.assertEqual('success', init_controller(parse(['--interface', '255.0.0.10:80']))) + from_port_mock.assert_called_once_with('255.0.0.10', 80) + + @patch('os.path.exists', Mock(return_value = True)) + @patch('stem.control.Controller.from_socket_file') + def test_getting_a_control_socket(self, from_socket_file_mock): + from_socket_file_mock.return_value = 'success' + + self.assertEqual('success', init_controller(parse([]))) + from_socket_file_mock.assert_called_once_with('/var/run/tor/control') + from_socket_file_mock.reset_mock() + + self.assertEqual('success', init_controller(parse(['--socket', '/tmp/my_socket']))) + from_socket_file_mock.assert_called_once_with('/tmp/my_socket') + + def _assert_init_controller_fails_with(self, args, msg): + try: + init_controller(parse(args)) + self.fail() + except ValueError, exc: + self.assertEqual(msg, str(exc))
tor-commits@lists.torproject.org