Mercurial > libervia-web
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):