[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] Basic auth support for ADD_ONION
commit 38aa76c0216e083b4eb5eb6cfc1deba03cf44988
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Thu Jun 16 10:03:26 2016 -0700
Basic auth support for ADD_ONION
Support for basic authentiction special recently added to ADD_ONION...
https://gitweb.torproject.org/torspec.git/commit/?id=c2865d9
---
docs/change_log.rst | 1 +
stem/control.py | 42 +++++++++++++++++++++++++++++++++++++++-
stem/response/add_onion.py | 9 +++++++++
stem/version.py | 2 ++
test/integ/control/controller.py | 21 ++++++++++++++++++++
test/unit/doctest.py | 11 +++++++++++
test/unit/response/add_onion.py | 20 +++++++++++++++++++
7 files changed, 105 insertions(+), 1 deletion(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index ecfc49a..291bca2 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -49,6 +49,7 @@ The following are only available within Stem's `git repository
* :func:`~stem.connection.connect` and :func:`~stem.control.Controller.from_port` now connect to both port 9051 (relay's default) and 9151 (Tor Browser's default) (:trac:`16075`)
* :class:`~stem.exit_policy.ExitPolicy` support for *accept6/reject6* and *\*4/6* wildcards (:trac:`16053`)
* Added `support for NETWORK_LIVENESS events <api/response.html#stem.response.events.NetworkLivenessEvent>`_ (:spec:`44aac63`)
+ * Added support for basic authentication to :func:`~stem.control.Controller.create_ephemeral_hidden_service` (:spec:`c2865d9`)
* Added :func:`~stem.control.event_description` for getting human-friendly descriptions of tor events (:trac:`19061`)
* Added :func:`~stem.control.Controller.reconnect` to the :class:`~stem.control.Controller`
* Added :func:`~stem.control.Controller.is_set` to the :class:`~stem.control.Controller`
diff --git a/stem/control.py b/stem/control.py
index 31e19d5..f29fb71 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -2764,7 +2764,7 @@ class Controller(BaseController):
return result
- def create_ephemeral_hidden_service(self, ports, key_type = 'NEW', key_content = 'BEST', discard_key = False, detached = False, await_publication = False):
+ def create_ephemeral_hidden_service(self, ports, key_type = 'NEW', key_content = 'BEST', discard_key = False, detached = False, await_publication = False, basic_auth = None):
"""
Creates a new hidden service. Unlike
:func:`~stem.control.Controller.create_hidden_service` this style of
@@ -2789,8 +2789,34 @@ class Controller(BaseController):
create_ephemeral_hidden_service({80: 80, 443: '173.194.33.133:443'})
+ If **basic_auth** is provided this service will require basic
+ authentication to access. This means users must set HidServAuth in their
+ torrc with credentials to access it.
+
+ **basic_auth** is a mapping of usernames to their credentials. If the
+ credential is **None** one is generated and returned as part of the
+ response. For instance, only bob can access using the given newly generated
+ credentials...
+
+ ::
+
+ >>> response = controller.create_ephemeral_hidden_service(80, basic_auth = {'bob': None})
+ >>> print(response.client_auth)
+ {'bob': 'nKwfvVPmTNr2k2pG0pzV4g'}
+
+ ... while both alice and bob can access with existing credentials in the
+ following...
+
+ controller.create_ephemeral_hidden_service(80, basic_auth = {
+ 'alice': 'l4BT016McqV2Oail+Bwe6w',
+ 'bob': 'vGnNRpWYiMBFTWD2gbBlcA',
+ })
+
.. versionadded:: 1.4.0
+ .. versionchanged:: 1.5.0
+ Added the basic_auth argument.
+
:param int,list,dict ports: hidden service port(s) or mapping of hidden
service ports to their targets
:param str key_type: type of key being provided, generates a new key if
@@ -2803,6 +2829,7 @@ class Controller(BaseController):
connection is closed if **True**
:param bool await_publication: blocks until our descriptor is successfully
published if **True**
+ :param dict basic_auth: required user credentials to access this service
:returns: :class:`~stem.response.add_onion.AddOnionResponse` with the response
@@ -2830,6 +2857,12 @@ class Controller(BaseController):
if detached:
flags.append('Detach')
+ if basic_auth is not None:
+ if self.get_version() < stem.version.Requirement.ADD_ONION_BASIC_AUTH:
+ raise stem.UnsatisfiableRequest(message = 'Basic authentication support was added to ADD_ONION in tor version %s' % stem.version.Requirement.ADD_ONION_BASIC_AUTH)
+
+ flags.append('BasicAuth')
+
if flags:
request += ' Flags=%s' % ','.join(flags)
@@ -2844,6 +2877,13 @@ class Controller(BaseController):
else:
raise ValueError("The 'ports' argument of create_ephemeral_hidden_service() needs to be an int, list, or dict")
+ if basic_auth is not None:
+ for client_name, client_blob in basic_auth.items():
+ if client_blob:
+ request += ' ClientAuth=%s:%s' % (client_name, client_blob)
+ else:
+ request += ' ClientAuth=%s' % client_name
+
response = self.msg(request)
stem.response.convert('ADD_ONION', response)
diff --git a/stem/response/add_onion.py b/stem/response/add_onion.py
index 2b87755..cbf7594 100644
--- a/stem/response/add_onion.py
+++ b/stem/response/add_onion.py
@@ -12,17 +12,20 @@ class AddOnionResponse(stem.response.ControlMessage):
:var str private_key: base64 encoded hidden service private key
:var str private_key_type: crypto used to generate the hidden service private
key (such as RSA1024)
+ :var dict client_auth: newly generated client credentials the service accepts
"""
def _parse_message(self):
# Example:
# 250-ServiceID=gfzprpioee3hoppz
# 250-PrivateKey=RSA1024:MIICXgIBAAKBgQDZvYVxv...
+ # 250-ClientAuth=bob:l4BT016McqV2Oail+Bwe6w
# 250 OK
self.service_id = None
self.private_key = None
self.private_key_type = None
+ self.client_auth = {}
if not self.is_ok():
raise stem.ProtocolError("ADD_ONION response didn't have an OK status: %s" % self)
@@ -41,3 +44,9 @@ class AddOnionResponse(stem.response.ControlMessage):
raise stem.ProtocolError("ADD_ONION PrivateKey lines should be of the form 'PrivateKey=[type]:[key]: %s" % self)
self.private_key_type, self.private_key = value.split(':', 1)
+ elif key == 'ClientAuth':
+ if ':' not in value:
+ raise stem.ProtocolError("ADD_ONION ClientAuth lines should be of the form 'ClientAuth=[username]:[credential]: %s" % self)
+
+ username, credential = value.split(':', 1)
+ self.client_auth[username] = credential
diff --git a/stem/version.py b/stem/version.py
index df89d28..2dbcff2 100644
--- a/stem/version.py
+++ b/stem/version.py
@@ -58,6 +58,7 @@ easily parsed and compared, for instance...
**HSFETCH** HSFETCH requests
**HSPOST** HSPOST requests
**ADD_ONION** ADD_ONION and DEL_ONION requests
+ **ADD_ONION_BASIC_AUTH** ADD_ONION supports basic authentication
**LOADCONF** LOADCONF requests
**MICRODESCRIPTOR_IS_DEFAULT** Tor gets microdescriptors by default rather than server descriptors
**TAKEOWNERSHIP** TAKEOWNERSHIP requests
@@ -372,6 +373,7 @@ Requirement = stem.util.enum.Enum(
('HSFETCH', Version('0.2.7.1-alpha')),
('HSPOST', Version('0.2.7.1-alpha')),
('ADD_ONION', Version('0.2.7.1-alpha')),
+ ('ADD_ONION_BASIC_AUTH', Version('0.2.9.1-alpha')),
('LOADCONF', Version('0.2.1.1')),
('MICRODESCRIPTOR_IS_DEFAULT', Version('0.2.3.3')),
('TAKEOWNERSHIP', Version('0.2.2.28-beta')),
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index d5c2ec1..2bd06a6 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -641,6 +641,7 @@ class TestController(unittest.TestCase):
response = controller.create_ephemeral_hidden_service(4567)
self.assertEqual([response.service_id], controller.list_ephemeral_hidden_services())
self.assertTrue(response.private_key is not None)
+ self.assertEqual({}, response.client_auth)
# drop the service
@@ -672,6 +673,26 @@ class TestController(unittest.TestCase):
self.assertEqual(0, len(second_controller.list_ephemeral_hidden_services()))
@require_controller
+ @require_version(Requirement.ADD_ONION_BASIC_AUTH)
+ def test_with_ephemeral_hidden_services_with_basic_auth(self):
+ """
+ Exercises creating ephemeral hidden services that uses basic authentication.
+ """
+
+ runner = test.runner.get_runner()
+
+ with runner.get_tor_controller() as controller:
+ response = controller.create_ephemeral_hidden_service(4567, basic_auth = {'alice': 'nKwfvVPmTNr2k2pG0pzV4g', 'bob': None})
+ self.assertEqual([response.service_id], controller.list_ephemeral_hidden_services())
+ self.assertTrue(response.private_key is not None)
+ self.assertEqual(['bob'], response.client_auth.keys()) # newly created credentials were only created for bob
+
+ # drop the service
+
+ self.assertEqual(True, controller.remove_ephemeral_hidden_service(response.service_id))
+ self.assertEqual([], controller.list_ephemeral_hidden_services())
+
+ @require_controller
@require_version(Requirement.ADD_ONION)
def test_with_detached_ephemeral_hidden_services(self):
"""
diff --git a/test/unit/doctest.py b/test/unit/doctest.py
index e3e53d8..548831a 100644
--- a/test/unit/doctest.py
+++ b/test/unit/doctest.py
@@ -14,6 +14,7 @@ import stem.util.str_tools
import stem.util.system
import stem.version
+import test.mocking
import test.util
try:
@@ -27,6 +28,12 @@ EXPECTED_CIRCUIT_STATUS = """\
19 BUILT $718BCEA286B531757ACAFF93AE04910EA73DE617=KsmoinOK,$30BAB8EE7606CBD12F3CC269AE976E0153E7A58D=Pascal1,$2765D8A8C4BBA3F89585A9FFE0E8575615880BEB=Anthracite PURPOSE=GENERAL TIME_CREATED=2012-12-06T13:50:56.969938\
"""
+ADD_ONION_RESPONSE = """\
+250-ServiceID=oekn5sqrvcu4wote
+250-ClientAuth=bob:nKwfvVPmTNr2k2pG0pzV4g
+250 OK
+"""
+
class TestDocumentation(unittest.TestCase):
def test_examples(self):
@@ -77,6 +84,10 @@ class TestDocumentation(unittest.TestCase):
'circuit-status': EXPECTED_CIRCUIT_STATUS,
}[arg]
+ response = test.mocking.get_message(ADD_ONION_RESPONSE)
+ stem.response.convert('ADD_ONION', response)
+ controller.create_ephemeral_hidden_service.return_value = response
+
args['globs'] = {'controller': controller}
test_run = doctest.testfile(path, **args)
elif path.endswith('/stem/version.py'):
diff --git a/test/unit/response/add_onion.py b/test/unit/response/add_onion.py
index b58fe3b..64e3688 100644
--- a/test/unit/response/add_onion.py
+++ b/test/unit/response/add_onion.py
@@ -14,6 +14,12 @@ WITH_PRIVATE_KEY = """250-ServiceID=gfzprpioee3hoppz
250-PrivateKey=RSA1024:MIICXgIBAAKBgQDZvYVxvKPTWhId/8Ss9fVxjAoFDsrJ3pk6HjHrEFRm3ypkK/vArbG9BrupzzYcyms+lO06O8b/iOSHuZI5mUEGkrYqQ+hpB2SkPUEzW7vcp8SQQivna3+LfkWH4JDqfiwZutU6MMEvU6g1OqK4Hll6uHbLpsfxkS/mGjyu1C9a9wIDAQABAoGBAJxsC3a25xZJqaRFfxwmIiptSTFy+/nj4T4gPQo6k/fHMKP/+P7liT9bm+uUwbITNNIjmPzxvrcKt+pNRR/92fizxr8QXr8l0ciVOLerbvdqvVUaQ/K1IVsblOLbactMvXcHactmqqLFUaZU9PPSDla7YkzikLDIUtHXQBEt4HEhAkEA/c4n+kpwi4odCaF49ESPbZC/Qejh7U9Tq10vAHzfrrGgQjnLw2UGDxJQXc9P12fGTvD2q3Q3VaMI8TKKFqZXsQJBANufh1zfP+xX/UfxJ4QzDUCHCu2gnyTDj3nG9Bc80E5g7NwR2VBXF1R+QQCK9GZcXd2y6vBYgrHOSUiLbVjGrycCQQDpOcs0zbjUEUuTsQUT+fiO50dJSrZpus6ZFxz85sMppeItWSzsVeYWbW7adYnZ2Gu72OPjM/0xPYsXEakhHSRRAkAxlVauNQjthv/72god4pi/VL224GiNmEkwKSa6iFRPHbrcBHuXk9IElWx/ft+mrHvUraw1DwaStgv9gNzzCghJAkEA08RegCRnIzuGvgeejLk4suIeCMD/11AvmSvxbRWS5rq1leSVo7uGLSnqDbwlzE4dGb5kH15NNAp14/l2Fu/yZg==
250 OK"""
+WITH_CLIENT_AUTH = """250-ServiceID=oekn5sqrvcu4wote
+250-ClientAuth=bob:lhwLVFt0Kd5/0Gy9DkKoyA
+250-ClientAuth=alice:T9UADxtrvqx2HnLKWp/fWQ
+250 OK
+"""
+
WITHOUT_PRIVATE_KEY = """250-ServiceID=gfzprpioee3hoppz
250 OK"""
@@ -57,6 +63,20 @@ class TestAddOnionResponse(unittest.TestCase):
self.assertEqual('gfzprpioee3hoppz', response.service_id)
self.assertTrue(response.private_key.startswith('MIICXgIBAAKB'))
self.assertEqual('RSA1024', response.private_key_type)
+ self.assertEqual({}, response.client_auth)
+
+ def test_with_client_auth(self):
+ """
+ Checks a response when there's client credentials.
+ """
+
+ response = mocking.get_message(WITH_CLIENT_AUTH)
+ stem.response.convert('ADD_ONION', response)
+
+ self.assertEqual('oekn5sqrvcu4wote', response.service_id)
+ self.assertEqual(None, response.private_key)
+ self.assertEqual(None, response.private_key_type)
+ self.assertEqual({'bob': 'lhwLVFt0Kd5/0Gy9DkKoyA', 'alice': 'T9UADxtrvqx2HnLKWp/fWQ'}, response.client_auth)
def test_without_private_key(self):
"""
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits