telepathy-haze: Add sasl/telepathy-password.py from Gabble

Will Thompson wjt at kemper.freedesktop.org
Fri Apr 12 03:21:34 PDT 2013


Module: telepathy-haze
Branch: master
Commit: 71f9f850be1eefc949c400a3081a7edaa336aec2
URL:    http://cgit.freedesktop.org/telepathy/telepathy-haze/commit/?id=71f9f850be1eefc949c400a3081a7edaa336aec2

Author: Will Thompson <will.thompson at collabora.co.uk>
Date:   Fri Apr 12 10:59:09 2013 +0100

Add sasl/telepathy-password.py from Gabble

This exercises the happy path of Stefan's patches to support
purple_account_request_password().

---

 tests/twisted/Makefile.am                |    2 +
 tests/twisted/sasl/saslutil.py           |  135 ++++++++++++++++++++++++++++++
 tests/twisted/sasl/telepathy-password.py |   53 ++++++++++++
 3 files changed, 190 insertions(+), 0 deletions(-)

diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
index ddf31ae..3777c89 100644
--- a/tests/twisted/Makefile.am
+++ b/tests/twisted/Makefile.am
@@ -11,6 +11,7 @@ TWISTED_TESTS = \
 	roster/publish.py \
 	roster/removed-from-rp-subscribe.py \
 	roster/subscribe.py \
+	sasl/telepathy-password.py \
 	text/destroy.py \
 	text/ensure.py \
 	text/initiate-requestotron.py \
@@ -40,6 +41,7 @@ EXTRA_DIST = \
 	$(TWISTED_TESTS) \
 	constants.py \
 	hazetest.py \
+	sasl/saslutil.py \
 	servicetest.py \
 	ns.py
 
diff --git a/tests/twisted/sasl/saslutil.py b/tests/twisted/sasl/saslutil.py
new file mode 100644
index 0000000..c09ea9b
--- /dev/null
+++ b/tests/twisted/sasl/saslutil.py
@@ -0,0 +1,135 @@
+# hey, Python: encoding: utf-8
+from hazetest import XmppAuthenticator
+from base64 import b64decode, b64encode
+from twisted.words.xish import domish
+import constants as cs
+import ns
+from servicetest import (ProxyWrapper, EventPattern, assertEquals,
+        assertLength, Event)
+
+class SaslChannelWrapper(ProxyWrapper):
+    def __init__(self, object, default=cs.CHANNEL, interfaces={
+            "ServerAuthentication" : cs.CHANNEL_TYPE_SERVER_AUTHENTICATION,
+            "SASLAuthentication" : cs.CHANNEL_IFACE_SASL_AUTH}):
+        ProxyWrapper.__init__(self, object, default, interfaces)
+
+class SaslEventAuthenticator(XmppAuthenticator):
+    def __init__(self, jid, mechanisms):
+        XmppAuthenticator.__init__(self, jid, '')
+        self._mechanisms = mechanisms
+
+    def streamSASL(self):
+        XmppAuthenticator.streamSASL(self)
+
+        self.xmlstream.addObserver("/response", self._response)
+        self.xmlstream.addObserver("/abort", self._abort)
+
+    def failure(self, fail_str):
+        reply = domish.Element((ns.NS_XMPP_SASL, 'failure'))
+        reply.addElement(fail_str)
+        self.xmlstream.send(reply)
+        self.xmlstream.reset()
+
+    def abort(self):
+        self.failure('abort')
+
+    def not_authorized(self):
+        self.failure('not-authorized')
+
+    def success(self, data=None):
+        reply = domish.Element((ns.NS_XMPP_SASL, 'success'))
+
+        if data is not None:
+            reply.addContent(b64encode(data))
+
+        self.xmlstream.send(reply)
+        self.authenticated=True
+        self.xmlstream.reset()
+
+    def challenge(self, data):
+        reply = domish.Element((ns.NS_XMPP_SASL, 'challenge'))
+        reply.addContent(b64encode(data))
+        self.xmlstream.send(reply)
+
+    def auth(self, auth):
+        # Special case in XMPP: '=' means a zero-byte blob, whereas an empty
+        # or self-terminating XML element means no initial response.
+        # (RFC 3920 §6.2 (3))
+        if str(auth) == '':
+            self._event_func(Event('sasl-auth', authenticator=self,
+                has_initial_response=False,
+                initial_response=None,
+                xml=auth))
+        elif str(auth) == '=':
+            self._event_func(Event('sasl-auth', authenticator=self,
+                has_initial_response=False,
+                initial_response=None,
+                xml=auth))
+        else:
+            self._event_func(Event('sasl-auth', authenticator=self,
+                has_initial_response=True,
+                initial_response=b64decode(str(auth)),
+                xml=auth))
+
+    def _response(self, response):
+        self._event_func(Event('sasl-response', authenticator=self,
+            response=b64decode(str(response)),
+            xml=response))
+
+    def _abort(self, abort):
+        self._event_func(Event('sasl-abort', authenticator=self,
+            xml=abort))
+
+def connect_and_get_sasl_channel(q, bus, conn):
+    conn.Connect()
+
+    q.expect('dbus-signal', signal='StatusChanged',
+             args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED])
+
+    return expect_sasl_channel(q, bus, conn)
+
+def expect_sasl_channel(q, bus, conn):
+    old_signal, new_signal = q.expect_many(
+            EventPattern('dbus-signal', signal='NewChannel',
+                predicate=lambda e:
+                    e.args[1] == cs.CHANNEL_TYPE_SERVER_AUTHENTICATION),
+            EventPattern('dbus-signal', signal='NewChannels',
+                predicate=lambda e:
+                    e.args[0][0][1].get(cs.CHANNEL_TYPE) ==
+                        cs.CHANNEL_TYPE_SERVER_AUTHENTICATION),
+                )
+
+    path, type, handle_type, handle, suppress_handler = old_signal.args
+
+    chan = SaslChannelWrapper(bus.get_object(conn.bus_name, path))
+    assertLength(1, new_signal.args[0])
+    assertEquals(path, new_signal.args[0][0][0])
+    props = new_signal.args[0][0][1]
+
+    assertEquals(cs.CHANNEL_IFACE_SASL_AUTH, props.get(cs.AUTH_METHOD))
+    return chan, props
+
+def abort_auth(q, chan, reason, message):
+    reason_err_map = {
+        cs.SASL_ABORT_REASON_USER_ABORT : cs.CANCELLED,
+        cs.SASL_ABORT_REASON_INVALID_CHALLENGE : cs.SERVICE_CONFUSED }
+
+    mapped_error = reason_err_map.get(reason, cs.CANCELLED)
+
+    chan.SASLAuthentication.AbortSASL(reason, message)
+
+    ssc, ce, _ = q.expect_many(
+        EventPattern(
+            'dbus-signal', signal='SASLStatusChanged',
+            interface=cs.CHANNEL_IFACE_SASL_AUTH,
+            predicate=lambda e: e.args[0] == cs.SASL_STATUS_CLIENT_FAILED),
+        EventPattern('dbus-signal', signal='ConnectionError'),
+        EventPattern(
+            'dbus-signal', signal="StatusChanged",
+            args=[cs.CONN_STATUS_DISCONNECTED,
+                  cs.CSR_AUTHENTICATION_FAILED]))
+
+    assertEquals(cs.SASL_STATUS_CLIENT_FAILED, ssc.args[0])
+    assertEquals(mapped_error, ssc.args[1])
+    assertEquals(message, ssc.args[2].get('debug-message')),
+    assertEquals(mapped_error, ce.args[0])
diff --git a/tests/twisted/sasl/telepathy-password.py b/tests/twisted/sasl/telepathy-password.py
new file mode 100644
index 0000000..f95a3d0
--- /dev/null
+++ b/tests/twisted/sasl/telepathy-password.py
@@ -0,0 +1,53 @@
+"""
+Test the server sasl channel with the X-TELEPATHY-PASSWORD mechanism.
+"""
+
+from servicetest import call_async, EventPattern
+from hazetest import exec_test
+import constants as cs
+from saslutil import connect_and_get_sasl_channel
+
+PASSWORD = "pass"
+
+def test_close_straight_after_accept(q, bus, conn, stream):
+    chan, props = connect_and_get_sasl_channel(q, bus, conn)
+
+    call_async(q, chan.SASLAuthentication, 'StartMechanismWithData',
+            'X-TELEPATHY-PASSWORD', PASSWORD)
+
+    # In_Progress appears before StartMechanismWithData returns
+    q.expect('dbus-signal', signal='SASLStatusChanged',
+             interface=cs.CHANNEL_IFACE_SASL_AUTH,
+             args=[cs.SASL_STATUS_IN_PROGRESS, '', {}])
+
+    # Different order to Gabble
+    q.expect_many(
+        EventPattern('dbus-return', method='StartMechanismWithData'),
+        EventPattern('dbus-signal', signal='SASLStatusChanged',
+            interface=cs.CHANNEL_IFACE_SASL_AUTH,
+            args=[cs.SASL_STATUS_SERVER_SUCCEEDED, '', {}]),
+        )
+
+    # fd.o#32278:
+    # When this was breaking, gabble received AcceptSASL and told the
+    # success_async GAsyncResult to complete in an idle. But, before
+    # the result got its callback called, Close was also received and
+    # the auth manager cleared its channel. When the idle function was
+    # finally reached it saw it no longer had a channel (it had been
+    # cleared in the closed callback) and thought it should be
+    # chaining up to the wocky auth registry but of course it should
+    # be calling the channel finish function.
+    call_async(q, chan.SASLAuthentication, 'AcceptSASL')
+    call_async(q, chan, 'Close')
+
+    q.expect('dbus-signal', signal='SASLStatusChanged',
+             interface=cs.CHANNEL_IFACE_SASL_AUTH,
+             args=[cs.SASL_STATUS_SUCCEEDED, '', {}])
+
+    e = q.expect('dbus-signal', signal='StatusChanged',
+                 args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
+
+if __name__ == '__main__':
+    exec_test(test_close_straight_after_accept,
+              {'password': None, 'account' : "test at example.org/Resource"},
+              do_connect=False)



More information about the telepathy-commits mailing list