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.
This commit is contained in:
Goffi 2020-01-25 21:08:40 +01:00
commit 87659cf0f0
2 changed files with 244 additions and 232 deletions

View file

@ -1130,29 +1130,24 @@ class Memory(object):
name, category, attr, security_limit, profile_key
)
def asyncGetParamsValuesFromCategory(
self, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE
def _getParamsValuesFromCategory(
self, category, security_limit, app, extra_s, profile_key
):
return self.params.asyncGetParamsValuesFromCategory(
category, security_limit, profile_key
return self.params._getParamsValuesFromCategory(
category, security_limit, app, extra_s, profile_key
)
def asyncGetStringParamA(
self,
name,
category,
attr="value",
security_limit=C.NO_SECURITY_LIMIT,
profile_key=C.PROF_KEY_NONE,
):
return self.params.asyncGetStringParamA(
name, category, attr, security_limit, profile_key
)
self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT,
profile_key=C.PROF_KEY_NONE):
def getParamsUI(
self, security_limit=C.NO_SECURITY_LIMIT, app="", profile_key=C.PROF_KEY_NONE
):
return self.params.getParamsUI(security_limit, app, profile_key)
profile = self.getProfileName(profile_key)
return defer.ensureDeferred(self.params.asyncGetStringParamA(
name, category, attr, security_limit, profile
))
def _getParamsUI(self, security_limit, app, extra_s, profile_key):
return self.params._getParamsUI(security_limit, app, extra_s, profile_key)
def getParamsCategories(self):
return self.params.getParamsCategories()

View file

@ -31,6 +31,7 @@ from twisted.python.failure import Failure
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 @@ class Params(object):
"""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 @@ class Params(object):
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 @@ class Params(object):
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
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()
)
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
d = self.asyncGetStringParamA(
name,
category,
security_limit=security_limit,
profile_key=profile,
)
d.addCallback(setValue, ret, name)
names_d_list.append(d)
break
continue
value = await self.asyncGetStringParamA(
name, category, security_limit=security_limit,
profile=client.profile)
prof_xml.unlink()
dlist = defer.gatherResults(names_d_list)
dlist.addCallback(lambda __: ret)
return ret
ret[name] = value
break
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 @@ class Params(object):
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 @@ class Params(object):
@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)
return d.addCallback(constructProfile, 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
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
def getParamsUI(self, security_limit, app, profile_key):
"""
@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 @@ class Params(object):
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 @@ class Params(object):
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