source: ralphm-patches/session_manager.patch @ 68:14b59e18ecb6

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

Checkpoint

File size: 6.8 KB
  • wokkel/client.py

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