source: ralphm-patches/session_manager.patch @ 71:7b5ddbb2b9bb

Last change on this file since 71:7b5ddbb2b9bb was 70:cc8ecefb5519, checked in by Ralph Meijer <ralphm@…>, 8 years ago

Move session lookup changes to session_manager.patch.

File size: 7.4 KB
  • wokkel/client.py

    # HG changeset patch
    # Parent bc450d2e7ed710c5605545e39bb6a054c368571f
    
    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
    2629from wokkel.subprotocols import ServerStreamManager
    2730from wokkel.subprotocols import StreamManager
     31from wokkel.subprotocols import XMPPHandler
    2832
    2933NS_CLIENT = 'jabber:client'
    3034
     
    482486        return [
    483487            generic.StanzaForwarder()
    484488        ]
     489
     490
     491
     492class UserSession(object):
     493
     494    implements(IUserSession)
     495
     496    realm = None
     497    mind = None
     498
     499    connected = False
     500    interested = False
     501    presence = None
     502
     503    clientStream = None
     504
     505    def __init__(self, entity):
     506        self.entity = entity
     507
     508
     509    def loggedIn(self, realm, mind):
     510        self.realm = realm
     511        self.mind = mind
     512
     513
     514    def bindResource(self, resource):
     515        def cb(entity):
     516            self.entity = entity
     517            self.connected = True
     518            return entity
     519
     520        d = self.realm.bindResource(self, resource)
     521        d.addCallback(cb)
     522        return d
     523
     524
     525    def logout(self):
     526        self.connected = False
     527        self.realm.unbindResource(self)
     528
     529
     530    def send(self, element):
     531        self.realm.onElement(element, self)
     532
     533
     534    def receive(self, element):
     535        self.mind.send(element)
     536
     537
     538
     539class SessionManager(XMPPHandler):
     540    """
     541    Session Manager.
     542
     543    @ivar xmlstream: XML Stream to inject incoming stanzas from client
     544        connections into. Stanzas where the C{'to'} attribute is not set
     545        or is directed at the local domain are injected as if received on
     546        the XML Stream (using C{dispatch}), other stanzas are injected as if
     547        they were sent from the XML Stream (using C{send}).
     548    """
     549
     550    implements(portal.IRealm)
     551
     552    def __init__(self, domain, accounts):
     553        XMPPHandler.__init__(self)
     554        self.domain = domain
     555        self.accounts = accounts
     556
     557        self.sessions = {}
     558        self.clientStream = utility.EventDispatcher()
     559        self.clientStream.addObserver('/*', self.routeOrDeliver, -1)
     560
     561
     562    def requestAvatar(self, avatarId, mind, *interfaces):
     563        if IUserSession not in interfaces:
     564            raise NotImplementedError(self, interfaces)
     565
     566        localpart = avatarId.decode('utf-8')
     567        entity = JID(tuple=(localpart, self.domain, None))
     568        session = UserSession(entity)
     569        session.loggedIn(self, mind)
     570        return IUserSession, session, session.logout
     571
     572
     573    def bindResource(self, session, resource):
     574        localpart = session.entity.user
     575
     576        try:
     577            userSessions = self.sessions[localpart]
     578        except KeyError:
     579            userSessions = self.sessions[localpart] = {}
     580
     581        if resource is None:
     582            resource = randbytes.secureRandom(8).encode('hex')
     583        elif resource in self.userSessions:
     584            resource = resource + ' ' + randbytes.secureRandom(8).encode('hex')
     585
     586        entity = JID(tuple=(session.entity.user, session.entity.host, resource))
     587        userSessions[resource] = session
     588
     589        return defer.succeed(entity)
     590
     591
     592    def lookupSessions(self, entity):
     593        """
     594        Return all sessions for a user.
     595
     596        @param entity: Entity to retrieve sessions for. This the resource part
     597            will be ignored.
     598        @type entity: L{JID<twisted.words.protocols.jabber.jid.JID>}
     599
     600        @return: Mapping of sessions keyed by resource.
     601        @rtype: C{dict}
     602        """
     603        localpart = entity.user
     604
     605        try:
     606            return self.sessions[localpart]
     607        except:
     608            return {}
     609
     610
     611    def lookupSession(self, entity):
     612        """
     613        Return the session for a particular resource of an entity.
     614
     615        @param entity: Entity to retrieve sessions for.
     616        @type entity: L{JID<twisted.words.protocols.jabber.jid.JID>}
     617
     618        @return: C{UserSession}.
     619        """
     620
     621        userSessions = self.lookupSessions(entity)
     622        return userSessions[entity.resource]
     623
     624
     625
     626    def unbindResource(self, session, reason=None):
     627        session.connected = False
     628
     629        localpart = session.entity.user
     630        resource = session.entity.resource
     631
     632        del self.sessions[localpart][resource]
     633        if not self.sessions[localpart]:
     634            del self.sessions[localpart]
     635
     636        return defer.succeed(None)
     637
     638
     639    def onElement(self, element, session):
     640        # Make sure each stanza has a sender address
     641        if (element.name == 'presence' and
     642            element.getAttribute('type') in ('subscribe', 'subscribed',
     643                                             'unsubscribe', 'unsubscribed')):
     644            element['from'] = session.entity.userhost()
     645        else:
     646            element['from'] = session.entity.full()
     647
     648        self.clientStream.dispatch(element)
     649
     650
     651    def routeOrDeliver(self, element):
     652        """
     653        Deliver a stanza locally or pass on for routing.
     654        """
     655        if element.handled:
     656            return
     657
     658        if (not element.hasAttribute('to') or
     659            internJID(element['to']).host == self.domain):
     660            # This stanza is for local delivery
     661            log.msg("Delivering locally: %r" % element.toXml())
     662            self.xmlstream.dispatch(element)
     663        else:
     664            # This stanza is for remote routing
     665            log.msg("Routing remotely: %r" % element.toXml())
     666            XMPPHandler.send(self, element)
     667
     668
     669    def deliverStanza(self, element, recipient):
     670        session = self.lookupSession(recipient)
     671        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.