summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Thompson <will.thompson@collabora.co.uk>2013-04-12 10:59:09 +0100
committerWill Thompson <will.thompson@collabora.co.uk>2013-04-12 10:59:09 +0100
commit71f9f850be1eefc949c400a3081a7edaa336aec2 (patch)
treed95cb6787fc365f5a0309460dde2d9b160346759
parent9ee8b96ad91af52936f5bee5b9a472de645c2363 (diff)
downloadtelepathy-haze-71f9f850be1eefc949c400a3081a7edaa336aec2.tar.gz
Add sasl/telepathy-password.py from Gabble
This exercises the happy path of Stefan's patches to support purple_account_request_password().
-rw-r--r--tests/twisted/Makefile.am2
-rw-r--r--tests/twisted/sasl/saslutil.py135
-rw-r--r--tests/twisted/sasl/telepathy-password.py53
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@example.org/Resource"},
+ do_connect=False)