source: ralphm-patches/session_manager.patch @ 66:b713f442b222

Last change on this file since 66:b713f442b222 was 66:b713f442b222, checked in by Ralph Meijer <ralphm@…>, 8 years ago

Add many tests, docstrings for authenticator, make example functional.

File size: 6.7 KB
  • wokkel/client.py

    # HG changeset patch
    # Parent 4962500d52ed0bd4a71047c93818303accea55fd
    
    diff --git a/wokkel/client.py b/wokkel/client.py
    a b  
    1212
    1313import base64
    1414
     15from zope.interface import implements
     16
    1517from twisted.application import service
    16 from twisted.cred import credentials, error as ecred
     18from twisted.cred import credentials, error as ecred, portal
    1719from twisted.internet import defer, reactor
    18 from twisted.python import log
     20from twisted.python import log, randbytes
    1921from twisted.names.srvconnect import SRVConnector
    2022from twisted.words.protocols.jabber import client, error, sasl, xmlstream
    21 from twisted.words.xish import domish
     23from twisted.words.protocols.jabber.jid import JID, internJID
     24from twisted.words.xish import domish, utility
    2225
    2326from wokkel import generic
    2427from wokkel.compat import XmlStreamServerFactory
    2528from wokkel.iwokkel import IUserSession
    26 from wokkel.subprotocols import StreamManager
     29from wokkel.subprotocols import StreamManager, XMPPHandler
    2730
    2831NS_CLIENT = 'jabber:client'
    2932
     
    501504
    502505    def onError(self, reason):
    503506        log.err(reason, "Stream Error")
     507
     508
     509
     510class UserSession(object):
     511
     512    implements(IUserSession)
     513
     514    realm = None
     515    mind = None
     516
     517    connected = False
     518    interested = False
     519    presence = None
     520
     521    clientStream = None
     522
     523    def __init__(self, entity):
     524        self.entity = entity
     525
     526
     527    def loggedIn(self, realm, mind):
     528        self.realm = realm
     529        self.mind = mind
     530
     531
     532    def bindResource(self, resource):
     533        def cb(entity):
     534            self.entity = entity
     535            self.connected = True
     536            return entity
     537
     538        d = self.realm.bindResource(self, resource)
     539        d.addCallback(cb)
     540        return d
     541
     542
     543    def logout(self):
     544        self.connected = False
     545        self.realm.unbindResource(self)
     546
     547
     548    def send(self, element):
     549        self.realm.onElement(element, self)
     550
     551
     552    def receive(self, element):
     553        self.mind.send(element)
     554
     555
     556
     557class SessionManager(XMPPHandler):
     558    """
     559    Session Manager.
     560
     561    @ivar xmlstream: XML Stream to inject incoming stanzas from client
     562        connections into. Stanzas where the C{'to'} attribute is not set
     563        or is directed at the local domain are injected as if received on
     564        the XML Stream (using C{dispatch}), other stanzas are injected as if
     565        they were sent from the XML Stream (using C{send}).
     566    """
     567
     568    implements(portal.IRealm)
     569
     570    def __init__(self, domain, accounts):
     571        XMPPHandler.__init__(self)
     572        self.domain = domain
     573        self.accounts = accounts
     574
     575        self.sessions = {}
     576        self.clientStream = utility.EventDispatcher()
     577        self.clientStream.addObserver('/*', self.routeOrDeliver, -1)
     578
     579
     580    def requestAvatar(self, avatarId, mind, *interfaces):
     581        if IUserSession not in interfaces:
     582            raise NotImplementedError(self, interfaces)
     583
     584        localpart = avatarId.decode('utf-8')
     585        entity = JID(tuple=(localpart, self.domain, None))
     586        session = UserSession(entity)
     587        session.loggedIn(self, mind)
     588        return IUserSession, session, session.logout
     589
     590
     591    def bindResource(self, session, resource):
     592        localpart = session.entity.user
     593
     594        try:
     595            userSessions = self.sessions[localpart]
     596        except KeyError:
     597            userSessions = self.sessions[localpart] = {}
     598
     599        if resource is None:
     600            resource = randbytes.secureRandom(8).encode('hex')
     601        elif resource in self.userSessions:
     602            resource = resource + ' ' + randbytes.secureRandom(8).encode('hex')
     603
     604        entity = JID(tuple=(session.entity.user, session.entity.host, resource))
     605        userSessions[resource] = session
     606
     607        return defer.succeed(entity)
     608
     609
     610    def lookupSession(self, entity):
     611        localpart = entity.user
     612        resource = entity.resource
     613
     614        userSessions = self.sessions[localpart]
     615        session = userSessions[resource]
     616        return session
     617
     618
     619    def unbindResource(self, session, reason=None):
     620        session.connected = False
     621
     622        localpart = session.entity.user
     623        resource = session.entity.resource
     624
     625        del self.sessions[localpart][resource]
     626        if not self.sessions[localpart]:
     627            del self.sessions[localpart]
     628
     629        return defer.succeed(None)
     630
     631
     632    def onElement(self, element, session):
     633        # Make sure each stanza has a sender address
     634        if (element.name == 'presence' and
     635            element.getAttribute('type') in ('subscribe', 'subscribed',
     636                                             'unsubscribe', 'unsubscribed')):
     637            element['from'] = session.entity.userhost()
     638        else:
     639            element['from'] = session.entity.full()
     640
     641        self.clientStream.dispatch(element)
     642
     643
     644    def routeOrDeliver(self, element):
     645        """
     646        Deliver a stanza locally or pass on for routing.
     647        """
     648        if element.handled:
     649            return
     650
     651        if (not element.hasAttribute('to') or
     652            internJID(element['to']).host == self.domain):
     653            # This stanza is for local delivery
     654            log.msg("Delivering locally: %r" % element.toXml())
     655            self.xmlstream.dispatch(element)
     656        else:
     657            # This stanza is for remote routing
     658            log.msg("Routing remotely: %r" % element.toXml())
     659            XMPPHandler.send(self, element)
     660
     661
     662    def deliverStanza(self, element, recipient):
     663        session = self.lookupSession(recipient)
     664        session.receive(element)
  • wokkel/test/test_client.py

    diff --git a/wokkel/test/test_client.py b/wokkel/test/test_client.py
    a b  
    77
    88from base64 import b64encode
    99
    10 from zope.interface import implements
     10from zope.interface import implements, verify
    1111
    1212from twisted.cred.portal import IRealm, Portal
    1313from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
     
    601601
    602602
    603603
     604
    604605class XMPPClientListenAuthenticatorTest(unittest.TestCase):
    605606    """
    606607    Tests for L{client.XMPPClientListenAuthenticator}.
     
    670671                         "to='example.com' "
    671672                         "version='1.0'>")
    672673        self.xmlstream.assertStreamError(self, condition='host-unknown')
     674
     675
     676
     677class UserSessionTest(unittest.TestCase):
     678
     679    def setUp(self):
     680        self.session = client.UserSession(JID('user@example.org'))
     681
     682
     683    def test_interface(self):
     684        verify.verifyObject(client.IUserSession, self.session)
     685
     686
     687
     688class SessionManagerTest(unittest.TestCase):
     689
     690    def setUp(self):
     691        accounts = {'user': None}
     692        self.sessionManager = client.SessionManager('example.org', accounts)
     693
     694
     695    def test_interface(self):
     696        verify.verifyObject(IRealm, self.sessionManager)
Note: See TracBrowser for help on using the repository browser.