source: ralphm-patches/client_listen_authenticator.patch @ 57:0d8b6cf41728

Last change on this file since 57:0d8b6cf41728 was 57:0d8b6cf41728, checked in by Ralph Meijer <ralphm@…>, 9 years ago

Wokkel 0.7.0 release, clean up various patches.

File size: 6.7 KB
  • wokkel/client.py

    # HG changeset patch
    # Parent 75701188facc278f61a0dfb4bcfcd2232ee771ca
    Add authenticator for accepting XMPP client connections.
    
    The new authenticator XMPPClientListenAuthenticator is to be used together
    with an `XmlStream` created for an incoming XMPP stream. It handles the
    SASL PLAIN mechanism only.
    
    This authenticator needs a backend service to hold the domain served and
    relay established connections. Upon binding a resource, its `bindResource`
    method is called with the local-part, domain and resource bound to the new
    stream.
    
    TODO:
    
     * Add tests.
     * Add docstrings.
     * Readd stream namespace check?
     * Host checks.
     * Password checks.
     * Support for multiple domains?
    
    diff -r 75701188facc wokkel/client.py
    a b  
    1010that should probably eventually move there.
    1111"""
    1212
     13import base64
     14
    1315from twisted.application import service
    1416from twisted.internet import reactor
     17from twisted.python import log
    1518from twisted.names.srvconnect import SRVConnector
    16 from twisted.words.protocols.jabber import client, sasl, xmlstream
     19from twisted.words.protocols.jabber import client, error, sasl, xmlstream
     20from twisted.words.xish import domish
    1721
    1822from wokkel import generic
    1923from wokkel.subprotocols import StreamManager
    2024
     25NS_CLIENT = 'jabber:client'
     26
     27XPATH_ALL = "/*"
     28XPATH_AUTH = "/auth[@xmlns='%s']" % sasl.NS_XMPP_SASL
     29XPATH_BIND = "/iq[@type='set']/bind[@xmlns='%s']" % client.NS_XMPP_BIND
     30XPATH_SESSION = "/iq[@type='set']/session[@xmlns='%s']" % \
     31                client.NS_XMPP_SESSION
     32
    2133class CheckAuthInitializer(object):
    2234    """
    2335    Check what authentication methods are available.
     
    5163    autentication.
    5264    """
    5365
    54     namespace = 'jabber:client'
     66    namespace = NS_CLIENT
    5567
    5668    def __init__(self, jid, password):
    5769        xmlstream.ConnectAuthenticator.__init__(self, jid.host)
     
    186198    c = XMPPClientConnector(reactor, domain, factory)
    187199    c.connect()
    188200    return factory.deferred
     201
     202
     203
     204class XMPPClientListenAuthenticator(xmlstream.ListenAuthenticator):
     205    namespace = NS_CLIENT
     206
     207    def __init__(self, service):
     208        self.service = service
     209        self.failureGrace = 3
     210        self.state = 'auth'
     211
     212
     213    def associateWithStream(self, xs):
     214        xmlstream.ListenAuthenticator.associateWithStream(self, xs)
     215        self.xmlstream.addObserver(XPATH_ALL, self.onElementFallback, -1)
     216
     217
     218    def onElementFallback(self, element):
     219        if element.handled:
     220            return
     221
     222        exc = error.StreamError('not-authorized')
     223        self.xmlstream.sendStreamError(exc)
     224
     225
     226    def streamStarted(self, rootElement):
     227        xmlstream.ListenAuthenticator.streamStarted(self, rootElement)
     228
     229        # check namespace
     230        #if self.xmlstream.namespace != self.namespace:
     231        #    self.xmlstream.namespace = self.namespace
     232        #    exc = error.StreamError('invalid-namespace')
     233        #    self.xmlstream.sendStreamError(exc)
     234        #    return
     235
     236        # TODO: check domain (self.service.domain)
     237
     238        self.xmlstream.sendHeader()
     239
     240        try:
     241            stateHandlerName = 'streamStarted_' + self.state
     242            stateHandler = getattr(self, stateHandlerName)
     243        except AttributeError:
     244            log.msg('streamStarted handler for', self.state, 'not found')
     245        else:
     246            stateHandler()
     247
     248
     249    def toState(self, state):
     250        self.state = state
     251        if state == 'initialized':
     252            self.xmlstream.removeObserver(XPATH_ALL, self.onElementFallback)
     253            self.xmlstream.addOnetimeObserver(XPATH_SESSION, self.onSession, 1)
     254            self.xmlstream.dispatch(self.xmlstream,
     255                                    xmlstream.STREAM_AUTHD_EVENT)
     256
     257
     258    def streamStarted_auth(self):
     259        features = domish.Element((xmlstream.NS_STREAMS, 'features'))
     260        features.addElement((sasl.NS_XMPP_SASL, 'mechanisms'))
     261        features.mechanisms.addElement('mechanism', content='PLAIN')
     262        self.xmlstream.send(features)
     263        self.xmlstream.addOnetimeObserver(XPATH_AUTH, self.onAuth)
     264
     265
     266    def onAuth(self, auth):
     267        auth.handled = True
     268
     269        if auth.getAttribute('mechanism') != 'PLAIN':
     270            failure = domish.Element((sasl.NS_XMPP_SASL, 'failure'))
     271            failure.addElement('invalid-mechanism')
     272            self.xmlstream.send(failure)
     273
     274            # Close stream on too many failing authentication attempts
     275            self.failureGrace -= 1
     276            if self.failureGrace == 0:
     277                self.xmlstream.sendFooter()
     278            else:
     279                self.xmlstream.addOnetimeObserver(XPATH_AUTH, self.onAuth)
     280
     281            return
     282
     283        initialResponse = base64.b64decode(unicode(auth))
     284        authzid, authcid, passwd = initialResponse.split('\x00')
     285
     286        # TODO: check passwd
     287
     288        # authenticated
     289
     290        self.username = authcid
     291
     292        success = domish.Element((sasl.NS_XMPP_SASL, 'success'))
     293        self.xmlstream.send(success)
     294        self.xmlstream.reset()
     295
     296        self.toState('bind')
     297
     298
     299    def streamStarted_bind(self):
     300        features = domish.Element((xmlstream.NS_STREAMS, 'features'))
     301        features.addElement((client.NS_XMPP_BIND, 'bind'))
     302        features.addElement((client.NS_XMPP_SESSION, 'session'))
     303        self.xmlstream.send(features)
     304        self.xmlstream.addOnetimeObserver(XPATH_BIND, self.onBind)
     305
     306
     307    def onBind(self, iq):
     308        def cb(boundJID):
     309            self.xmlstream.otherEntity = boundJID
     310            self.toState('initialized')
     311
     312            response = xmlstream.toResponse(iq, 'result')
     313            response.addElement((client.NS_XMPP_BIND, 'bind'))
     314            response.bind.addElement((client.NS_XMPP_BIND, 'jid'),
     315                                  content=boundJID.full())
     316
     317            return response
     318
     319        def eb(failure):
     320            if not isinstance(failure, error.StanzaError):
     321                log.msg(failure)
     322                exc = error.StanzaError('internal-server-error')
     323            else:
     324                exc = failure.value
     325
     326            return exc.toResponse(iq)
     327
     328        iq.handled = True
     329        resource = unicode(iq.bind) or None
     330        d = self.service.bindResource(self.username,
     331                                      self.service.domain,
     332                                      resource)
     333        d.addCallback(cb)
     334        d.addErrback(eb)
     335        d.addCallback(self.xmlstream.send)
     336
     337
     338    def onSession(self, iq):
     339        iq.handled = True
     340
     341        reply = domish.Element((None, 'iq'))
     342        reply['type'] = 'result'
     343        if iq.getAttribute('id'):
     344            reply['id'] = iq['id']
     345        reply.addElement((client.NS_XMPP_SESSION, 'session'))
     346        self.xmlstream.send(reply)
     347
     348
     349
Note: See TracBrowser for help on using the repository browser.