Mercurial > libervia-backend
diff sat/memory/params.py @ 3123:130f9cb6e0ab
core (memory/params): added `extra` argument to filter out params notably in `getParamsUI`:
In some case, it may be desirable for a frontend to not expose some parameters to user
(e.g. it is the case on Android with the `autoconnect_backend` parameter). An new `extra`
parameter has been added to a couple of parameters method for that: it can contain the
`ignore` key with a list of [category, name] of parameters to skip.
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 25 Jan 2020 21:08:40 +0100 |
parents | 0c29155ac68b |
children | 2b0f739f8a46 |
line wrap: on
line diff
--- a/sat/memory/params.py Sat Jan 25 21:08:39 2020 +0100 +++ b/sat/memory/params.py Sat Jan 25 21:08:40 2020 +0100 @@ -31,6 +31,7 @@ from twisted.words.xish import domish from twisted.words.protocols.jabber import jid from sat.tools.xml_tools import paramsXML2XMLUI, getText +from sat.tools.common import data_format from xml.sax.saxutils import quoteattr # TODO: params should be rewritten using Twisted directly instead of minidom @@ -553,7 +554,7 @@ """Helper method to get a specific attribute. /!\ This method would return encrypted password values, - to get the plain values you have to use _asyncGetParamA. + to get the plain values you have to use asyncGetParamA. @param name: name of the parameter @param category: category of the parameter @param attr: name of the attribute (default: "value") @@ -602,12 +603,12 @@ return value return self._getAttr(node[1], attr, value) - def asyncGetStringParamA( + async def asyncGetStringParamA( self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, - profile_key=C.PROF_KEY_NONE): - d = self.asyncGetParamA(name, category, attr, security_limit, profile_key) - d.addCallback(self._type_to_str) - return d + profile=C.PROF_KEY_NONE): + value = await self.asyncGetParamA( + name, category, attr, security_limit, profile_key=profile) + return self._type_to_str(value) def asyncGetParamA( self, @@ -668,56 +669,50 @@ lambda value: self._asyncGetAttr(node[1], attr, value, profile) ) - def asyncGetParamsValuesFromCategory(self, category, security_limit, profile_key): + def _getParamsValuesFromCategory( + self, category, security_limit, app, extra_s, profile_key): + client = self.host.getClient(profile_key) + extra = data_format.deserialise(extra_s) + return defer.ensureDeferred(self.getParamsValuesFromCategory( + client, category, security_limit, app, extra)) + + async def getParamsValuesFromCategory( + self, client, category, security_limit, app='', extra=None): """Get all parameters "attribute" for a category @param category(unicode): the desired category @param security_limit(int): NO_SECURITY_LIMIT (-1) to return all the params. Otherwise sole the params which have a security level defined *and* lower or equal to the specified value are returned. - @param profile_key: %(doc_profile_key)s + @param app(str): see [getParams] + @param extra(dict): see [getParams] @return (dict): key: param name, value: param value (converted to string if needed) """ # TODO: manage category of general type (without existant profile) - profile = self.getProfileName(profile_key) - if not profile: - log.error(_("Asking params for inexistant profile")) - return "" - - def setValue(value, ret, name): - ret[name] = value + if extra is None: + extra = {} + prof_xml = await self._constructProfileXml(client, security_limit, app, extra) + ret = {} + for category_node in prof_xml.getElementsByTagName("category"): + if category_node.getAttribute("name") == category: + for param_node in category_node.getElementsByTagName("param"): + name = param_node.getAttribute("name") + if not name: + log.warning( + "ignoring attribute without name: {}".format( + param_node.toxml() + ) + ) + continue + value = await self.asyncGetStringParamA( + name, category, security_limit=security_limit, + profile=client.profile) - def returnCategoryXml(prof_xml): - ret = {} - names_d_list = [] - for category_node in prof_xml.getElementsByTagName("category"): - if category_node.getAttribute("name") == category: - for param_node in category_node.getElementsByTagName("param"): - name = param_node.getAttribute("name") - if not name: - log.warning( - "ignoring attribute without name: {}".format( - param_node.toxml() - ) - ) - continue - d = self.asyncGetStringParamA( - name, - category, - security_limit=security_limit, - profile_key=profile, - ) - d.addCallback(setValue, ret, name) - names_d_list.append(d) - break + ret[name] = value + break - prof_xml.unlink() - dlist = defer.gatherResults(names_d_list) - dlist.addCallback(lambda __: ret) - return ret - - d = self._constructProfileXml(security_limit, "", profile) - return d.addCallback(returnCategoryXml) + prof_xml.unlink() + return ret def _getParam( self, category, name, type_=C.INDIVIDUAL, cache=None, profile=C.PROF_KEY_NONE @@ -749,7 +744,7 @@ return None return cache[(category, name)] - def _constructProfileXml(self, security_limit, app, profile): + async def _constructProfileXml(self, client, security_limit, app, extra): """Construct xml for asked profile, filling values when needed /!\ as noticed in doc, don't forget to unlink the minidom.Document @@ -760,166 +755,163 @@ @param profile: profile name (not key !) @return: a deferred that fire a minidom.Document of the profile xml (cf warning above) """ + profile = client.profile def checkNode(node): - """Check the node against security_limit and app""" - return self.checkSecurityLimit(node, security_limit) and self.checkApp( - node, app - ) - - def constructProfile(ignore, profile_cache): - # init the result document - prof_xml = minidom.parseString("<params/>") - cache = {} - - for type_node in self.dom.documentElement.childNodes: - if type_node.nodeName != C.GENERAL and type_node.nodeName != C.INDIVIDUAL: - continue - # we use all params, general and individual - for cat_node in type_node.childNodes: - if cat_node.nodeName != "category": - continue - category = cat_node.getAttribute("name") - dest_params = {} # result (merged) params for category - if category not in cache: - # we make a copy for the new xml - cache[category] = dest_cat = cat_node.cloneNode(True) - to_remove = [] - for node in dest_cat.childNodes: - if node.nodeName != "param": - continue - if not checkNode(node): - to_remove.append(node) - continue - dest_params[node.getAttribute("name")] = node - for node in to_remove: - dest_cat.removeChild(node) - new_node = True - else: - # It's not a new node, we use the previously cloned one - dest_cat = cache[category] - new_node = False - params = cat_node.getElementsByTagName("param") - - for param_node in params: - # we have to merge new params (we are parsing individual parameters, we have to add them - # to the previously parsed general ones) - name = param_node.getAttribute("name") - if not checkNode(param_node): - continue - if name not in dest_params: - # this is reached when a previous category exists - dest_params[name] = param_node.cloneNode(True) - dest_cat.appendChild(dest_params[name]) - - profile_value = self._getParam( - category, - name, - type_node.nodeName, - cache=profile_cache, - profile=profile, - ) - if profile_value is not None: - # there is a value for this profile, we must change the default - if dest_params[name].getAttribute("type") == "list": - for option in dest_params[name].getElementsByTagName( - "option" - ): - if option.getAttribute("value") == profile_value: - option.setAttribute("selected", "true") - else: - try: - option.removeAttribute("selected") - except NotFoundErr: - pass - elif dest_params[name].getAttribute("type") == "jids_list": - jids = profile_value.split("\t") - for jid_elt in dest_params[name].getElementsByTagName( - "jid" - ): - dest_params[name].removeChild( - jid_elt - ) # remove all default - for jid_ in jids: # rebuilt the children with use values - try: - jid.JID(jid_) - except ( - RuntimeError, - jid.InvalidFormat, - AttributeError, - ): - log.warning( - "Incorrect jid value found in jids list: [{}]".format( - jid_ - ) - ) - else: - jid_elt = prof_xml.createElement("jid") - jid_elt.appendChild(prof_xml.createTextNode(jid_)) - dest_params[name].appendChild(jid_elt) - else: - dest_params[name].setAttribute("value", profile_value) - if new_node: - prof_xml.documentElement.appendChild(dest_cat) - - to_remove = [] - for cat_node in prof_xml.documentElement.childNodes: - # we remove empty categories - if cat_node.getElementsByTagName("param").length == 0: - to_remove.append(cat_node) - for node in to_remove: - prof_xml.documentElement.removeChild(node) - return prof_xml + """Check the node against security_limit, app and extra""" + return (self.checkSecurityLimit(node, security_limit) + and self.checkApp(node, app) + and self.checkExtra(node, extra)) if profile in self.params: - d = defer.succeed(None) profile_cache = self.params[profile] else: # profile is not in cache, we load values in a short time cache profile_cache = {} - d = self.loadIndParams(profile, profile_cache) + await self.loadIndParams(profile, profile_cache) + + # init the result document + prof_xml = minidom.parseString("<params/>") + cache = {} - return d.addCallback(constructProfile, profile_cache) + for type_node in self.dom.documentElement.childNodes: + if type_node.nodeName != C.GENERAL and type_node.nodeName != C.INDIVIDUAL: + continue + # we use all params, general and individual + for cat_node in type_node.childNodes: + if cat_node.nodeName != "category": + continue + category = cat_node.getAttribute("name") + dest_params = {} # result (merged) params for category + if category not in cache: + # we make a copy for the new xml + cache[category] = dest_cat = cat_node.cloneNode(True) + to_remove = [] + for node in dest_cat.childNodes: + if node.nodeName != "param": + continue + if not checkNode(node): + to_remove.append(node) + continue + dest_params[node.getAttribute("name")] = node + for node in to_remove: + dest_cat.removeChild(node) + new_node = True + else: + # It's not a new node, we use the previously cloned one + dest_cat = cache[category] + new_node = False + params = cat_node.getElementsByTagName("param") + + for param_node in params: + # we have to merge new params (we are parsing individual parameters, we have to add them + # to the previously parsed general ones) + name = param_node.getAttribute("name") + if not checkNode(param_node): + continue + if name not in dest_params: + # this is reached when a previous category exists + dest_params[name] = param_node.cloneNode(True) + dest_cat.appendChild(dest_params[name]) - def getParamsUI(self, security_limit, app, profile_key): - """ + profile_value = self._getParam( + category, + name, + type_node.nodeName, + cache=profile_cache, + profile=profile, + ) + if profile_value is not None: + # there is a value for this profile, we must change the default + if dest_params[name].getAttribute("type") == "list": + for option in dest_params[name].getElementsByTagName( + "option" + ): + if option.getAttribute("value") == profile_value: + option.setAttribute("selected", "true") + else: + try: + option.removeAttribute("selected") + except NotFoundErr: + pass + elif dest_params[name].getAttribute("type") == "jids_list": + jids = profile_value.split("\t") + for jid_elt in dest_params[name].getElementsByTagName( + "jid" + ): + dest_params[name].removeChild( + jid_elt + ) # remove all default + for jid_ in jids: # rebuilt the children with use values + try: + jid.JID(jid_) + except ( + RuntimeError, + jid.InvalidFormat, + AttributeError, + ): + log.warning( + "Incorrect jid value found in jids list: [{}]".format( + jid_ + ) + ) + else: + jid_elt = prof_xml.createElement("jid") + jid_elt.appendChild(prof_xml.createTextNode(jid_)) + dest_params[name].appendChild(jid_elt) + else: + dest_params[name].setAttribute("value", profile_value) + if new_node: + prof_xml.documentElement.appendChild(dest_cat) + + to_remove = [] + for cat_node in prof_xml.documentElement.childNodes: + # we remove empty categories + if cat_node.getElementsByTagName("param").length == 0: + to_remove.append(cat_node) + for node in to_remove: + prof_xml.documentElement.removeChild(node) + + return prof_xml + + + def _getParamsUI(self, security_limit, app, extra_s, profile_key): + client = self.host.getClient(profile_key) + extra = data_format.deserialise(extra_s) + return defer.ensureDeferred(self.getParamsUI(client, security_limit, app, extra)) + + async def getParamsUI(self, client, security_limit, app, extra=None): + """Get XMLUI to handle parameters + @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params. - Otherwise sole the params which have a security level defined *and* - lower or equal to the specified value are returned. + Otherwise sole the params which have a security level defined *and* + lower or equal to the specified value are returned. @param app: name of the frontend requesting the parameters, or '' to get all parameters - @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile. - @return: a SàT XMLUI for parameters + @param extra (dict, None): extra options. Key can be: + - ignore: list of (category/name) values to remove from parameters + @return(str): a SàT XMLUI for parameters """ - profile = self.getProfileName(profile_key) - if not profile: - log.error(_("Asking params for inexistant profile")) - return "" - d = self.getParams(security_limit, app, profile) - return d.addCallback(lambda param_xml: paramsXML2XMLUI(param_xml)) + param_xml = await self.getParams(client, security_limit, app, extra) + return paramsXML2XMLUI(param_xml) - def getParams(self, security_limit, app, profile_key): + async def getParams(self, client, security_limit, app, extra=None): """Construct xml for asked profile, take params xml as skeleton @param security_limit: NO_SECURITY_LIMIT (-1) to return all the params. - Otherwise sole the params which have a security level defined *and* - lower or equal to the specified value are returned. + Otherwise sole the params which have a security level defined *and* + lower or equal to the specified value are returned. @param app: name of the frontend requesting the parameters, or '' to get all parameters + @param extra (dict, None): extra options. Key can be: + - ignore: list of (category/name) values to remove from parameters @param profile_key: Profile key which can be either a magic (eg: @DEFAULT@) or the name of an existing profile. @return: XML of parameters """ - profile = self.getProfileName(profile_key) - if not profile: - log.error(_("Asking params for inexistant profile")) - return defer.succeed("") - - def returnXML(prof_xml): - return_xml = prof_xml.toxml() - prof_xml.unlink() - return "\n".join((line for line in return_xml.split("\n") if line)) - - return self._constructProfileXml(security_limit, app, profile).addCallback( - returnXML - ) + if extra is None: + extra = {} + prof_xml = await self._constructProfileXml(client, security_limit, app, extra) + return_xml = prof_xml.toxml() + prof_xml.unlink() + return "\n".join((line for line in return_xml.split("\n") if line)) def _getParamNode(self, name, category, type_="@ALL@"): # FIXME: is type_ useful ? """Return a node from the param_xml @@ -1128,6 +1120,7 @@ def checkApp(self, node, app): """Check the given node against the given app. + @param node: parameter node @param app: name of the frontend requesting the parameters, or '' to get all parameters @return: True if this node concerns the given app. @@ -1136,6 +1129,24 @@ return True return node.getAttribute("app") == app + def checkExtra(self, node, extra): + """Check the given node against the extra filters. + + @param node: parameter node + @param app: name of the frontend requesting the parameters, or '' to get all parameters + @return: True if node doesn't match category/name of extra['ignore'] list + """ + ignore_list = extra.get('ignore') + if not ignore_list: + return True + category = node.parentNode.getAttribute('name') + name = node.getAttribute('name') + ignore = [category, name] in ignore_list + if ignore: + log.debug(f"Ignoring parameter {category}/{name} as requested") + return False + return True + def makeOptions(options, selected=None): """Create option XML form dictionary