source: ralphm-patches/client_listen_authenticator.patch @ 54:03ec57713c90

Last change on this file since 54:03ec57713c90 was 54:03ec57713c90, checked in by Ralph Meijer <ralphm@…>, 9 years ago

Upstreamed Request patches, split out c2s patches in managable chunks, prepare for release of Wokkel 0.7.0.

File size: 6.6 KB
RevLine 
[54]1# HG changeset patch
2# Parent 661689a96e34ac375d95c612d36d959eee3927a9
3Add authenticator for accepting XMPP client connections.
4
5The new authenticator XMPPClientListenAuthenticator is to be used together
6with an `XmlStream` created for an incoming XMPP stream. It handles the
7SASL PLAIN mechanism only.
8
9This authenticator needs a backend service to hold the domain served and
10relay established connections. Upon binding a resource, its `bindResource`
11method is called with the local-part, domain and resource bound to the new
12stream.
13
14TODO:
15
16 * Add tests.
17 * Add docstrings.
18 * Readd stream namespace check?
19 * Host checks.
20 * Password checks.
21 * Support for multiple domains?
22
23diff -r 661689a96e34 wokkel/client.py
24--- a/wokkel/client.py  Wed Oct 05 09:40:15 2011 +0200
25+++ b/wokkel/client.py  Wed Oct 05 09:50:55 2011 +0200
26@@ -10,14 +10,24 @@
27 that should probably eventually move there.
28 """
29 
30+import base64
31+
32 from twisted.application import service
33 from twisted.internet import reactor
34 from twisted.names.srvconnect import SRVConnector
35-from twisted.words.protocols.jabber import client, sasl, xmlstream
36+from twisted.words.protocols.jabber import client, error, sasl, xmlstream
37 
38 from wokkel import generic
39 from wokkel.subprotocols import StreamManager
40 
41+NS_CLIENT = 'jabber:client'
42+
43+XPATH_ALL = "/*"
44+XPATH_AUTH = "/auth[@xmlns='%s']" % sasl.NS_XMPP_SASL
45+XPATH_BIND = "/iq[@type='set']/bind[@xmlns='%s']" % client.NS_XMPP_BIND
46+XPATH_SESSION = "/iq[@type='set']/session[@xmlns='%s']" % \
47+                client.NS_XMPP_SESSION
48+
49 class CheckAuthInitializer(object):
50     """
51     Check what authentication methods are available.
52@@ -51,7 +61,7 @@
53     autentication.
54     """
55 
56-    namespace = 'jabber:client'
57+    namespace = NS_CLIENT
58 
59     def __init__(self, jid, password):
60         xmlstream.ConnectAuthenticator.__init__(self, jid.host)
61@@ -186,3 +196,152 @@
62     c = XMPPClientConnector(reactor, domain, factory)
63     c.connect()
64     return factory.deferred
65+
66+
67+
68+class XMPPClientListenAuthenticator(xmlstream.ListenAuthenticator):
69+    namespace = NS_CLIENT
70+
71+    def __init__(self, service):
72+        self.service = service
73+        self.failureGrace = 3
74+        self.state = 'auth'
75+
76+
77+    def associateWithStream(self, xs):
78+        xmlstream.ListenAuthenticator.associateWithStream(self, xs)
79+        self.xmlstream.addObserver(XPATH_ALL, self.onElementFallback, -1)
80+
81+
82+    def onElementFallback(self, element):
83+        if element.handled:
84+            return
85+
86+        exc = error.StreamError('not-authorized')
87+        self.xmlstream.sendStreamError(exc)
88+
89+
90+    def streamStarted(self, rootElement):
91+        xmlstream.ListenAuthenticator.streamStarted(self, rootElement)
92+
93+        # check namespace
94+        #if self.xmlstream.namespace != self.namespace:
95+        #    self.xmlstream.namespace = self.namespace
96+        #    exc = error.StreamError('invalid-namespace')
97+        #    self.xmlstream.sendStreamError(exc)
98+        #    return
99+
100+        # TODO: check domain (self.service.domain)
101+
102+        self.xmlstream.sendHeader()
103+
104+        try:
105+            stateHandlerName = 'streamStarted_' + self.state
106+            stateHandler = getattr(self, stateHandlerName)
107+        except AttributeError:
108+            log.msg('streamStarted handler for', self.state, 'not found')
109+        else:
110+            stateHandler()
111+
112+
113+    def toState(self, state):
114+        self.state = state
115+        if state == 'initialized':
116+            self.xmlstream.removeObserver(XPATH_ALL, self.onElementFallback)
117+            self.xmlstream.addOnetimeObserver(XPATH_SESSION, self.onSession, 1)
118+            self.xmlstream.dispatch(self.xmlstream,
119+                                    xmlstream.STREAM_AUTHD_EVENT)
120+
121+
122+    def streamStarted_auth(self):
123+        features = domish.Element((xmlstream.NS_STREAMS, 'features'))
124+        features.addElement((sasl.NS_XMPP_SASL, 'mechanisms'))
125+        features.mechanisms.addElement('mechanism', content='PLAIN')
126+        self.xmlstream.send(features)
127+        self.xmlstream.addOnetimeObserver(XPATH_AUTH, self.onAuth)
128+
129+
130+    def onAuth(self, auth):
131+        auth.handled = True
132+
133+        if auth.getAttribute('mechanism') != 'PLAIN':
134+            failure = domish.Element((sasl.NS_XMPP_SASL, 'failure'))
135+            failure.addElement('invalid-mechanism')
136+            self.xmlstream.send(failure)
137+
138+            # Close stream on too many failing authentication attempts
139+            self.failureGrace -= 1
140+            if self.failureGrace == 0:
141+                self.xmlstream.sendFooter()
142+            else:
143+                self.xmlstream.addOnetimeObserver(XPATH_AUTH, self.onAuth)
144+
145+            return
146+
147+        initialResponse = base64.b64decode(unicode(auth))
148+        authzid, authcid, passwd = initialResponse.split('\x00')
149+
150+        # TODO: check passwd
151+
152+        # authenticated
153+
154+        self.username = authcid
155+
156+        success = domish.Element((sasl.NS_XMPP_SASL, 'success'))
157+        self.xmlstream.send(success)
158+        self.xmlstream.reset()
159+
160+        self.toState('bind')
161+
162+
163+    def streamStarted_bind(self):
164+        features = domish.Element((xmlstream.NS_STREAMS, 'features'))
165+        features.addElement((client.NS_XMPP_BIND, 'bind'))
166+        features.addElement((client.NS_XMPP_SESSION, 'session'))
167+        self.xmlstream.send(features)
168+        self.xmlstream.addOnetimeObserver(XPATH_BIND, self.onBind)
169+
170+
171+    def onBind(self, iq):
172+        def cb(boundJID):
173+            self.xmlstream.otherEntity = boundJID
174+            self.toState('initialized')
175+
176+            response = xmlstream.toResponse(iq, 'result')
177+            response.addElement((client.NS_XMPP_BIND, 'bind'))
178+            response.bind.addElement((client.NS_XMPP_BIND, 'jid'),
179+                                  content=boundJID.full())
180+
181+            return response
182+
183+        def eb(failure):
184+            if not isinstance(failure, error.StanzaError):
185+                log.msg(failure)
186+                exc = error.StanzaError('internal-server-error')
187+            else:
188+                exc = failure.value
189+
190+            return exc.toResponse(iq)
191+
192+        iq.handled = True
193+        resource = unicode(iq.bind) or None
194+        d = self.service.bindResource(self.username,
195+                                      self.service.domain,
196+                                      resource)
197+        d.addCallback(cb)
198+        d.addErrback(eb)
199+        d.addCallback(self.xmlstream.send)
200+
201+
202+    def onSession(self, iq):
203+        iq.handled = True
204+
205+        reply = domish.Element((None, 'iq'))
206+        reply['type'] = 'result'
207+        if iq.getAttribute('id'):
208+            reply['id'] = iq['id']
209+        reply.addElement((client.NS_XMPP_SESSION, 'session'))
210+        self.xmlstream.send(reply)
211+
212+
213+
Note: See TracBrowser for help on using the repository browser.