From 54b8a41bb6f61555511714ce7ff476132b97f45d Mon Sep 17 00:00:00 2001 From: flvr-odoo Date: Tue, 11 Jul 2023 15:01:46 +0200 Subject: [PATCH] [FIX] adding a standalone test for markup This commits make a new standalone test for Markup() The idea is to only flag the usage of Markup inside of runbot if it is called on the non-constant string. Markup(' %s ') % text #should not raise a flag, Markup(' %s ' % 'text') #should raise a flag closes odoo/odoo#131206 Signed-off-by: Vranckx Florian (flvr) Co-authored-by: Alex Roscav --- odoo/tests/_odoo_checker_markup.py | 68 ++++++++++++++++++++++++++++++ odoo/tests/test_security.py | 18 ++++++++ 2 files changed, 86 insertions(+) create mode 100644 odoo/tests/_odoo_checker_markup.py create mode 100644 odoo/tests/test_security.py diff --git a/odoo/tests/_odoo_checker_markup.py b/odoo/tests/_odoo_checker_markup.py new file mode 100644 index 00000000000..cb39cfb0dc9 --- /dev/null +++ b/odoo/tests/_odoo_checker_markup.py @@ -0,0 +1,68 @@ +from typing import Optional + +import astroid +from pylint import interfaces, checkers + +try: + from pylint.checkers.utils import only_required_for_messages +except ImportError: + from pylint.checkers.utils import check_messages as only_required_for_messages + + +class OdooBaseChecker(checkers.BaseChecker): + __implements__ = interfaces.IAstroidChecker + name = 'odoo' + + msgs = { + 'E8504': ( + 'The Markup constructor called with a non-constant argument', + 'non-const-markup', + '', + ) + } + + @only_required_for_messages('non-const-markup') + def visit_call(self, node): + if (isinstance(node.func, astroid.Name) and + node.func.name == "Markup" and + not self._is_constant(node.args[0])): + self.add_message('non-const-markup', node=node, col_offset=len(node.as_string().split('\\n'))) + elif (isinstance(node.func, astroid.Attribute) and + node.func.attrname == "Markup" and + not self._is_constant(node.args[0])): + self.add_message('non-const-markup', node=node, col_offset=len(node.as_string().split('\\n'))) + + def _is_constant(self, node: Optional[astroid.node_classes.NodeNG]) -> bool: + if isinstance(node, astroid.Const) or node is None: + return True + elif isinstance(node, astroid.JoinedStr): + return all(map(self._is_constant, node.values)) + elif isinstance(node, astroid.FormattedValue): + return self._is_constant(node.value) + elif isinstance(node, astroid.Name): + _, assignments = node.lookup(node.name) + return all(map(self._is_constant, assignments)) + elif isinstance(node, astroid.AssignName): + return self._is_constant(node.parent) + elif isinstance(node, astroid.Assign): + return self._is_constant(node.value) + elif (isinstance(node, astroid.Call) and + isinstance(node.func, astroid.Attribute) and + node.func.attrname in ["format", "join"]): + return (self._is_constant(node.func.expr) and + all(map(self._is_constant, node.args)) and + all(map(self._is_constant, node.keywords))) + elif isinstance(node, astroid.Keyword): + return self._is_constant(node.value) + elif isinstance(node, (astroid.List, astroid.Set, astroid.Tuple)): + return all(map(self._is_constant, node.elts)) + elif isinstance(node, astroid.Dict): + return all(map(self._is_constant, node.values)) + elif isinstance(node, astroid.BinOp): + return self._is_constant(node.left) and self._is_constant(node.right) + elif isinstance(node, astroid.IfExp): + return self._is_constant(node.body) and self._is_constant(node.orelse) + return False + +def register(linter): + linter.register_checker(OdooBaseChecker(linter)) diff --git a/odoo/tests/test_security.py b/odoo/tests/test_security.py new file mode 100644 index 00000000000..d79f78bbbb5 --- /dev/null +++ b/odoo/tests/test_security.py @@ -0,0 +1,18 @@ +from os import environ, path +import subprocess +import sys + +#This test is meant to be standalone, correct usage : python test_security.py file1 file2 file3 ... + +if __name__ == '__main__': + + HERE = path.dirname(__file__) + + if 'PYTHONPATH' not in environ: + environ['PYTHONPATH'] = HERE + else: + environ['PYTHONPATH'] += ':' + HERE + + command = ['pylint', '--rcfile=/dev/null', '--disable=all', '--output-format', 'json', '--enable=non-const-markup', '--reports=n', '--load-plugins=_odoo_checker_markup', *sys.argv[1:]] + + proc = subprocess.run(command, env=environ, check=True)