[IMP] base: view validation: detect js implementation details

This commit adds checks in the view validation, to detect the use
of implementation details in archs. The goal is to prevent people
from using owl directives in archs (e.g. t-on-click). Recall that
a view arch isn't an owl template, it must follow the DSL of the
given view type.

In kanban, gantt and activity archs, using `t-xxx` attributes is
forbidden, except for directives historically supported by qweb
(e.g. `t-esc`).

In other views, using `t-xxx` attributes is forbidden (except for
`t-translation`).

In all views, using attributes `data-tooltip`, `data-tooltip-template`
and `data-tooltip-info` is forbidden.

Task 3085357

closes odoo/odoo#107083

Related: odoo/enterprise#35352
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Aaron Bohy
2023-01-06 15:32:47 +01:00
parent 299d4921c8
commit 7fd5cec72b
3 changed files with 130 additions and 3 deletions
+3
View File
@@ -14,3 +14,6 @@ class View(models.Model):
name_manager.has_field(node, node.get('name'), {})
return
return super()._postprocess_tag_field(node, name_manager, node_info)
def _is_qweb_based_view(self, view_type):
return view_type == "activity" or super()._is_qweb_based_view(view_type)
+37 -3
View File
@@ -2,15 +2,12 @@
import ast
import collections
import datetime
import functools
import inspect
import json
import logging
import math
import pprint
import re
import time
import uuid
import warnings
@@ -1417,6 +1414,7 @@ actual arch.
model = self.env[model_name].with_context(lang=None)
name_manager = NameManager(model)
view_type = node.tag
# use a stack to recursively traverse the tree
stack = [(node, editable, full)]
while stack:
@@ -1428,6 +1426,7 @@ actual arch.
node_info = {
'editable': editable and self._editable_node(node, name_manager),
'validate': validate,
'view_type': view_type,
}
# tag-specific validation
@@ -1733,6 +1732,9 @@ actual arch.
msg = 'o_progressbar class must have aria-valuemaxattribute'
self._log_view_warning(msg, node)
def _is_qweb_based_view(self, view_type):
return view_type in ("kanban", "gantt")
def _validate_attrs(self, node, name_manager, node_info):
""" Generic validation of node attrs. """
for attr, expr in node.items():
@@ -1813,6 +1815,12 @@ actual arch.
msg = "attribute 'group' is not valid. Did you mean 'groups'?"
self._log_view_warning(msg, node)
elif (re.match(r'^(t\-att\-|t\-attf\-)?data-tooltip(-template|-info)?$', attr)):
self._raise_view_error(_("Forbidden attribute used in arch (%s).", attr), node)
elif (attr.startswith("t-")):
self._validate_qweb_directive(node, attr, node_info["view_type"])
def _validate_classes(self, node, expr):
""" Validate the classes present on node. """
classes = set(expr.split(' '))
@@ -1924,6 +1932,32 @@ actual arch.
msg = '%s must have title in its tag, parents, descendants or have text'
self._log_view_warning(msg % description, node)
def _validate_qweb_directive(self, node, directive, view_type):
"""Some views (e.g. kanban, form) generate owl templates from the archs.
However, we don't want to see owl directives directly written in archs.
There are exceptions though, since the kanban and gantt archs define qweb templates.
We thus here validate that the given directive is allowed, according to the view_type.
"""
allowed_directives = ["t-translation"]
if self._is_qweb_based_view(view_type):
allowed_directives.extend([
"t-name",
"t-esc",
"t-out",
"t-set",
"t-value",
"t-if",
"t-else",
"t-elif",
"t-foreach",
"t-as",
"t-key",
"t-att.*",
"t-call",
])
if (not next(filter(lambda regex: re.match(regex, directive), allowed_directives), None)):
self._raise_view_error(_("Forbidden owl directive used in arch (%s).", directive), node)
def _get_domain_identifiers(self, node, domain, use, expr=None):
try:
return get_domain_identifiers(domain)
+90
View File
@@ -3279,6 +3279,96 @@ class TestViews(ViewCase):
"The view test_views_test_view_ref should not be in the views of the many2many field groups_id"
)
@mute_logger('odoo.addons.base.models.ir_ui_view')
def test_forbidden_owl_directives_in_form(self):
arch = "<form>%s</form>"
self.assertInvalid(
arch % ('<span t-esc="x"/>'),
"""Error while validating view near:
<form __validate__="1"><span t-esc="x"/></form>
Forbidden owl directive used in arch (t-esc).""",
)
self.assertInvalid(
arch % ('<span t-on-click="x.doIt()"/>'),
"""Error while validating view near:
<form __validate__="1"><span t-on-click="x.doIt()"/></form>
Forbidden owl directive used in arch (t-on-click).""",
)
@mute_logger('odoo.addons.base.models.ir_ui_view')
def test_forbidden_owl_directives_in_kanban(self):
arch = "<kanban><templates><t t-name='kanban-box'>%s</t></templates></kanban>"
self.assertValid(arch % ('<span t-esc="record.resId"/>'))
self.assertInvalid(
arch % ('<span t-on-click="x.doIt()"/>'),
"""Error while validating view near:
<kanban __validate__="1"><templates><t t-name="kanban-box"><span t-on-click="x.doIt()"/></t></templates></kanban>
Forbidden owl directive used in arch (t-on-click).""",
)
@mute_logger('odoo.addons.base.models.ir_ui_view')
def test_forbidden_data_tooltip_attributes_in_form(self):
arch = "<form>%s</form>"
self.assertInvalid(
arch % ('<span data-tooltip="Test"/>'),
"""Error while validating view near:
<form __validate__="1"><span data-tooltip="Test"/></form>
Forbidden attribute used in arch (data-tooltip)."""
)
self.assertInvalid(
arch % ('<span data-tooltip-template="test"/>'),
"""Error while validating view near:
<form __validate__="1"><span data-tooltip-template="test"/></form>
Forbidden attribute used in arch (data-tooltip-template)."""
)
@mute_logger('odoo.addons.base.models.ir_ui_view')
def test_forbidden_data_tooltip_attributes_in_kanban(self):
arch = "<kanban><templates><t t-name='kanban-box'>%s</t></templates></kanban>"
self.assertInvalid(
arch % ('<span data-tooltip="Test"/>'),
"""Error while validating view near:
<kanban __validate__="1"><templates><t t-name="kanban-box"><span data-tooltip="Test"/></t></templates></kanban>
Forbidden attribute used in arch (data-tooltip)."""
)
self.assertInvalid(
arch % ('<span data-tooltip-template="test"/>'),
"""Error while validating view near:
<kanban __validate__="1"><templates><t t-name="kanban-box"><span data-tooltip-template="test"/></t></templates></kanban>
Forbidden attribute used in arch (data-tooltip-template)."""
)
self.assertInvalid(
arch % ('<span t-att-data-tooltip="test"/>'),
"""Error while validating view near:
<kanban __validate__="1"><templates><t t-name="kanban-box"><span t-att-data-tooltip="test"/></t></templates></kanban>
Forbidden attribute used in arch (t-att-data-tooltip)."""
)
self.assertInvalid(
arch % ('<span t-attf-data-tooltip-template="{{ test }}"/>'),
"""Error while validating view near:
<kanban __validate__="1"><templates><t t-name="kanban-box"><span t-attf-data-tooltip-template="{{ test }}"/></t></templates></kanban>
Forbidden attribute used in arch (t-attf-data-tooltip-template)."""
)
class TestViewTranslations(common.TransactionCase):
# these tests are essentially the same as in test_translate.py, but they use