diff src/server/server.py @ 914:0c0551967bdf

server, browser: partial Libervia fix Libervia was broken following the refactorings. This commit partially fixes it : Libervia is starting, avatar, blog and message are working again, but not everything is restablished yet. following things have been fixed/changed: - new dependency: shortuuid - D-Bus bridge is working again - fixed naming in several bridge methods - register method changed to register_signal - fixed Chat widget, which was not working anymore since the refactoring - avatar now use avatarGet. Cache dir is accessible using a session specific uuid, to avoid cache leak (i.e. accessing cache of other profiles) - server: new uuid attribute in session data Browser code is not fully working yet, notably OTR and contact list are not fully fixed.
author Goffi <goffi@goffi.org>
date Sun, 26 Feb 2017 18:32:47 +0100
parents 58f611481e6d
children e9e9d9d893a8
line wrap: on
line diff
--- a/src/server/server.py	Sun Aug 28 19:25:52 2016 +0200
+++ b/src/server/server.py	Sun Feb 26 18:32:47 2017 +0100
@@ -33,10 +33,11 @@
 
 from sat.core.log import getLogger
 log = getLogger(__name__)
-from sat_frontends.bridge.DBus import DBusBridgeFrontend, BridgeExceptionNoService, const_TIMEOUT as BRIDGE_TIMEOUT
+from sat_frontends.bridge.dbus_bridge import Bridge, BridgeExceptionNoService, const_TIMEOUT as BRIDGE_TIMEOUT
 from sat.core.i18n import _, D_
 from sat.core import exceptions
 from sat.tools import utils
+from sat.tools.common import regex
 
 import re
 import glob
@@ -47,6 +48,7 @@
 import uuid
 import urlparse
 import urllib
+import shortuuid
 from zope.interface import Interface, Attribute, implements
 from httplib import HTTPS_PORT
 import libervia
@@ -68,6 +70,7 @@
 class ISATSession(Interface):
     profile = Attribute("Sat profile")
     jid = Attribute("JID associated with the profile")
+    uuid = Attribute("uuid associated with the profile session")
 
 
 class SATSession(object):
@@ -76,6 +79,7 @@
     def __init__(self, session):
         self.profile = None
         self.jid = None
+        self.uuid = unicode(shortuuid.uuid())
 
 
 class LiberviaSession(server.Session):
@@ -650,7 +654,7 @@
         profile = ISATSession(self.session).profile
         return self.sat_host.bridge.getPresenceStatuses(profile)
 
-    def jsonrpc_getHistory(self, from_jid, to_jid, size, between, search=''):
+    def jsonrpc_historyGet(self, from_jid, to_jid, size, between, search=''):
         """Return history for the from_jid/to_jid couple"""
         sat_session = ISATSession(self.session)
         profile = sat_session.profile
@@ -661,15 +665,15 @@
         if jid.JID(from_jid).userhost() != sat_jid.userhost() and jid.JID(to_jid).userhost() != sat_jid.userhost():
             log.error(u"Trying to get history from a different jid (given (browser): {}, real (backend): {}), maybe a hack attempt ?".format(from_jid, sat_jid))
             return {}
-        d = self.asyncBridgeCall("getHistory", from_jid, to_jid, size, between, search, profile)
+        d = self.asyncBridgeCall("historyGet", from_jid, to_jid, size, between, search, profile)
 
         def show(result_dbus):
             result = []
             for line in result_dbus:
                 #XXX: we have to do this stupid thing because Python D-Bus use its own types instead of standard types
                 #     and txJsonRPC doesn't accept D-Bus types, resulting in a empty query
-                timestamp, from_jid, to_jid, message, mess_type, extra = line
-                result.append((float(timestamp), unicode(from_jid), unicode(to_jid), unicode(message), unicode(mess_type), dict(extra)))
+                uuid, timestamp, from_jid, to_jid, message, subject, mess_type, extra = line
+                result.append((unicode(uuid), float(timestamp), unicode(from_jid), unicode(to_jid), dict(message), dict(subject), unicode(mess_type), dict(extra)))
             return result
         d.addCallback(show)
         return d
@@ -784,6 +788,19 @@
         profile = ISATSession(self.session).profile
         return self.sat_host.bridge.getCard(jid_, profile)
 
+    @defer.inlineCallbacks
+    def jsonrpc_avatarGet(self, entity, cache_only, hash_only):
+        session_data = ISATSession(self.session)
+        profile = session_data.profile
+        # profile_uuid = session_data.uuid
+        avatar = yield self.asyncBridgeCall("avatarGet", entity, cache_only, hash_only, profile)
+        if hash_only:
+            defer.returnValue(avatar)
+        else:
+            filename = os.path.basename(avatar)
+            avatar_url = os.path.join(C.CACHE_DIR, session_data.uuid, filename)
+            defer.returnValue(avatar_url)
+
     def jsonrpc_getAccountDialogUI(self):
         """Get the dialog for managing user account
         @return: XML string of the XMLUI"""
@@ -920,7 +937,7 @@
         _session = request.getSession()
         parsed = jsonrpclib.loads(request.content.read())
         method = parsed.get("method")  # pylint: disable=E1103
-        if  method not in ['getSessionMetadata', 'registerParams', 'getMenus']:
+        if  method not in ['getSessionMetadata', 'registerParams', 'menusGet']:
             #if we don't call these methods, we need to be identified
             profile = ISATSession(_session).profile
             if not profile:
@@ -1020,7 +1037,7 @@
                 return
             register_with_ext_jid = False
 
-            connect_method = "asyncConnect"
+            connect_method = "connect"
 
         if self.waiting_profiles.getRequest(profile):
             request.write(C.ALREADY_WAITING)
@@ -1105,16 +1122,22 @@
         _session = request.getSession()
         sat_session = ISATSession(_session)
         if sat_session.profile:
-            log.error(('/!\\ Session has already a profile, this should NEVER happen!'))
+            log.error(_(u'/!\\ Session has already a profile, this should NEVER happen!'))
             request.write(C.SESSION_ACTIVE)
             request.finish()
             return
         # we manage profile server side to avoid profile spoofing
         sat_session.profile = profile
         self.sat_host.prof_connected.add(profile)
+        cache_dir = os.path.join(self.sat_host.cache_root_dir, regex.pathEscape(profile))
+        # FIXME: would be better to have a global /cache URL which redirect to profile's cache directory, without uuid
+        self.sat_host.cache_resource.putChild(sat_session.uuid, ProtectedFile(cache_dir))
+        log.debug(_(u"profile cache resource added from {uuid} to {path}").format(uuid=sat_session.uuid, path=cache_dir))
 
         def onExpire():
             log.info(u"Session expired (profile=%s)" % (profile,))
+            self.sat_host.cache_resource.delEntity(sat_session.uuid)
+            log.debug(_(u"profile cache resource {uuid} deleted").format(uuid = sat_session.uuid))
             try:
                 #We purge the queue
                 del self.sat_host.signal_handler.queue[profile]
@@ -1133,13 +1156,13 @@
         profile = ISATSession(_session).profile
         return self.sat_host.bridge.isConnected(profile)
 
-    def jsonrpc_asyncConnect(self):
+    def jsonrpc_connect(self):
         _session = self.request.getSession()
         profile = ISATSession(_session).profile
         if self.waiting_profiles.getRequest(profile):
             raise jsonrpclib.Fault(1, C.ALREADY_WAITING)  # FIXME: define some standard error codes for libervia
         self.waiting_profiles.setRequest(self.request, profile)
-        self.sat_host.bridge.asyncConnect(profile)
+        self.sat_host.bridge.connect(profile)
         return server.NOT_DONE_YET
 
     def jsonrpc_getSessionMetadata(self):
@@ -1171,10 +1194,10 @@
         # params = """<params><individual>...</category></individual>"""
         # self.sat_host.bridge.paramsRegisterApp(params, C.SECURITY_LIMIT, C.APP_NAME)
 
-    def jsonrpc_getMenus(self):
+    def jsonrpc_menusGet(self):
         """Return the parameters XML for profile"""
         # XXX: we put this method in Register because we get menus before being logged
-        return self.sat_host.bridge.getMenus('', C.SECURITY_LIMIT)
+        return self.sat_host.bridge.menusGet('', C.SECURITY_LIMIT)
 
     def _getSecurityWarning(self):
         """@return: a security warning message, or None if the connection is secure"""
@@ -1437,80 +1460,84 @@
 
         self._cleanup = []
 
-        root = LiberviaRootResource(self.options, self.html_dir)
-
         self.signal_handler = SignalHandler(self)
-        _register = Register(self)
-        _upload_radiocol = UploadManagerRadioCol(self)
-        _upload_avatar = UploadManagerAvatar(self)
-        self.signal_handler.plugRegister(_register)
         self.sessions = {}  # key = session value = user
         self.prof_connected = set()  # Profiles connected
 
         ## bridge ##
         try:
-            self.bridge = DBusBridgeFrontend()
+            self.bridge = Bridge()
         except BridgeExceptionNoService:
             print(u"Can't connect to SàT backend, are you sure it's launched ?")
             sys.exit(1)
-
-        def backendReady(dummy):
-            self.bridge.register("connected", self.signal_handler.connected)
-            self.bridge.register("disconnected", self.signal_handler.disconnected)
-            #core
-            for signal_name in ['presenceUpdate', 'messageNew', 'subscribe', 'contactDeleted',
-                                'newContact', 'entityDataUpdated', 'paramUpdate']:
-                self.bridge.register(signal_name, self.signal_handler.getGenericCb(signal_name))
-            # XXX: actionNew is handled separately because the handler must manage security_limit
-            self.bridge.register('actionNew', self.signal_handler.actionNewHandler)
-            #plugins
-            for signal_name in ['psEvent', 'mucRoomJoined', 'tarotGameStarted', 'tarotGameNew', 'tarotGameChooseContrat',
-                                'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore', 'tarotGamePlayers',
-                                'radiocolStarted', 'radiocolPreload', 'radiocolPlay', 'radiocolNoUpload', 'radiocolUploadOk', 'radiocolSongRejected', 'radiocolPlayers',
-                                'mucRoomLeft', 'mucRoomUserChangedNick', 'chatStateReceived']:
-                self.bridge.register(signal_name, self.signal_handler.getGenericCb(signal_name), "plugin")
-            self.media_dir = self.bridge.getConfig('', 'media_dir')
-            self.local_dir = self.bridge.getConfig('', 'local_dir')
+        self.bridge.bridgeConnect(callback=self._bridgeCb, errback=self._bridgeEb)
 
-            ## URLs ##
-            def putChild(path, resource):
-                """Add a child to the root resource"""
-                # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders)
-                root.putChild(path, web_resource.EncodingResourceWrapper(resource, [server.GzipEncoderFactory()]))
-
-            # JSON APIs
-            putChild('json_signal_api', self.signal_handler)
-            putChild('json_api', MethodHandler(self))
-            putChild('register_api', _register)
+    def backendReady(self, dummy):
+        self.root = root = LiberviaRootResource(self.options, self.html_dir)
+        _register = Register(self)
+        _upload_radiocol = UploadManagerRadioCol(self)
+        _upload_avatar = UploadManagerAvatar(self)
+        self.signal_handler.plugRegister(_register)
+        self.bridge.register_signal("connected", self.signal_handler.connected)
+        self.bridge.register_signal("disconnected", self.signal_handler.disconnected)
+        #core
+        for signal_name in ['presenceUpdate', 'messageNew', 'subscribe', 'contactDeleted',
+                            'newContact', 'entityDataUpdated', 'paramUpdate']:
+            self.bridge.register_signal(signal_name, self.signal_handler.getGenericCb(signal_name))
+        # XXX: actionNew is handled separately because the handler must manage security_limit
+        self.bridge.register_signal('actionNew', self.signal_handler.actionNewHandler)
+        #plugins
+        for signal_name in ['psEvent', 'mucRoomJoined', 'tarotGameStarted', 'tarotGameNew', 'tarotGameChooseContrat',
+                            'tarotGameShowCards', 'tarotGameInvalidCards', 'tarotGameCardsPlayed', 'tarotGameYourTurn', 'tarotGameScore', 'tarotGamePlayers',
+                            'radiocolStarted', 'radiocolPreload', 'radiocolPlay', 'radiocolNoUpload', 'radiocolUploadOk', 'radiocolSongRejected', 'radiocolPlayers',
+                            'mucRoomLeft', 'mucRoomUserChangedNick', 'chatStateReceived']:
+            self.bridge.register_signal(signal_name, self.signal_handler.getGenericCb(signal_name), "plugin")
+        self.media_dir = self.bridge.getConfig('', 'media_dir')
+        self.local_dir = self.bridge.getConfig('', 'local_dir')
+        self.cache_root_dir = os.path.join(
+            self.local_dir,
+            C.CACHE_DIR)
 
-            # files upload
-            putChild('upload_radiocol', _upload_radiocol)
-            putChild('upload_avatar', _upload_avatar)
+        # JSON APIs
+        self.putChild('json_signal_api', self.signal_handler)
+        self.putChild('json_api', MethodHandler(self))
+        self.putChild('register_api', _register)
 
-            # static pages
-            putChild('blog', MicroBlog(self))
-            putChild(C.THEMES_URL, ProtectedFile(self.themes_dir))
+        # files upload
+        self.putChild('upload_radiocol', _upload_radiocol)
+        self.putChild('upload_avatar', _upload_avatar)
 
-            # media dirs
-            putChild(os.path.dirname(C.MEDIA_DIR), ProtectedFile(self.media_dir))
-            putChild(os.path.dirname(C.AVATARS_DIR), ProtectedFile(os.path.join(self.local_dir, C.AVATARS_DIR)))
+        # static pages
+        self.putChild('blog', MicroBlog(self))
+        self.putChild(C.THEMES_URL, ProtectedFile(self.themes_dir))
 
-            # special
-            putChild('radiocol', ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg"))  # FIXME: We cheat for PoC because we know we are on the same host, so we use directly upload dir
-            # pyjamas tests, redirected only for dev versions
-            if self.version[-1] == 'D':
-                putChild('test', web_util.Redirect('/libervia_test.html'))
+        # media dirs
+        # FIXME: get rid of dirname and "/" in C.XXX_DIR
+        self.putChild(os.path.dirname(C.MEDIA_DIR), ProtectedFile(self.media_dir))
+        self.cache_resource = web_resource.NoResource()
+        self.putChild(C.CACHE_DIR, self.cache_resource)
+
+        # special
+        self.putChild('radiocol', ProtectedFile(_upload_radiocol.getTmpDir(), defaultType="audio/ogg"))  # FIXME: We cheat for PoC because we know we are on the same host, so we use directly upload dir
+        # pyjamas tests, redirected only for dev versions
+        if self.version[-1] == 'D':
+            self.putChild('test', web_util.Redirect('/libervia_test.html'))
 
 
-            wrapped = web_resource.EncodingResourceWrapper(root, [server.GzipEncoderFactory()])
-            self.site = server.Site(wrapped)
-            self.site.sessionFactory = LiberviaSession
+        wrapped = web_resource.EncodingResourceWrapper(root, [server.GzipEncoderFactory()])
+        self.site = server.Site(wrapped)
+        self.site.sessionFactory = LiberviaSession
 
+
+    def _bridgeCb(self):
         self.bridge.getReady(lambda: self.initialised.callback(None),
                              lambda failure: self.initialised.errback(Exception(failure)))
-        self.initialised.addCallback(backendReady)
+        self.initialised.addCallback(self.backendReady)
         self.initialised.addErrback(lambda failure: log.error(u"Init error: %s" % failure))
 
+    def _bridgeEb(self, failure):
+        log.error(u"Can't connect to bridge: {}".format(failure))
+
     @property
     def version(self):
         """Return the short version of Libervia"""
@@ -1555,13 +1582,19 @@
                 self.stop()
                 return
             if not connected:
-                self.bridge.asyncConnect(C.SERVICE_PROFILE, self.options['passphrase'],
-                                         callback=self._startService, errback=eb)
+                self.bridge.connect(C.SERVICE_PROFILE, self.options['passphrase'],
+                                    {}, callback=self._startService, errback=eb)
             else:
                 self._startService()
 
         self.initialised.addCallback(initOk)
 
+    ## URLs ##
+    def putChild(self, path, resource):
+        """Add a child to the root resource"""
+        # FIXME: check that no information is leaked (c.f. https://twistedmatrix.com/documents/current/web/howto/using-twistedweb.html#request-encoders)
+        self.root.putChild(path, web_resource.EncodingResourceWrapper(resource, [server.GzipEncoderFactory()]))
+
     ## TLS related methods ##
 
     def _TLSOptionsCheck(self):