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
  • wokkel/client.py

    # HG changeset patch
    # Parent 661689a96e34ac375d95c612d36d959eee3927a9
    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 661689a96e34 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
    1517from twisted.names.srvconnect import SRVConnector
    16 from twisted.words.protocols.jabber import client, sasl, xmlstream
     18from twisted.words.protocols.jabber import client, error, sasl, xmlstream
    1719
    1820from wokkel import generic
    1921from wokkel.subprotocols import StreamManager
    2022
     23NS_CLIENT = 'jabber:client'
     24
     25XPATH_ALL = "/*"
     26XPATH_AUTH = "/auth[@xmlns='%s']" % sasl.NS_XMPP_SASL
     27XPATH_BIND = "/iq[@type='set']/bind[@xmlns='%s']" % client.NS_XMPP_BIND
     28XPATH_SESSION = "/iq[@type='set']/session[@xmlns='%s']" % \
     29                client.NS_XMPP_SESSION
     30
    2131class CheckAuthInitializer(object):
    2232    """
    2333    Check what authentication methods are available.
     
    5161    autentication.
    5262    """
    5363
    54     namespace = 'jabber:client'
     64    namespace = NS_CLIENT
    5565
    5666    def __init__(self, jid, password):
    5767        xmlstream.ConnectAuthenticator.__init__(self, jid.host)
     
    186196    c = XMPPClientConnector(reactor, domain, factory)
    187197    c.connect()
    188198    return factory.deferred
     199
     200
     201
     202class XMPPClientListenAuthenticator(xmlstream.ListenAuthenticator):
     203    namespace = NS_CLIENT
     204
     205    def __init__(self, service):
     206        self.service = service
     207        self.failureGrace = 3
     208        self.state = 'auth'
     209
     210
     211    def associateWithStream(self, xs):
     212        xmlstream.ListenAuthenticator.associateWithStream(self, xs)
     213        self.xmlstream.addObserver(XPATH_ALL, self.onElementFallback, -1)
     214
     215
     216    def onElementFallback(self, element):
     217        if element.handled:
     218            return
     219
     220        exc = error.StreamError('not-authorized')
     221        self.xmlstream.sendStreamError(exc)
     222
     223
     224    def streamStarted(self, rootElement):
     225        xmlstream.ListenAuthenticator.streamStarted(self, rootElement)
     226
     227        # check namespace
     228        #if self.xmlstream.namespace != self.namespace:
     229        #    self.xmlstream.namespace = self.namespace
     230        #    exc = error.StreamError('invalid-namespace')
     231        #    self.xmlstream.sendStreamError(exc)
     232        #    return
     233
     234        # TODO: check domain (self.service.domain)
     235
     236        self.xmlstream.sendHeader()
     237
     238        try:
     239            stateHandlerName = 'streamStarted_' + self.state
     240            stateHandler = getattr(self, stateHandlerName)
     241        except AttributeError:
     242            log.msg('streamStarted handler for', self.state, 'not found')
     243        else:
     244            stateHandler()
     245
     246
     247    def toState(self, state):
     248        self.state = state
     249        if state == 'initialized':
     250            self.xmlstream.removeObserver(XPATH_ALL, self.onElementFallback)
     251            self.xmlstream.addOnetimeObserver(XPATH_SESSION, self.onSession, 1)
     252            self.xmlstream.dispatch(self.xmlstream,
     253                                    xmlstream.STREAM_AUTHD_EVENT)
     254
     255
     256    def streamStarted_auth(self):
     257        features = domish.Element((xmlstream.NS_STREAMS, 'features'))
     258        features.addElement((sasl.NS_XMPP_SASL, 'mechanisms'))
     259        features.mechanisms.addElement('mechanism', content='PLAIN')
     260        self.xmlstream.send(features)
     261        self.xmlstream.addOnetimeObserver(XPATH_AUTH, self.onAuth)
     262
     263
     264    def onAuth(self, auth):
     265        auth.handled = True
     266
     267        if auth.getAttribute('mechanism') != 'PLAIN':
     268            failure = domish.Element((sasl.NS_XMPP_SASL, 'failure'))
     269            failure.addElement('invalid-mechanism')
     270            self.xmlstream.send(failure)
     271
     272            # Close stream on too many failing authentication attempts
     273            self.failureGrace -= 1
     274            if self.failureGrace == 0:
     275                self.xmlstream.sendFooter()
     276            else:
     277                self.xmlstream.addOnetimeObserver(XPATH_AUTH, self.onAuth)
     278
     279            return
     280
     281        initialResponse = base64.b64decode(unicode(auth))
     282        authzid, authcid, passwd = initialResponse.split('\x00')
     283
     284        # TODO: check passwd
     285
     286        # authenticated
     287
     288        self.username = authcid
     289
     290        success = domish.Element((sasl.NS_XMPP_SASL, 'success'))
     291        self.xmlstream.send(success)
     292        self.xmlstream.reset()
     293
     294        self.toState('bind')
     295
     296
     297    def streamStarted_bind(self):
     298        features = domish.Element((xmlstream.NS_STREAMS, 'features'))
     299        features.addElement((client.NS_XMPP_BIND, 'bind'))
     300        features.addElement((client.NS_XMPP_SESSION, 'session'))
     301        self.xmlstream.send(features)
     302        self.xmlstream.addOnetimeObserver(XPATH_BIND, self.onBind)
     303
     304
     305    def onBind(self, iq):
     306        def cb(boundJID):
     307            self.xmlstream.otherEntity = boundJID
     308            self.toState('initialized')
     309
     310            response = xmlstream.toResponse(iq, 'result')
     311            response.addElement((client.NS_XMPP_BIND, 'bind'))
     312            response.bind.addElement((client.NS_XMPP_BIND, 'jid'),
     313                                  content=boundJID.full())
     314
     315            return response
     316
     317        def eb(failure):
     318            if not isinstance(failure, error.StanzaError):
     319                log.msg(failure)
     320                exc = error.StanzaError('internal-server-error')
     321            else:
     322                exc = failure.value
     323
     324            return exc.toResponse(iq)
     325
     326        iq.handled = True
     327        resource = unicode(iq.bind) or None
     328        d = self.service.bindResource(self.username,
     329                                      self.service.domain,
     330                                      resource)
     331        d.addCallback(cb)
     332        d.addErrback(eb)
     333        d.addCallback(self.xmlstream.send)
     334
     335
     336    def onSession(self, iq):
     337        iq.handled = True
     338
     339        reply = domish.Element((None, 'iq'))
     340        reply['type'] = 'result'
     341        if iq.getAttribute('id'):
     342            reply['id'] = iq['id']
     343        reply.addElement((client.NS_XMPP_SESSION, 'session'))
     344        self.xmlstream.send(reply)
     345
     346
     347
Note: See TracBrowser for help on using the repository browser.