[FIX] core: invalid field should raise an Exception in domain
Since https://github.com/odoo/odoo/commit/5a998694a6f353da05d7f69e4c59b9e7dd139e27, we don't crash anymore at https://github.com/odoo/odoo/commit/5a998694a6f353da05d7f69e4c59b9e7dd139e27#diff-fa4d9268d6e65e19aebec81c46038f0e496b91142588ed4d1c1bce7ff2338f2cL759 (at `field.auto_join`) if `len(path) > 1`, `field` is translated the `left` is not only a field name (example: `"name.<something else>"`). Because we trust `left` at this [point](https://github.com/odoo/odoo/blob/1bbdd77f0ee6bd632f5ade88b8b71c5576fa9053/odoo/osv/expression.py#L1339), (we shouldn't, coming from https://github.com/odoo/odoo/pull/101115) then SQL expression generated for translated field is unsafe (SQL injection). In case of translated field, check that `left` side is only a valid field name. (if it is not, it will crash later in `__leaf_to_sql`). In addition, use `field.name` instead of `left` when it is possible to be more robust. closes odoo/odoo#120565 Signed-off-by: Vincent Schippefilt (vsc) <vsc@odoo.com>
This commit is contained in:
@@ -777,6 +777,12 @@ class TestExpression(SavepointCaseWithUserDemo):
|
||||
with self.assertRaisesRegex(ValueError, r"^Invalid field res\.country\.does_not_exist in leaf \('does_not_exist', '=', 'foo'\)$"):
|
||||
Country.search([('does_not_exist', '=', 'foo')])
|
||||
|
||||
with self.assertRaisesRegex(AssertionError, "^Invalid field 'name.\"Et plouf\"'"):
|
||||
Country.search([('name."Et plouf"', 'ilike', 'foo')])
|
||||
|
||||
with self.assertRaisesRegex(AssertionError, "^Invalid field 'name.\"Et plouf\"'"):
|
||||
Country.search([('name."Et plouf"', 'in', ['foo'])])
|
||||
|
||||
with self.assertRaisesRegex(KeyError, r"^'does_not_exist'$"):
|
||||
Country.search([]).filtered_domain([('does_not_exist', '=', 'foo')])
|
||||
|
||||
|
||||
+12
-13
@@ -119,13 +119,12 @@ import json
|
||||
import logging
|
||||
import reprlib
|
||||
import traceback
|
||||
import warnings
|
||||
from datetime import date, datetime, time
|
||||
|
||||
from psycopg2.sql import Composable, SQL
|
||||
|
||||
import odoo.modules
|
||||
from ..models import BaseModel, check_property_field_value_name
|
||||
from odoo.models import BaseModel, check_property_field_value_name
|
||||
from odoo.tools import pycompat, Query, _generate_table_alias, sql
|
||||
|
||||
|
||||
@@ -1072,7 +1071,7 @@ class expression(object):
|
||||
elif operator in ('any', 'not any') and field.store and field.type == 'many2one' and field.auto_join:
|
||||
# res_partner.state_id = res_partner__state_id.id
|
||||
coalias = self.query.left_join(
|
||||
alias, left, comodel._table, 'id', left,
|
||||
alias, field.name, comodel._table, 'id', field.name,
|
||||
)
|
||||
|
||||
if operator == 'not any':
|
||||
@@ -1336,7 +1335,7 @@ class expression(object):
|
||||
expr, params = self.__leaf_to_sql(leaf, model, alias)
|
||||
push_result(expr, params)
|
||||
|
||||
elif field.translate and isinstance(right, str):
|
||||
elif field.translate and isinstance(right, str) and left == field.name:
|
||||
sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(operator, operator)
|
||||
expr = ''
|
||||
params = []
|
||||
@@ -1346,7 +1345,7 @@ class expression(object):
|
||||
right = field.convert_to_column(right, model, validate=False).adapted['en_US']
|
||||
|
||||
if (need_wildcard and not right) or (right and sql_operator in NEGATIVE_TERM_OPERATORS):
|
||||
expr += f'"{alias}"."{left}" is NULL OR '
|
||||
expr += f'"{alias}"."{field.name}" is NULL OR '
|
||||
|
||||
if self._has_trigram and field.index == 'trigram' and sql_operator in ('=', 'like', 'ilike'):
|
||||
# a prefilter using trigram index to speed up '=', 'like', 'ilike'
|
||||
@@ -1358,7 +1357,7 @@ class expression(object):
|
||||
|
||||
if _right != '%':
|
||||
_unaccent = self._unaccent(field)
|
||||
_left = _unaccent(f'''jsonb_path_query_array("{alias}"."{left}", '$.*')::text''')
|
||||
_left = _unaccent(f'''jsonb_path_query_array("{alias}"."{field.name}", '$.*')::text''')
|
||||
_sql_operator = 'like' if sql_operator == '=' else sql_operator
|
||||
expr += f"{_left} {_sql_operator} {_unaccent('%s')} AND "
|
||||
params.append(_right)
|
||||
@@ -1366,9 +1365,9 @@ class expression(object):
|
||||
unaccent = self._unaccent(field) if sql_operator.endswith('like') else lambda x: x
|
||||
lang = model.env.lang or 'en_US'
|
||||
if lang == 'en_US':
|
||||
left = unaccent(f""""{alias}"."{left}"->>'en_US'""")
|
||||
left = unaccent(f""""{alias}"."{field.name}"->>'en_US'""")
|
||||
else:
|
||||
left = unaccent(f'''COALESCE("{alias}"."{left}"->>'{lang}', "{alias}"."{left}"->>'en_US')''')
|
||||
left = unaccent(f'''COALESCE("{alias}"."{field.name}"->>'{lang}', "{alias}"."{field.name}"->>'en_US')''')
|
||||
|
||||
if need_wildcard:
|
||||
right = f'%{right}%'
|
||||
@@ -1377,24 +1376,24 @@ class expression(object):
|
||||
params.append(right)
|
||||
push_result(f'({expr})', params)
|
||||
|
||||
elif field.translate and operator in ['in', 'not in'] and isinstance(right, (list, tuple)):
|
||||
elif field.translate and operator in ['in', 'not in'] and isinstance(right, (list, tuple)) and left == field.name:
|
||||
params = [it for it in right if it is not False and it is not None]
|
||||
check_null = len(params) < len(right)
|
||||
if params:
|
||||
params = [field.convert_to_column(p, model, validate=False).adapted['en_US'] for p in params]
|
||||
lang = model.env.lang or 'en_US'
|
||||
if lang == 'en_US':
|
||||
query = f'''("{alias}"."{left}"->>'en_US' {operator} %s)'''
|
||||
query = f'''("{alias}"."{field.name}"->>'en_US' {operator} %s)'''
|
||||
else:
|
||||
query = f'''(COALESCE("{alias}"."{left}"->>'{lang}', "{alias}"."{left}"->>'en_US') {operator} %s)'''
|
||||
query = f'''(COALESCE("{alias}"."{field.name}"->>'{lang}', "{alias}"."{field.name}"->>'en_US') {operator} %s)'''
|
||||
params = [tuple(params)]
|
||||
else:
|
||||
# The case for (left, 'in', []) or (left, 'not in', []).
|
||||
query = 'FALSE' if operator == 'in' else 'TRUE'
|
||||
if (operator == 'in' and check_null) or (operator == 'not in' and not check_null):
|
||||
query = '(%s OR %s."%s" IS NULL)' % (query, alias, left)
|
||||
query = f'({query} OR "{alias}"."{field.name}" IS NULL)'
|
||||
elif operator == 'not in' and check_null:
|
||||
query = '(%s AND %s."%s" IS NOT NULL)' % (query, alias, left) # needed only for TRUE.
|
||||
query = f'({query} AND "{alias}"."{field.name}" IS NOT NULL)' # needed only for TRUE.
|
||||
push_result(query, params)
|
||||
else:
|
||||
expr, params = self.__leaf_to_sql(leaf, model, alias)
|
||||
|
||||
Reference in New Issue
Block a user