commit b65341c6a69d93ebdca6b35975821426268b6319
Author: Damian Johnson <atagar(a)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))