Mercurial > libervia-web
diff src/server/server.py @ 1113:cdd389ef97bc
server: code style reformatting using black
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 29 Jun 2018 17:45:26 +0200 |
parents | 7ec1ba86d38d |
children |
line wrap: on
line diff
--- a/src/server/server.py Sun Jun 24 22:21:25 2018 +0200 +++ b/src/server/server.py Fri Jun 29 17:45:26 2018 +0200 @@ -32,8 +32,13 @@ from txjsonrpc import jsonrpclib from sat.core.log import getLogger + log = getLogger(__name__) -from sat_frontends.bridge.dbus_bridge import Bridge, 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 @@ -112,61 +117,85 @@ self.redirections = {} self.inv_redirections = {} # new URL to old URL map - if options['url_redirections_dict'] and not options['url_redirections_profile']: + if options["url_redirections_dict"] and not options["url_redirections_profile"]: # FIXME: url_redirections_profile should not be needed. It is currently used to # redirect to an URL which associate the profile with the service, but this # is not clean, and service should be explicitly specified - raise ValueError(u"url_redirections_profile need to be filled if you want to use url_redirections_dict") + raise ValueError( + u"url_redirections_profile need to be filled if you want to use url_redirections_dict" + ) - for old, new_data in options['url_redirections_dict'].iteritems(): + for old, new_data in options["url_redirections_dict"].iteritems(): # new_data can be a dictionary or a unicode url if isinstance(new_data, dict): # new_data dict must contain either "url", "page" or "path" key (exclusive) # if "path" is used, a file url is constructed with it - if len({'path', 'url', 'page'}.intersection(new_data.keys())) != 1: - raise ValueError(u'You must have one and only one of "url", "page" or "path" key in your url_redirections_dict data') - if 'url' in new_data: - new = new_data['url'] - elif 'page' in new_data: + if len({"path", "url", "page"}.intersection(new_data.keys())) != 1: + raise ValueError( + u'You must have one and only one of "url", "page" or "path" key in your url_redirections_dict data' + ) + if "url" in new_data: + new = new_data["url"] + elif "page" in new_data: new = new_data - new['type'] = 'page' - new.setdefault('path_args', []) - if not isinstance(new['path_args'], list): - log.error(_(u'"path_args" in redirection of {old} must be a list. Ignoring the redirection'.format( - old = old))) + new["type"] = "page" + new.setdefault("path_args", []) + if not isinstance(new["path_args"], list): + log.error( + _( + u'"path_args" in redirection of {old} must be a list. Ignoring the redirection'.format( + old=old + ) + ) + ) continue - new.setdefault('query_args', {}) - if not isinstance(new['query_args'], dict): - log.error(_(u'"query_args" in redirection of {old} must be a dictionary. Ignoring the redirection'.format( - old = old))) + new.setdefault("query_args", {}) + if not isinstance(new["query_args"], dict): + log.error( + _( + u'"query_args" in redirection of {old} must be a dictionary. Ignoring the redirection'.format( + old=old + ) + ) + ) continue - new['path_args'] = [quote(a) for a in new['path_args']] + new["path_args"] = [quote(a) for a in new["path_args"]] # we keep an inversed dict of page redirection (page/path_args => redirecting URL) # so getURL can return the redirecting URL if the same arguments are used # making the URL consistent - args_hash = tuple(new['path_args']) - LiberviaPage.pages_redirects.setdefault(new_data['page'], {})[args_hash] = old + args_hash = tuple(new["path_args"]) + LiberviaPage.pages_redirects.setdefault(new_data["page"], {})[ + args_hash + ] = old # we need lists in query_args because it will be used # as it in request.path_args - for k,v in new['query_args'].iteritems(): + for k, v in new["query_args"].iteritems(): if isinstance(v, basestring): - new['query_args'][k] = [v] - elif 'path' in new_data: - new = 'file:{}'.format(urllib.quote(new_data['path'])) + new["query_args"][k] = [v] + elif "path" in new_data: + new = "file:{}".format(urllib.quote(new_data["path"])) elif isinstance(new_data, basestring): new = new_data new_data = {} else: - log.error(_(u"ignoring invalid redirection value: {new_data}").format(new_data=new_data)) + log.error( + _(u"ignoring invalid redirection value: {new_data}").format( + new_data=new_data + ) + ) continue # some normalization if not old.strip(): # root URL special case - old = '' - elif not old.startswith('/'): - log.error(_(u"redirected url must start with '/', got {value}. Ignoring").format(value=old)) + old = "" + elif not old.startswith("/"): + log.error( + _( + u"redirected url must start with '/', got {value}. Ignoring" + ).format(value=old) + ) continue else: old = self._normalizeURL(old) @@ -176,73 +205,105 @@ # which ared use dynamically when the request is done self.redirections[old] = new if not old: - if new[u'type'] == u'page': - log.info(_(u"Root URL redirected to page {name}").format(name=new[u'page'])) + if new[u"type"] == u"page": + log.info( + _(u"Root URL redirected to page {name}").format( + name=new[u"page"] + ) + ) else: - if new[u'type'] == u'page': - page = LiberviaPage.getPageByName(new[u'page']) - url = page.getURL(*new.get(u'path_args', [])) + if new[u"type"] == u"page": + page = LiberviaPage.getPageByName(new[u"page"]) + url = page.getURL(*new.get(u"path_args", [])) self.inv_redirections[url] = old continue # at this point we have a redirection URL in new, we can parse it - new_url = urlparse.urlsplit(new.encode('utf-8')) + new_url = urlparse.urlsplit(new.encode("utf-8")) # we handle the known URL schemes - if new_url.scheme == 'xmpp': + if new_url.scheme == "xmpp": location = LiberviaPage.getPagePathFromURI(new) if location is None: - log.warning(_(u"ignoring redirection, no page found to handle this URI: {uri}").format(uri=new)) + log.warning( + _( + u"ignoring redirection, no page found to handle this URI: {uri}" + ).format(uri=new) + ) continue request_data = self._getRequestData(location) if old: self.inv_redirections[location] = old - elif new_url.scheme in ('', 'http', 'https'): + elif new_url.scheme in ("", "http", "https"): # direct redirection if new_url.netloc: - raise NotImplementedError(u"netloc ({netloc}) is not implemented yet for url_redirections_dict, it is not possible to redirect to an external website".format( - netloc = new_url.netloc)) - location = urlparse.urlunsplit(('', '', new_url.path, new_url.query, new_url.fragment)).decode('utf-8') + raise NotImplementedError( + u"netloc ({netloc}) is not implemented yet for url_redirections_dict, it is not possible to redirect to an external website".format( + netloc=new_url.netloc + ) + ) + location = urlparse.urlunsplit( + ("", "", new_url.path, new_url.query, new_url.fragment) + ).decode("utf-8") request_data = self._getRequestData(location) if old: self.inv_redirections[location] = old - elif new_url.scheme in ('file'): + elif new_url.scheme in ("file"): # file or directory if new_url.netloc: - raise NotImplementedError(u"netloc ({netloc}) is not implemented for url redirection to file system, it is not possible to redirect to an external host".format( - netloc = new_url.netloc)) + raise NotImplementedError( + u"netloc ({netloc}) is not implemented for url redirection to file system, it is not possible to redirect to an external host".format( + netloc=new_url.netloc + ) + ) path = urllib.unquote(new_url.path) if not os.path.isabs(path): - raise ValueError(u'file redirection must have an absolute path: e.g. file:/path/to/my/file') + raise ValueError( + u"file redirection must have an absolute path: e.g. file:/path/to/my/file" + ) # for file redirection, we directly put child here - segments, dummy, last_segment = old.rpartition('/') - url_segments = segments.split('/') if segments else [] + segments, dummy, last_segment = old.rpartition("/") + url_segments = segments.split("/") if segments else [] current = self for segment in url_segments: resource = web_resource.NoResource() current.putChild(segment, resource) current = resource - resource_class = ProtectedFile if new_data.get('protected',True) else static.File + resource_class = ( + ProtectedFile if new_data.get("protected", True) else static.File + ) current.putChild(last_segment, resource_class(path)) - log.info(u"Added redirection from /{old} to file system path {path}".format(old=old.decode('utf-8'), path=path.decode('utf-8'))) - continue # we don't want to use redirection system, so we continue here + log.info( + u"Added redirection from /{old} to file system path {path}".format( + old=old.decode("utf-8"), path=path.decode("utf-8") + ) + ) + continue # we don't want to use redirection system, so we continue here else: - raise NotImplementedError(u"{scheme}: scheme is not managed for url_redirections_dict".format(scheme=new_url.scheme)) + raise NotImplementedError( + u"{scheme}: scheme is not managed for url_redirections_dict".format( + scheme=new_url.scheme + ) + ) self.redirections[old] = request_data if not old: - log.info(_(u"Root URL redirected to {uri}").format(uri=request_data[1].decode('utf-8'))) + log.info( + _(u"Root URL redirected to {uri}").format( + uri=request_data[1].decode("utf-8") + ) + ) # no need to keep url_redirections*, they will not be used anymore - del options['url_redirections_dict'] - del options['url_redirections_profile'] + del options["url_redirections_dict"] + del options["url_redirections_profile"] # the default root URL, if not redirected - if not '' in self.redirections: - self.redirections[''] = self._getRequestData(C.LIBERVIA_MAIN_PAGE) + if not "" in self.redirections: + self.redirections[""] = self._getRequestData(C.LIBERVIA_MAIN_PAGE) def _normalizeURL(self, url, lower=True): """Return URL normalized for self.redirections dict @@ -253,7 +314,7 @@ """ if lower: url = url.lower() - return '/'.join((p for p in url.encode('utf-8').split('/') if p)) + return "/".join((p for p in url.encode("utf-8").split("/") if p)) def _getRequestData(self, uri): """Return data needed to redirect request @@ -265,10 +326,10 @@ path as in Request.path args as in Request.args """ - uri = uri.encode('utf-8') + uri = uri.encode("utf-8") # XXX: we reuse code from twisted.web.http.py here # as we need to have the same behaviour - x = uri.split(b'?', 1) + x = uri.split(b"?", 1) if len(x) == 1: path = uri @@ -279,7 +340,12 @@ # XXX: splitted path case must not be changed, as it may be significant # (e.g. for blog items) - return self._normalizeURL(path.decode('utf-8'), lower=False).split('/'), uri, path, args + return ( + self._normalizeURL(path.decode("utf-8"), lower=False).split("/"), + uri, + path, + args, + ) def _redirect(self, request, request_data): """Redirect an URL by rewritting request @@ -298,40 +364,48 @@ try: dummy, uri, dummy, dummy = request_data except ValueError: - uri = u'' - log.error(D_(u"recursive redirection, please fix this URL:\n{old} ==> {new}").format( - old=request.uri.decode('utf-8'), - new=uri.decode('utf-8'), - )) + uri = u"" + log.error( + D_( + u"recursive redirection, please fix this URL:\n{old} ==> {new}" + ).format(old=request.uri.decode("utf-8"), new=uri.decode("utf-8")) + ) return web_resource.NoResource() - request._redirected = True # here to avoid recursive redirections + request._redirected = True # here to avoid recursive redirections if isinstance(request_data, dict): - if request_data['type'] == 'page': + if request_data["type"] == "page": try: - page = LiberviaPage.getPageByName(request_data['page']) + page = LiberviaPage.getPageByName(request_data["page"]) except KeyError: - log.error(_(u"Can't find page named \"{name}\" requested in redirection").format( - name = request_data['page'])) + log.error( + _( + u'Can\'t find page named "{name}" requested in redirection' + ).format(name=request_data["page"]) + ) return web_resource.NoResource() - request.postpath = request_data['path_args'][:] + request.postpath + request.postpath = request_data["path_args"][:] + request.postpath try: - request.args.update(request_data['query_args']) + request.args.update(request_data["query_args"]) except (TypeError, ValueError): - log.error(_(u"Invalid args in redirection: {query_args}").format( - query_args=request_data['query_args'])) + log.error( + _(u"Invalid args in redirection: {query_args}").format( + query_args=request_data["query_args"] + ) + ) return web_resource.NoResource() return page else: - raise exceptions.InternalError(u'unknown request_data type') + raise exceptions.InternalError(u"unknown request_data type") else: path_list, uri, path, args = request_data - log.debug(u"Redirecting URL {old} to {new}".format( - old=request.uri.decode('utf-8'), - new=uri.decode('utf-8'), - )) + log.debug( + u"Redirecting URL {old} to {new}".format( + old=request.uri.decode("utf-8"), new=uri.decode("utf-8") + ) + ) # we change the request to reflect the new url request.postpath = path_list[1:] + request.postpath request.args = args @@ -342,8 +416,8 @@ def getChildWithDefault(self, name, request): # XXX: this method is overriden only for root url # which is the only ones who need to be handled before other children - if name == '' and not request.postpath: - return self._redirect(request, self.redirections['']) + if name == "" and not request.postpath: + return self._redirect(request, self.redirections[""]) return super(LiberviaRootResource, self).getChildWithDefault(name, request) def getChild(self, name, request): @@ -354,7 +428,7 @@ # XXX: we want redirections to happen only if everything else failed path_elt = request.prepath + request.postpath for idx in xrange(len(path_elt), 0, -1): - test_url = '/'.join(path_elt[:idx]).lower() + test_url = "/".join(path_elt[:idx]).lower() if test_url in self.redirections: request_data = self.redirections[test_url] request.postpath = path_elt[idx:] @@ -365,7 +439,9 @@ def createSimilarFile(self, path): # XXX: this method need to be overriden to avoid recreating a LiberviaRootResource - f = LiberviaRootResource.__base__(path, self.defaultType, self.ignoredExts, self.registry) + f = LiberviaRootResource.__base__( + path, self.defaultType, self.ignoredExts, self.registry + ) # refactoring by steps, here - constructor should almost certainly take these f.processors = self.processors f.indexNames = self.indexNames[:] @@ -374,7 +450,6 @@ class JSONRPCMethodManager(jsonrpc.JSONRPC): - def __init__(self, sat_host): jsonrpc.JSONRPC.__init__(self) self.sat_host = sat_host @@ -384,7 +459,6 @@ class MethodHandler(JSONRPCMethodManager): - def __init__(self, sat_host): JSONRPCMethodManager.__init__(self, sat_host) @@ -392,10 +466,14 @@ self.session = request.getSession() profile = session_iface.ISATSession(self.session).profile if not profile: - #user is not identified, we return a jsonrpc fault + # user is not identified, we return a jsonrpc fault parsed = jsonrpclib.loads(request.content.read()) - fault = jsonrpclib.Fault(C.ERRNUM_LIBERVIA, C.NOT_ALLOWED) # FIXME: define some standard error codes for libervia - return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) # pylint: disable=E1103 + fault = jsonrpclib.Fault( + C.ERRNUM_LIBERVIA, C.NOT_ALLOWED + ) # FIXME: define some standard error codes for libervia + return jsonrpc.JSONRPC._cbRender( + self, fault, request, parsed.get("id"), parsed.get("jsonrpc") + ) # pylint: disable=E1103 return jsonrpc.JSONRPC.render(self, request) @defer.inlineCallbacks @@ -456,12 +534,16 @@ @param status: any string to describe your status """ profile = session_iface.ISATSession(self.session).profile - return self.sat_host.bridgeCall("setPresence", '', presence, {'': status}, profile) + return self.sat_host.bridgeCall( + "setPresence", "", presence, {"": status}, profile + ) def jsonrpc_messageSend(self, to_jid, msg, subject, type_, extra={}): """send message""" profile = session_iface.ISATSession(self.session).profile - return self.asyncBridgeCall("messageSend", to_jid, msg, subject, type_, extra, profile) + return self.asyncBridgeCall( + "messageSend", to_jid, msg, subject, type_, extra, profile + ) ## PubSub ## @@ -530,7 +612,9 @@ @return: a deferred couple with the list of items and metadatas. """ profile = session_iface.ISATSession(self.session).profile - return self.asyncBridgeCall("mbGet", service_jid, node, max_items, item_ids, extra, profile) + return self.asyncBridgeCall( + "mbGet", service_jid, node, max_items, item_ids, extra, profile + ) def jsonrpc_mbGetFromMany(self, publishers_type, publishers, max_items, extra): """Get many blog nodes at once @@ -542,7 +626,9 @@ @return (str): RT Deferred session id """ profile = session_iface.ISATSession(self.session).profile - return self.sat_host.bridgeCall("mbGetFromMany", publishers_type, publishers, max_items, extra, profile) + return self.sat_host.bridgeCall( + "mbGetFromMany", publishers_type, publishers, max_items, extra, profile + ) def jsonrpc_mbGetFromManyRTResult(self, rt_session): """Get results from RealTime mbGetFromMany session @@ -552,7 +638,15 @@ profile = session_iface.ISATSession(self.session).profile return self.asyncBridgeCall("mbGetFromManyRTResult", rt_session, profile) - def jsonrpc_mbGetFromManyWithComments(self, publishers_type, publishers, max_items, max_comments, rsm_dict, rsm_comments_dict): + def jsonrpc_mbGetFromManyWithComments( + self, + publishers_type, + publishers, + max_items, + max_comments, + rsm_dict, + rsm_comments_dict, + ): """Helper method to get the microblogs and their comments in one shot @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL") @@ -565,7 +659,16 @@ @return (str): RT Deferred session id """ profile = session_iface.ISATSession(self.session).profile - return self.sat_host.bridgeCall("mbGetFromManyWithComments", publishers_type, publishers, max_items, max_comments, rsm_dict, rsm_comments_dict, profile) + return self.sat_host.bridgeCall( + "mbGetFromManyWithComments", + publishers_type, + publishers, + max_items, + max_comments, + rsm_dict, + rsm_comments_dict, + profile, + ) def jsonrpc_mbGetFromManyWithCommentsRTResult(self, rt_session): """Get results from RealTime mbGetFromManyWithComments session @@ -573,8 +676,9 @@ @param rt_session (str): RT Deferred session id """ profile = session_iface.ISATSession(self.session).profile - return self.asyncBridgeCall("mbGetFromManyWithCommentsRTResult", rt_session, profile) - + return self.asyncBridgeCall( + "mbGetFromManyWithCommentsRTResult", rt_session, profile + ) # def jsonrpc_sendMblog(self, type_, dest, text, extra={}): # """ Send microblog message @@ -684,26 +788,49 @@ profile = session_iface.ISATSession(self.session).profile return self.sat_host.bridgeCall("getPresenceStatuses", profile) - def jsonrpc_historyGet(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 = session_iface.ISATSession(self.session) profile = sat_session.profile sat_jid = sat_session.jid if not sat_jid: - raise exceptions.InternalError('session jid should be set') - 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)) + raise exceptions.InternalError("session jid should be set") + 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("historyGet", 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 + # 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 - 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))) + 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 @@ -726,7 +853,9 @@ profile = session_iface.ISATSession(self.session).profile room_id = room_jid.split("@")[0] service = room_jid.split("@")[1] - return self.sat_host.bridgeCall("inviteMUC", contact_jid, service, room_id, {}, profile) + return self.sat_host.bridgeCall( + "inviteMUC", contact_jid, service, room_id, {}, profile + ) def jsonrpc_mucLeave(self, room_jid): """Quit a Multi-User Chat room""" @@ -734,7 +863,7 @@ try: room_jid = jid.JID(room_jid) except: - log.warning('Invalid room jid') + log.warning("Invalid room jid") return return self.sat_host.bridgeCall("mucLeave", room_jid.userhost(), profile) @@ -755,13 +884,18 @@ @param room_jid (unicode): room JID or empty string to generate a unique name """ profile = session_iface.ISATSession(self.session).profile - return self.sat_host.bridgeCall("tarotGameLaunch", other_players, room_jid, profile) + return self.sat_host.bridgeCall( + "tarotGameLaunch", other_players, room_jid, profile + ) def jsonrpc_getTarotCardsPaths(self): """Give the path of all the tarot cards""" _join = os.path.join - _media_dir = _join(self.sat_host.media_dir, '') - return map(lambda x: _join(C.MEDIA_DIR, x[len(_media_dir):]), glob.glob(_join(_media_dir, C.CARDS_DIR, '*_*.png'))) + _media_dir = _join(self.sat_host.media_dir, "") + return map( + lambda x: _join(C.MEDIA_DIR, x[len(_media_dir) :]), + glob.glob(_join(_media_dir, C.CARDS_DIR, "*_*.png")), + ) def jsonrpc_tarotGameReady(self, player, referee): """Tell to the server that we are ready to start the game""" @@ -771,7 +905,9 @@ def jsonrpc_tarotGamePlayCards(self, player_nick, referee, cards): """Tell to the server the cards we want to put on the table""" profile = session_iface.ISATSession(self.session).profile - return self.sat_host.bridgeCall("tarotGamePlayCards", player_nick, referee, cards, profile) + return self.sat_host.bridgeCall( + "tarotGamePlayCards", player_nick, referee, cards, profile + ) def jsonrpc_launchRadioCollective(self, invited, room_jid=""): """Create a room, invite people, and start a radio collective. @@ -789,7 +925,9 @@ @param keys: name of data we want (list) @return: requested data""" if not C.ALLOWED_ENTITY_DATA.issuperset(keys): - raise exceptions.PermissionError("Trying to access unallowed data (hack attempt ?)") + raise exceptions.PermissionError( + "Trying to access unallowed data (hack attempt ?)" + ) profile = session_iface.ISATSession(self.session).profile try: return self.sat_host.bridgeCall("getEntitiesData", jids, keys, profile) @@ -803,7 +941,9 @@ @param keys: name of data we want (list) @return: requested data""" if not C.ALLOWED_ENTITY_DATA.issuperset(keys): - raise exceptions.PermissionError("Trying to access unallowed data (hack attempt ?)") + raise exceptions.PermissionError( + "Trying to access unallowed data (hack attempt ?)" + ) profile = session_iface.ISATSession(self.session).profile try: return self.sat_host.bridgeCall("getEntityData", jid, keys, profile) @@ -822,7 +962,9 @@ session_data = session_iface.ISATSession(self.session) profile = session_data.profile # profile_uuid = session_data.uuid - avatar = yield self.asyncBridgeCall("avatarGet", entity, cache_only, hash_only, profile) + avatar = yield self.asyncBridgeCall( + "avatarGet", entity, cache_only, hash_only, profile + ) if hash_only: defer.returnValue(avatar) else: @@ -847,18 +989,29 @@ if category == "Connection": # we need to manage the followings params here, else SECURITY_LIMIT would block them if param == "JabberID": - return self.asyncBridgeCall("asyncGetParamA", param, category, attribute, profile_key=profile) + return self.asyncBridgeCall( + "asyncGetParamA", param, category, attribute, profile_key=profile + ) elif param == "autoconnect": return defer.succeed(C.BOOL_TRUE) - d = self.asyncBridgeCall("asyncGetParamA", param, category, attribute, C.SECURITY_LIMIT, profile_key=profile) + d = self.asyncBridgeCall( + "asyncGetParamA", + param, + category, + attribute, + C.SECURITY_LIMIT, + profile_key=profile, + ) return d def jsonrpc_setParam(self, name, value, category): profile = session_iface.ISATSession(self.session).profile - return self.sat_host.bridgeCall("setParam", name, value, category, C.SECURITY_LIMIT, profile) + return self.sat_host.bridgeCall( + "setParam", name, value, category, C.SECURITY_LIMIT, profile + ) def jsonrpc_launchAction(self, callback_id, data): - #FIXME: any action can be launched, this can be a huge security issue if callback_id can be guessed + # FIXME: any action can be launched, this can be a huge security issue if callback_id can be guessed # a security system with authorised callback_id must be implemented, similar to the one for authorised params profile = session_iface.ISATSession(self.session).profile d = self.asyncBridgeCall("launchAction", callback_id, data, profile) @@ -876,7 +1029,9 @@ d = self.asyncBridgeCall("getNewAccountDomain") return d - def jsonrpc_syntaxConvert(self, text, syntax_from=C.SYNTAX_XHTML, syntax_to=C.SYNTAX_CURRENT): + def jsonrpc_syntaxConvert( + self, text, syntax_from=C.SYNTAX_XHTML, syntax_to=C.SYNTAX_CURRENT + ): """ Convert a text between two syntaxes @param text: text to convert @param syntax_from: source syntax (e.g. "markdown") @@ -884,7 +1039,9 @@ @param safe: clean resulting XHTML to avoid malicious code if True (forced here) @return: converted text """ profile = session_iface.ISATSession(self.session).profile - return self.sat_host.bridgeCall("syntaxConvert", text, syntax_from, syntax_to, True, profile) + return self.sat_host.bridgeCall( + "syntaxConvert", text, syntax_from, syntax_to, True, profile + ) def jsonrpc_getLastResource(self, jid_s): """Get the last active resource of that contact.""" @@ -902,11 +1059,10 @@ return self.sat_host.bridgeCall("skipOTR", profile) def jsonrpc_namespacesGet(self): - return self.sat_host.bridgeCall('namespacesGet') + return self.sat_host.bridgeCall("namespacesGet") class WaitingRequests(dict): - def setRequest(self, request, profile, register_with_ext_jid=False): """Add the given profile to the waiting list. @@ -963,18 +1119,22 @@ - except login, every method is jsonrpc - user doesn't need to be authentified for explicitely listed methods, but must be for all others """ - if request.postpath == ['login']: + if request.postpath == ["login"]: return self.loginOrRegister(request) _session = request.getSession() parsed = jsonrpclib.loads(request.content.read()) method = parsed.get("method") # pylint: disable=E1103 - if method not in ['getSessionMetadata', 'registerParams', 'menusGet']: - #if we don't call these methods, we need to be identified + if method not in ["getSessionMetadata", "registerParams", "menusGet"]: + # if we don't call these methods, we need to be identified profile = session_iface.ISATSession(_session).profile if not profile: - #user is not identified, we return a jsonrpc fault - fault = jsonrpclib.Fault(C.ERRNUM_LIBERVIA, C.NOT_ALLOWED) # FIXME: define some standard error codes for libervia - return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) # pylint: disable=E1103 + # user is not identified, we return a jsonrpc fault + fault = jsonrpclib.Fault( + C.ERRNUM_LIBERVIA, C.NOT_ALLOWED + ) # FIXME: define some standard error codes for libervia + return jsonrpc.JSONRPC._cbRender( + self, fault, request, parsed.get("id"), parsed.get("jsonrpc") + ) # pylint: disable=E1103 self.request = request return jsonrpc.JSONRPC.render(self, request) @@ -987,24 +1147,24 @@ - a return value from self._loginAccount or self._registerNewAccount """ try: - submit_type = request.args['submit_type'][0] + submit_type = request.args["submit_type"][0] except KeyError: return C.BAD_REQUEST - if submit_type == 'register': + if submit_type == "register": self._registerNewAccount(request) return server.NOT_DONE_YET - elif submit_type == 'login': + elif submit_type == "login": self._loginAccount(request) return server.NOT_DONE_YET - return Exception('Unknown submit type') + return Exception("Unknown submit type") @defer.inlineCallbacks def _registerNewAccount(self, request): try: - login = request.args['register_login'][0] - password = request.args['register_password'][0] - email = request.args['email'][0] + login = request.args["register_login"][0] + password = request.args["register_password"][0] + email = request.args["email"][0] except KeyError: request.write(C.BAD_REQUEST) request.finish() @@ -1030,8 +1190,8 @@ @param request: request of the register form """ try: - login = request.args['login'][0] - password = request.args['login_password'][0] + login = request.args["login"][0] + password = request.args["login_password"][0] except KeyError: request.write(C.BAD_REQUEST) request.finish() @@ -1041,9 +1201,11 @@ try: status = yield self.sat_host.connect(request, login, password) - except (exceptions.DataError, - exceptions.ProfileUnknownError, - exceptions.PermissionError): + except ( + exceptions.DataError, + exceptions.ProfileUnknownError, + exceptions.PermissionError, + ): request.write(C.PROFILE_AUTH_ERROR) request.finish() return @@ -1084,7 +1246,9 @@ _session = self.request.getSession() profile = session_iface.ISATSession(_session).profile if self.waiting_profiles.getRequest(profile): - raise jsonrpclib.Fault(1, C.ALREADY_WAITING) # FIXME: define some standard error codes for libervia + 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.bridgeCall("connect", profile) return server.NOT_DONE_YET @@ -1121,35 +1285,54 @@ 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.bridgeCall("menusGet", '', C.SECURITY_LIMIT) + return self.sat_host.bridgeCall("menusGet", "", C.SECURITY_LIMIT) def _getSecurityWarning(self): """@return: a security warning message, or None if the connection is secure""" - if self.request.URLPath().scheme == 'https' or not self.sat_host.options['security_warning']: + if ( + self.request.URLPath().scheme == "https" + or not self.sat_host.options["security_warning"] + ): return None - text = "<p>" + D_("You are about to connect to an unsecure service.") + "</p><p> </p><p>" + text = ( + "<p>" + + D_("You are about to connect to an unsecure service.") + + "</p><p> </p><p>" + ) - if self.sat_host.options['connection_type'] == 'both': - new_port = (':%s' % self.sat_host.options['port_https_ext']) if self.sat_host.options['port_https_ext'] != HTTPS_PORT else '' - url = "https://%s" % self.request.URLPath().netloc.replace(':%s' % self.sat_host.options['port'], new_port) - text += D_('Please read our %(faq_prefix)ssecurity notice%(faq_suffix)s regarding HTTPS') % {'faq_prefix': '<a href="http://salut-a-toi.org/faq.html#https" target="#">', 'faq_suffix': '</a>'} - text += "</p><p>" + D_('and use the secure version of this website:') - text += '</p><p> </p><p align="center"><a href="%(url)s">%(url)s</a>' % {'url': url} + if self.sat_host.options["connection_type"] == "both": + new_port = ( + (":%s" % self.sat_host.options["port_https_ext"]) + if self.sat_host.options["port_https_ext"] != HTTPS_PORT + else "" + ) + url = "https://%s" % self.request.URLPath().netloc.replace( + ":%s" % self.sat_host.options["port"], new_port + ) + text += D_( + "Please read our %(faq_prefix)ssecurity notice%(faq_suffix)s regarding HTTPS" + ) % { + "faq_prefix": '<a href="http://salut-a-toi.org/faq.html#https" target="#">', + "faq_suffix": "</a>", + } + text += "</p><p>" + D_("and use the secure version of this website:") + text += '</p><p> </p><p align="center"><a href="%(url)s">%(url)s</a>' % { + "url": url + } else: - text += D_('You should ask your administrator to turn on HTTPS.') + text += D_("You should ask your administrator to turn on HTTPS.") return text + "</p><p> </p>" class SignalHandler(jsonrpc.JSONRPC): - def __init__(self, sat_host): web_resource.Resource.__init__(self) self.register = None self.sat_host = sat_host self._last_service_prof_disconnect = time.time() - self.signalDeferred = {} # dict of deferred (key: profile, value: Deferred) - # which manages the long polling HTTP request with signals + self.signalDeferred = {} # dict of deferred (key: profile, value: Deferred) + # which manages the long polling HTTP request with signals self.queue = {} def plugRegister(self, register): @@ -1164,7 +1347,7 @@ if self.queue[profile]: return self.queue[profile].pop(0) else: - #the queue is empty, we delete the profile from queue + # the queue is empty, we delete the profile from queue del self.queue[profile] _session.lock() # we don't want the session to expire as long as this connection is active @@ -1177,9 +1360,9 @@ try: _session.expire() except KeyError: - # FIXME: happen if session is ended using login page + # FIXME: happen if session is ended using login page # when pyjamas page is also launched - log.warning(u'session is already expired') + log.warning(u"session is already expired") except IndexError: log.error("Deferred result should be a tuple with fonction name first") @@ -1190,6 +1373,7 @@ def getGenericCb(self, function_name): """Return a generic function which send all params to signalDeferred.callback function must have profile as last argument""" + def genericCb(*args): profile = args[-1] if not profile in self.sat_host.prof_connected: @@ -1198,10 +1382,11 @@ try: signal_callback = self.signalDeferred[profile].callback except KeyError: - self.queue.setdefault(profile,[]).append(signal_data) + self.queue.setdefault(profile, []).append(signal_data) else: signal_callback(signal_data) del self.signalDeferred[profile] + return genericCb def actionNewHandler(self, action_data, action_id, security_limit, profile): @@ -1219,13 +1404,17 @@ # raise an exception if it's not OK # and read value in sat.conf if security_limit >= C.SECURITY_LIMIT: - log.debug(u"Ignoring action {action_id}, blocked by security limit".format(action_id=action_id)) + log.debug( + u"Ignoring action {action_id}, blocked by security limit".format( + action_id=action_id + ) + ) return signal_data = ("actionNew", (action_data, action_id, security_limit)) try: signal_callback = self.signalDeferred[profile].callback except KeyError: - self.queue.setdefault(profile,[]).append(signal_data) + self.queue.setdefault(profile, []).append(signal_data) else: signal_callback(signal_data) del self.signalDeferred[profile] @@ -1252,20 +1441,36 @@ # and display an error message disconnect_delta = time.time() - self._last_service_prof_disconnect if disconnect_delta < 15: - log.error(_(u'Service profile disconnected twice in a short time, please check connection')) + log.error( + _( + u"Service profile disconnected twice in a short time, please check connection" + ) + ) else: - log.info(_(u"Service profile has been disconnected, but we need it! Reconnecting it...")) - d = self.sat_host.bridgeCall("connect", profile, - self.sat_host.options['passphrase'], - {}, + log.info( + _( + u"Service profile has been disconnected, but we need it! Reconnecting it..." ) - d.addErrback(lambda failure_: log.error(_(u"Can't reconnect service profile, please check connection: {reason}").format( - reason=failure_))) + ) + d = self.sat_host.bridgeCall( + "connect", profile, self.sat_host.options["passphrase"], {} + ) + d.addErrback( + lambda failure_: log.error( + _( + u"Can't reconnect service profile, please check connection: {reason}" + ).format(reason=failure_) + ) + ) self._last_service_prof_disconnect = time.time() return if not profile in self.sat_host.prof_connected: - log.info(_(u"'disconnected' signal received for a not connected profile ({profile})").format(profile=profile)) + log.info( + _( + u"'disconnected' signal received for a not connected profile ({profile})" + ).format(profile=profile) + ) return self.sat_host.prof_connected.remove(profile) if profile in self.signalDeferred: @@ -1284,9 +1489,13 @@ parsed = jsonrpclib.loads(request.content.read()) profile = session_iface.ISATSession(_session).profile if not profile: - #user is not identified, we return a jsonrpc fault - fault = jsonrpclib.Fault(C.ERRNUM_LIBERVIA, C.NOT_ALLOWED) # FIXME: define some standard error codes for libervia - return jsonrpc.JSONRPC._cbRender(self, fault, request, parsed.get('id'), parsed.get('jsonrpc')) # pylint: disable=E1103 + # user is not identified, we return a jsonrpc fault + fault = jsonrpclib.Fault( + C.ERRNUM_LIBERVIA, C.NOT_ALLOWED + ) # FIXME: define some standard error codes for libervia + return jsonrpc.JSONRPC._cbRender( + self, fault, request, parsed.get("id"), parsed.get("jsonrpc") + ) # pylint: disable=E1103 self.request = request return jsonrpc.JSONRPC.render(self, request) @@ -1294,8 +1503,9 @@ class UploadManager(web_resource.Resource): """This class manage the upload of a file It redirect the stream to SàT core backend""" + isLeaf = True - NAME = 'path' # name use by the FileUpload + NAME = "path" # name use by the FileUpload def __init__(self, sat_host): self.sat_host = sat_host @@ -1327,12 +1537,12 @@ """ filename = self._getFileName(request) filepath = os.path.join(self.upload_dir, filename) - #FIXME: the uploaded file is fully loaded in memory at form parsing time so far + # FIXME: the uploaded file is fully loaded in memory at form parsing time so far # (see twisted.web.http.Request.requestReceived). A custom requestReceived should # be written in the futur. In addition, it is not yet possible to get progression informations # (see http://twistedmatrix.com/trac/ticket/288) - with open(filepath, 'w') as f: + with open(filepath, "w") as f: f.write(request.args[self.NAME][0]) def finish(d): @@ -1343,17 +1553,22 @@ # a DBusException instance --> extract the data from the backtrace? request.finish() - d = JSONRPCMethodManager(self.sat_host).asyncBridgeCall(*self._fileWritten(request, filepath)) + d = JSONRPCMethodManager(self.sat_host).asyncBridgeCall( + *self._fileWritten(request, filepath) + ) d.addCallbacks(lambda d: finish(d), lambda failure: finish(failure)) return server.NOT_DONE_YET class UploadManagerRadioCol(UploadManager): - NAME = 'song' + NAME = "song" def _getFileName(self, request): - extension = os.path.splitext(request.args['filename'][0])[1] - return "%s%s" % (str(uuid.uuid4()), extension) # XXX: chromium doesn't seem to play song without the .ogg extension, even with audio/ogg mime-type + extension = os.path.splitext(request.args["filename"][0])[1] + return "%s%s" % ( + str(uuid.uuid4()), + extension, + ) # XXX: chromium doesn't seem to play song without the .ogg extension, even with audio/ogg mime-type def _fileWritten(self, request, filepath): """Called once the file is actually written on disk @@ -1363,11 +1578,11 @@ to be called followed by its arguments. """ profile = session_iface.ISATSession(request.getSession()).profile - return ("radiocolSongAdded", request.args['referee'][0], filepath, profile) + return ("radiocolSongAdded", request.args["referee"][0], filepath, profile) class UploadManagerAvatar(UploadManager): - NAME = 'avatar_path' + NAME = "avatar_path" def _getFileName(self, request): return str(uuid.uuid4()) @@ -1391,24 +1606,26 @@ self.initialised = defer.Deferred() self.waiting_profiles = WaitingRequests() # FIXME: should be removed - if self.options['base_url_ext']: - self.base_url_ext = self.options.pop('base_url_ext') - if self.base_url_ext[-1] != '/': - self.base_url_ext += '/' + if self.options["base_url_ext"]: + self.base_url_ext = self.options.pop("base_url_ext") + if self.base_url_ext[-1] != "/": + self.base_url_ext += "/" self.base_url_ext_data = urlparse.urlsplit(self.base_url_ext) else: self.base_url_ext = None # we split empty string anyway so we can do things like # scheme = self.base_url_ext_data.scheme or 'https' - self.base_url_ext_data = urlparse.urlsplit('') + self.base_url_ext_data = urlparse.urlsplit("") - if not self.options['port_https_ext']: - self.options['port_https_ext'] = self.options['port_https'] - if self.options['data_dir'] == DATA_DIR_DEFAULT: - coerceDataDir(self.options['data_dir']) # this is not done when using the default value + if not self.options["port_https_ext"]: + self.options["port_https_ext"] = self.options["port_https"] + if self.options["data_dir"] == DATA_DIR_DEFAULT: + coerceDataDir( + self.options["data_dir"] + ) # this is not done when using the default value - self.html_dir = os.path.join(self.options['data_dir'], C.HTML_DIR) - self.themes_dir = os.path.join(self.options['data_dir'], C.THEMES_DIR) + self.html_dir = os.path.join(self.options["data_dir"], C.HTML_DIR) + self.themes_dir = os.path.join(self.options["data_dir"], C.THEMES_DIR) self._cleanup = [] @@ -1436,63 +1653,100 @@ _register = Register(self) _upload_radiocol = UploadManagerRadioCol(self) _upload_avatar = UploadManagerAvatar(self) - d = self.bridgeCall('namespacesGet') + d = self.bridgeCall("namespacesGet") d.addCallback(self._namespacesGetCb) d.addErrback(self._namespacesGetEb) 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)) + # 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) + 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) # JSON APIs - self.putChild('json_signal_api', self.signal_handler) - self.putChild('json_api', MethodHandler(self)) - self.putChild('register_api', _register) + self.putChild("json_signal_api", self.signal_handler) + self.putChild("json_api", MethodHandler(self)) + self.putChild("register_api", _register) # files upload - self.putChild('upload_radiocol', _upload_radiocol) - self.putChild('upload_avatar', _upload_avatar) + self.putChild("upload_radiocol", _upload_radiocol) + self.putChild("upload_avatar", _upload_avatar) # static pages - self.putChild('blog_legacy', MicroBlog(self)) + self.putChild("blog_legacy", MicroBlog(self)) self.putChild(C.THEMES_URL, ProtectedFile(self.themes_dir)) # websocket - if self.options['connection_type'] in ('https', 'both'): + if self.options["connection_type"] in ("https", "both"): wss = websockets.LiberviaPageWSProtocol.getResource(self, secure=True) - self.putChild('wss', wss) - if self.options['connection_type'] in ('http', 'both'): + self.putChild("wss", wss) + if self.options["connection_type"] in ("http", "both"): ws = websockets.LiberviaPageWSProtocol.getResource(self, secure=False) - self.putChild('ws', ws) + self.putChild("ws", ws) - # Libervia pages + # Libervia pages LiberviaPage.importPages(self) - LiberviaPage.setMenu(self.options['menu_json']) + LiberviaPage.setMenu(self.options["menu_json"]) ## following signal is needed for cache handling in Libervia pages - self.bridge.register_signal("psEventRaw", partial(LiberviaPage.onNodeEvent, self), "plugin") - self.bridge.register_signal("messageNew", partial(LiberviaPage.onSignal, self, "messageNew")) + self.bridge.register_signal( + "psEventRaw", partial(LiberviaPage.onNodeEvent, self), "plugin" + ) + self.bridge.register_signal( + "messageNew", partial(LiberviaPage.onSignal, self, "messageNew") + ) - # Progress handling - self.bridge.register_signal("progressStarted", partial(ProgressHandler._signal, "started")) - self.bridge.register_signal("progressFinished", partial(ProgressHandler._signal, "finished")) - self.bridge.register_signal("progressError", partial(ProgressHandler._signal, "error")) - + # Progress handling + self.bridge.register_signal( + "progressStarted", partial(ProgressHandler._signal, "started") + ) + self.bridge.register_signal( + "progressFinished", partial(ProgressHandler._signal, "finished") + ) + self.bridge.register_signal( + "progressError", partial(ProgressHandler._signal, "error") + ) # media dirs # FIXME: get rid of dirname and "/" in C.XXX_DIR @@ -1501,20 +1755,25 @@ 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 + 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')) + if self.version[-1] == "D": + self.putChild("test", web_util.Redirect("/libervia_test.html")) # redirections root._initRedirections(self.options) - server.Request.defaultContentType = 'text/html; charset=utf-8' - wrapped = web_resource.EncodingResourceWrapper(root, [server.GzipEncoderFactory()]) + server.Request.defaultContentType = "text/html; charset=utf-8" + wrapped = web_resource.EncodingResourceWrapper( + root, [server.GzipEncoderFactory()] + ) self.site = server.Site(wrapped) self.site.sessionFactory = LiberviaSession self.renderer = template.Renderer(self) - self.putChild('templates', ProtectedFile(self.renderer.base_dir)) + self.putChild("templates", ProtectedFile(self.renderer.base_dir)) def initEb(self, failure): log.error(_(u"Init error: {msg}").format(msg=failure)) @@ -1522,8 +1781,10 @@ return failure def _bridgeCb(self): - self.bridge.getReady(lambda: self.initialised.callback(None), - lambda failure: self.initialised.errback(Exception(failure))) + self.bridge.getReady( + lambda: self.initialised.callback(None), + lambda failure: self.initialised.errback(Exception(failure)), + ) self.initialised.addCallback(self.backendReady) self.initialised.addErrback(self.initEb) @@ -1539,12 +1800,14 @@ def full_version(self): """Return the full version of Libervia (with extra data when in development mode)""" version = self.version - if version[-1] == 'D': + if version[-1] == "D": # we are in debug version, we add extra data try: return self._version_cache except AttributeError: - self._version_cache = u"{} ({})".format(version, utils.getRepositoryData(libervia)) + self._version_cache = u"{} ({})".format( + version, utils.getRepositoryData(libervia) + ) return self._version_cache else: return version @@ -1567,7 +1830,11 @@ d.callback(args[0]) def _errback(result): - d.errback(failure.Failure(jsonrpclib.Fault(C.ERRNUM_BRIDGE_ERRBACK, result.classname))) + d.errback( + failure.Failure( + jsonrpclib.Fault(C.ERRNUM_BRIDGE_ERRBACK, result.classname) + ) + ) kwargs["callback"] = _callback kwargs["errback"] = _errback @@ -1590,34 +1857,42 @@ session = request.getSession() sat_session = session_iface.ISATSession(session) if sat_session.profile: - log.error(_(u'/!\\ Session has already a profile, this should NEVER happen!')) + log.error(_(u"/!\\ Session has already a profile, this should NEVER happen!")) raise failure.Failure(exceptions.ConflictError("Already active")) sat_session.profile = profile self.prof_connected.add(profile) - cache_dir = os.path.join(self.cache_root_dir, u'profiles', regex.pathEscape(profile)) + cache_dir = os.path.join( + self.cache_root_dir, u"profiles", regex.pathEscape(profile) + ) # FIXME: would be better to have a global /cache URL which redirect to profile's cache directory, without uuid self.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)) + 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={profile})".format(profile=profile,)) + log.info(u"Session expired (profile={profile})".format(profile=profile)) self.cache_resource.delEntity(sat_session.uuid) - log.debug(_(u"profile cache resource {uuid} deleted").format(uuid = sat_session.uuid)) + log.debug( + _(u"profile cache resource {uuid} deleted").format(uuid=sat_session.uuid) + ) try: - #We purge the queue + # We purge the queue del self.signal_handler.queue[profile] except KeyError: pass - #and now we disconnect the profile + # and now we disconnect the profile self.bridgeCall("disconnect", profile) session.notifyOnExpire(onExpire) # FIXME: those session infos should be returned by connect or isConnected - infos = yield self.bridgeCall('sessionInfosGet', profile) - sat_session.jid = jid.JID(infos['jid']) - sat_session.backend_started = int(infos['started']) + infos = yield self.bridgeCall("sessionInfosGet", profile) + sat_session.jid = jid.JID(infos["jid"]) + sat_session.backend_started = int(infos["started"]) state = C.PROFILE_LOGGED_EXT_JID if register_with_ext_jid else C.PROFILE_LOGGED defer.returnValue(state) @@ -1645,18 +1920,20 @@ """ # XXX: all security checks must be done here, even if present in javascript - if login.startswith('@'): - raise failure.Failure(exceptions.DataError('No profile_key allowed')) + if login.startswith("@"): + raise failure.Failure(exceptions.DataError("No profile_key allowed")) - if login.startswith('guest@@') and login.count('@') == 2: + if login.startswith("guest@@") and login.count("@") == 2: log.debug("logging a guest account") - elif '@' in login: - if login.count('@') != 1: - raise failure.Failure(exceptions.DataError('Invalid login: {login}'.format(login=login))) + elif "@" in login: + if login.count("@") != 1: + raise failure.Failure( + exceptions.DataError("Invalid login: {login}".format(login=login)) + ) try: login_jid = jid.JID(login) except (RuntimeError, jid.InvalidFormat, AttributeError): - raise failure.Failure(exceptions.DataError('No profile_key allowed')) + raise failure.Failure(exceptions.DataError("No profile_key allowed")) # FIXME: should it be cached? new_account_domain = yield self.bridgeCall("getNewAccountDomain") @@ -1672,17 +1949,29 @@ profile = yield self.bridgeCall("profileNameGet", login) except Exception: # XXX: ProfileUnknownError wouldn't work, it's encapsulated # FIXME: find a better way to handle bridge errors - if login_jid is not None and login_jid.user: # try to create a new sat profile using the XMPP credentials + if ( + login_jid is not None and login_jid.user + ): # try to create a new sat profile using the XMPP credentials if not self.options["allow_registration"]: - log.warning(u"Trying to register JID account while registration is not allowed") - raise failure.Failure(exceptions.DataError(u"JID login while registration is not allowed")) - profile = login # FIXME: what if there is a resource? + log.warning( + u"Trying to register JID account while registration is not allowed" + ) + raise failure.Failure( + exceptions.DataError( + u"JID login while registration is not allowed" + ) + ) + profile = login # FIXME: what if there is a resource? connect_method = "asyncConnectWithXMPPCredentials" register_with_ext_jid = True - else: # non existing username + else: # non existing username raise failure.Failure(exceptions.ProfileUnknownError()) else: - if profile != login or (not password and profile not in self.options['empty_password_allowed_warning_dangerous_list']): + if profile != login or ( + not password + and profile + not in self.options["empty_password_allowed_warning_dangerous_list"] + ): # profiles with empty passwords are restricted to local frontends raise exceptions.PermissionError register_with_ext_jid = False @@ -1695,13 +1984,15 @@ # yes, there is if sat_session.profile != profile: # it's a different profile, we need to disconnect it - log.warning(_(u"{new_profile} requested login, but {old_profile} was already connected, disconnecting {old_profile}").format( - old_profile = sat_session.profile, - new_profile = profile)) + log.warning( + _( + u"{new_profile} requested login, but {old_profile} was already connected, disconnecting {old_profile}" + ).format(old_profile=sat_session.profile, new_profile=profile) + ) self.purgeSession(request) if self.waiting_profiles.getRequest(profile): - # FIXME: check if and when this can happen + # FIXME: check if and when this can happen raise failure.Failure(exceptions.NotReady("Already waiting")) self.waiting_profiles.setRequest(request, profile, register_with_ext_jid) @@ -1710,22 +2001,37 @@ except Exception as failure_: fault = failure_.faultString self.waiting_profiles.purgeRequest(profile) - if fault in ('PasswordError', 'ProfileUnknownError'): - log.info(u"Profile {profile} doesn't exist or the submitted password is wrong".format(profile=profile)) + if fault in ("PasswordError", "ProfileUnknownError"): + log.info( + u"Profile {profile} doesn't exist or the submitted password is wrong".format( + profile=profile + ) + ) raise failure.Failure(ValueError(C.PROFILE_AUTH_ERROR)) - elif fault == 'SASLAuthError': - log.info(u"The XMPP password of profile {profile} is wrong".format(profile=profile)) + elif fault == "SASLAuthError": + log.info( + u"The XMPP password of profile {profile} is wrong".format( + profile=profile + ) + ) raise failure.Failure(ValueError(C.XMPP_AUTH_ERROR)) - elif fault == 'NoReply': - log.info(_("Did not receive a reply (the timeout expired or the connection is broken)")) + elif fault == "NoReply": + log.info( + _( + "Did not receive a reply (the timeout expired or the connection is broken)" + ) + ) raise exceptions.TimeOutError else: - log.error(u'Unmanaged fault string "{fault}" in errback for the connection of profile {profile}'.format( - fault=fault, profile=profile)) + log.error( + u'Unmanaged fault string "{fault}" in errback for the connection of profile {profile}'.format( + fault=fault, profile=profile + ) + ) raise failure.Failure(exceptions.InternalError(fault)) if connected: - # profile is already connected in backend + # profile is already connected in backend # do we have a corresponding session in Libervia? sat_session = session_iface.ISATSession(request.getSession()) if sat_session.profile: @@ -1733,14 +2039,21 @@ if sat_session.profile != profile: # existing session should have been ended above # so this line should never be reached - log.error(_(u'session profile [{session_profile}] differs from login profile [{profile}], this should not happen!').format( - session_profile = sat_session.profile, - profile = profile - )) + log.error( + _( + u"session profile [{session_profile}] differs from login profile [{profile}], this should not happen!" + ).format(session_profile=sat_session.profile, profile=profile) + ) raise exceptions.InternalError("profile mismatch") defer.returnValue(C.SESSION_ACTIVE) - log.info(_(u"profile {profile} was already connected in backend".format(profile=profile))) - # no, we have to create it + log.info( + _( + u"profile {profile} was already connected in backend".format( + profile=profile + ) + ) + ) + # no, we have to create it state = yield self._logged(profile, request) defer.returnValue(state) @@ -1760,12 +2073,18 @@ @raise PermissionError: registration is now allowed in server configuration """ if not self.options["allow_registration"]: - log.warning(_(u"Registration received while it is not allowed, hack attempt?")) - raise failure.Failure(exceptions.PermissionError(u"Registration is not allowed on this server")) + log.warning( + _(u"Registration received while it is not allowed, hack attempt?") + ) + raise failure.Failure( + exceptions.PermissionError(u"Registration is not allowed on this server") + ) - if not re.match(C.REG_LOGIN_RE, login) or \ - not re.match(C.REG_EMAIL_RE, email, re.IGNORECASE) or \ - len(password) < C.PASSWORD_MIN_LENGTH: + if ( + not re.match(C.REG_LOGIN_RE, login) + or not re.match(C.REG_EMAIL_RE, email, re.IGNORECASE) + or len(password) < C.PASSWORD_MIN_LENGTH + ): return C.INVALID_INPUT def registered(result): @@ -1778,8 +2097,11 @@ elif status == "InternalError": return C.INTERNAL_ERROR else: - log.error(_(u'Unknown registering error status: {status }').format( - status = status)) + log.error( + _(u"Unknown registering error status: {status }").format( + status=status + ) + ) return status d = self.bridgeCall("registerSatAccount", email, password, login) @@ -1798,6 +2120,7 @@ def startService(self): """Connect the profile for Libervia and start the HTTP(S) server(s)""" + def eb(e): log.error(_(u"Connection failed: %s") % e) self.stop() @@ -1807,14 +2130,22 @@ connected = self.bridge.isConnected(C.SERVICE_PROFILE) except Exception as e: # we don't want the traceback - msg = [l for l in unicode(e).split('\n') if l][-1] - log.error(u"Can't check service profile ({profile}), are you sure it exists ?\n{error}".format( - profile=C.SERVICE_PROFILE, error=msg)) + msg = [l for l in unicode(e).split("\n") if l][-1] + log.error( + u"Can't check service profile ({profile}), are you sure it exists ?\n{error}".format( + profile=C.SERVICE_PROFILE, error=msg + ) + ) self.stop() return if not connected: - self.bridge.connect(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() @@ -1825,7 +2156,10 @@ 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()])) + self.root.putChild( + path, + web_resource.EncodingResourceWrapper(resource, [server.GzipEncoderFactory()]), + ) def getExtBaseURLData(self, request): """Retrieve external base URL Data @@ -1840,23 +2174,31 @@ ext_data = self.base_url_ext_data url_path = request.URLPath() if not ext_data.scheme or not ext_data.netloc: - # ext_data is not specified, we check headers - if request.requestHeaders.hasHeader('x-forwarded-host'): + # ext_data is not specified, we check headers + if request.requestHeaders.hasHeader("x-forwarded-host"): # we are behing a proxy # we fill proxy_scheme and proxy_netloc value - proxy_host = request.requestHeaders.getRawHeaders('x-forwarded-host')[0] + proxy_host = request.requestHeaders.getRawHeaders("x-forwarded-host")[0] try: - proxy_server = request.requestHeaders.getRawHeaders('x-forwarded-server')[0] + proxy_server = request.requestHeaders.getRawHeaders( + "x-forwarded-server" + )[0] except TypeError: # no x-forwarded-server found, we use proxy_host proxy_netloc = proxy_host else: # if the proxy host has a port, we use it with server name - proxy_port = urlparse.urlsplit(u'//{}'.format(proxy_host)).port - proxy_netloc = u'{}:{}'.format(proxy_server, proxy_port) if proxy_port is not None else proxy_server - proxy_netloc = proxy_netloc.decode('utf-8') + proxy_port = urlparse.urlsplit(u"//{}".format(proxy_host)).port + proxy_netloc = ( + u"{}:{}".format(proxy_server, proxy_port) + if proxy_port is not None + else proxy_server + ) + proxy_netloc = proxy_netloc.decode("utf-8") try: - proxy_scheme = request.requestHeaders.getRawHeaders('x-forwarded-proto')[0].decode('utf-8') + proxy_scheme = request.requestHeaders.getRawHeaders( + "x-forwarded-proto" + )[0].decode("utf-8") except TypeError: proxy_scheme = None else: @@ -1865,12 +2207,14 @@ proxy_scheme, proxy_netloc = None, None return urlparse.SplitResult( - ext_data.scheme or proxy_scheme or url_path.scheme.decode('utf-8'), - ext_data.netloc or proxy_netloc or url_path.netloc.decode('utf-8'), - ext_data.path or u'/', - '', '') + ext_data.scheme or proxy_scheme or url_path.scheme.decode("utf-8"), + ext_data.netloc or proxy_netloc or url_path.netloc.decode("utf-8"), + ext_data.path or u"/", + "", + "", + ) - def getExtBaseURL(self, request, path='', query='', fragment='', scheme=None): + def getExtBaseURL(self, request, path="", query="", fragment="", scheme=None): """Get external URL according to given elements external URL is the URL seen by external user @@ -1883,11 +2227,15 @@ @return (unicode): external URL """ split_result = self.getExtBaseURLData(request) - return urlparse.urlunsplit(( - split_result.scheme.decode('utf-8') if scheme is None else scheme, - split_result.netloc.decode('utf-8'), - os.path.join(split_result.path, path), - query, fragment)) + return urlparse.urlunsplit( + ( + split_result.scheme.decode("utf-8") if scheme is None else scheme, + split_result.netloc.decode("utf-8"), + os.path.join(split_result.path, path), + query, + fragment, + ) + ) def checkRedirection(self, url): """check is a part of the URL prefix is redirected then replace it @@ -1896,14 +2244,14 @@ @return (unicode): possibly redirected URL which should link to the same location """ inv_redirections = self.root.inv_redirections - url_parts = url.strip(u'/').split(u'/') + url_parts = url.strip(u"/").split(u"/") for idx in xrange(len(url), 0, -1): - test_url = u'/' + u'/'.join(url_parts[:idx]) + test_url = u"/" + u"/".join(url_parts[:idx]) if test_url in inv_redirections: rem_url = url_parts[idx:] return os.path.join( - u'/', - u'/'.join([inv_redirections[test_url]] + rem_url)) + u"/", u"/".join([inv_redirections[test_url]] + rem_url) + ) return url ## Sessions ## @@ -1943,16 +2291,21 @@ """ sat_session = self.getSessionData(request, session_iface.ISATSession) if sat_session.profile is None: - raise exceptions.InternalError(u'profile must be set to use this method') + raise exceptions.InternalError(u"profile must be set to use this method") affiliation = sat_session.getAffiliation(service, node) if affiliation is not None: defer.returnValue(affiliation) else: try: - affiliations = yield self.bridgeCall('psAffiliationsGet', service.full(), node, sat_session.profile) + affiliations = yield self.bridgeCall( + "psAffiliationsGet", service.full(), node, sat_session.profile + ) except Exception as e: - log.warning("Can't retrieve affiliation for {service}/{node}: {reason}".format( - service=service, node=node, reason=e)) + log.warning( + "Can't retrieve affiliation for {service}/{node}: {reason}".format( + service=service, node=node, reason=e + ) + ) affiliation = u"" else: try: @@ -1966,10 +2319,10 @@ def getWebsocketURL(self, request): base_url_split = self.getExtBaseURLData(request) - if base_url_split.scheme.endswith('s'): - scheme = u'wss' + if base_url_split.scheme.endswith("s"): + scheme = u"wss" else: - scheme = u'ws' + scheme = u"ws" return self.getExtBaseURL(request, path=scheme, scheme=scheme) @@ -1981,8 +2334,8 @@ def getHTTPDate(self, timestamp=None): now = time.gmtime(timestamp) fmt_date = u"{day_name}, %d {month_name} %Y %H:%M:%S GMT".format( - day_name = C.HTTP_DAYS[now.tm_wday], - month_name = C.HTTP_MONTH[now.tm_mon-1]) + day_name=C.HTTP_DAYS[now.tm_wday], month_name=C.HTTP_MONTH[now.tm_mon - 1] + ) return time.strftime(fmt_date, now) ## TLS related methods ## @@ -1992,15 +2345,14 @@ Must be called only if TLS is activated """ - if not self.options['tls_certificate']: + if not self.options["tls_certificate"]: log.error(u"a TLS certificate is needed to activate HTTPS connection") self.quit(1) - if not self.options['tls_private_key']: - self.options['tls_private_key'] = self.options['tls_certificate'] + if not self.options["tls_private_key"]: + self.options["tls_private_key"] = self.options["tls_certificate"] - - if not self.options['tls_private_key']: - self.options['tls_private_key'] = self.options['tls_certificate'] + if not self.options["tls_private_key"]: + self.options["tls_private_key"] = self.options["tls_certificate"] def _loadCertificates(self, f): """Read a .pem file with a list of certificates @@ -2016,9 +2368,13 @@ while True: line = f.readline() buf.append(line) - if '-----END CERTIFICATE-----' in line: - certificates.append(OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, ''.join(buf))) - buf=[] + if "-----END CERTIFICATE-----" in line: + certificates.append( + OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, "".join(buf) + ) + ) + buf = [] elif not line: log.debug(u"{} certificate(s) found".format(len(certificates))) return certificates @@ -2048,24 +2404,41 @@ cert_options = {} - for name, option, method in [('privateKey', 'tls_private_key', self._loadPKey), - ('certificate', 'tls_certificate', self._loadCertificate), - ('extraCertChain', 'tls_chain', self._loadCertificates)]: + for name, option, method in [ + ("privateKey", "tls_private_key", self._loadPKey), + ("certificate", "tls_certificate", self._loadCertificate), + ("extraCertChain", "tls_chain", self._loadCertificates), + ]: path = self.options[option] if not path: - assert option=='tls_chain' + assert option == "tls_chain" continue log.debug(u"loading {option} from {path}".format(option=option, path=path)) try: with open(path) as f: cert_options[name] = method(f) except IOError as e: - log.error(u"Error while reading file {path} for option {option}: {error}".format(path=path, option=option, error=e)) + log.error( + u"Error while reading file {path} for option {option}: {error}".format( + path=path, option=option, error=e + ) + ) self.quit(2) except OpenSSL.crypto.Error: - log.error(u"Error while parsing file {path} for option {option}, are you sure it is a valid .pem file?".format(path=path, option=option)) - if option=='tls_private_key' and self.options['tls_certificate'] == path: - log.error(u"You are using the same file for private key and public certificate, make sure that both a in {path} or use --tls_private_key option".format(path=path)) + log.error( + u"Error while parsing file {path} for option {option}, are you sure it is a valid .pem file?".format( + path=path, option=option + ) + ) + if ( + option == "tls_private_key" + and self.options["tls_certificate"] == path + ): + log.error( + u"You are using the same file for private key and public certificate, make sure that both a in {path} or use --tls_private_key option".format( + path=path + ) + ) self.quit(2) return ssl.CertificateOptions(**cert_options) @@ -2081,20 +2454,30 @@ """ # now that we have service profile connected, we add resource for its cache service_path = regex.pathEscape(C.SERVICE_PROFILE) - cache_dir = os.path.join(self.cache_root_dir, u'profiles', service_path) + cache_dir = os.path.join(self.cache_root_dir, u"profiles", service_path) self.cache_resource.putChild(service_path, ProtectedFile(cache_dir)) - self.service_cache_url = u'/' + os.path.join(C.CACHE_DIR, service_path) + self.service_cache_url = u"/" + os.path.join(C.CACHE_DIR, service_path) session_iface.SATSession.service_cache_url = self.service_cache_url - if self.options['connection_type'] in ('https', 'both'): + if self.options["connection_type"] in ("https", "both"): self._TLSOptionsCheck() context_factory = self._getTLSContextFactory() - reactor.listenSSL(self.options['port_https'], self.site, context_factory) - if self.options['connection_type'] in ('http', 'both'): - if self.options['connection_type'] == 'both' and self.options['redirect_to_https']: - reactor.listenTCP(self.options['port'], server.Site(RedirectToHTTPS(self.options['port'], self.options['port_https_ext']))) + reactor.listenSSL(self.options["port_https"], self.site, context_factory) + if self.options["connection_type"] in ("http", "both"): + if ( + self.options["connection_type"] == "both" + and self.options["redirect_to_https"] + ): + reactor.listenTCP( + self.options["port"], + server.Site( + RedirectToHTTPS( + self.options["port"], self.options["port_https_ext"] + ) + ), + ) else: - reactor.listenTCP(self.options['port'], self.site) + reactor.listenTCP(self.options["port"], self.site) @defer.inlineCallbacks def stopService(self): @@ -2122,7 +2505,6 @@ class RedirectToHTTPS(web_resource.Resource): - def __init__(self, old_port, new_port): web_resource.Resource.__init__(self) self.isLeaf = True @@ -2130,10 +2512,14 @@ self.new_port = new_port def render(self, request): - netloc = request.URLPath().netloc.replace(':%s' % self.old_port, ':%s' % self.new_port) + netloc = request.URLPath().netloc.replace( + ":%s" % self.old_port, ":%s" % self.new_port + ) url = "https://" + netloc + request.uri return web_util.redirectTo(url, request) registerAdapter(session_iface.SATSession, server.Session, session_iface.ISATSession) -registerAdapter(session_iface.SATGuestSession, server.Session, session_iface.ISATGuestSession) +registerAdapter( + session_iface.SATGuestSession, server.Session, session_iface.ISATGuestSession +)