Skip to content

Commit 660ece3

Browse files
Prevent contains to be executed on arbitrary fields (#3563)
* Prevent contains to be executed on arbitrary fields * Fixing browser test and updating browser while we're here --------- Co-authored-by: Alex Cottner <acottner@mozilla.com>
1 parent fa18bdc commit 660ece3

File tree

5 files changed

+30
-0
lines changed

5 files changed

+30
-0
lines changed

kinto/core/resource/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,14 @@ def is_valid_timestamp(value):
11351135
if field == self.model.modified_field and not is_valid_timestamp(value):
11361136
raise_invalid(self.request, **error_details)
11371137

1138+
if field in (self.model.modified_field, self.model.id_field) and operator in (
1139+
COMPARISON.CONTAINS,
1140+
COMPARISON.CONTAINS_ANY,
1141+
):
1142+
error_msg = f"Field '{field}' is not an array"
1143+
error_details["description"] = error_msg
1144+
raise_invalid(self.request, **error_details)
1145+
11381146
filters.append(Filter(field, value, operator))
11391147

11401148
# If a plural endpoint is reached, and if the user does not have the

kinto/core/storage/postgresql/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,14 @@ def _format_conditions(self, filters, id_field, modified_field, prefix="filters"
884884
operator = "IS NOT NULL" if filtr.value else "IS NULL"
885885
cond = f"{sql_field} {operator}"
886886

887+
elif filtr.operator == COMPARISON.CONTAINS:
888+
value_holder = f"{prefix}_value_{i}"
889+
holders[value_holder] = value
890+
# In case the field is not a sequence, we ignore the object.
891+
is_json_sequence = f"jsonb_typeof({sql_field}) = 'array'"
892+
sql_operator = operators[filtr.operator]
893+
cond = f"{is_json_sequence} AND {sql_field} {sql_operator} :{value_holder}"
894+
887895
elif filtr.operator == COMPARISON.CONTAINS_ANY:
888896
value_holder = f"{prefix}_value_{i}"
889897
holders[value_holder] = value

kinto/core/storage/testing.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,14 @@ def test_list_all_can_filter_on_array_with_contains_and_unsupported_type(self):
491491
objects = self.storage.list_all(filters=filters, **self.storage_kw)
492492
self.assertEqual(len(objects), 0)
493493

494+
def test_list_all_contains_ignores_object_if_field_is_not_array(self):
495+
self.create_object({"code": "black"})
496+
self.create_object({"fib": ["a", "b", "c"]})
497+
498+
filters = [Filter("fib", ["a"], utils.COMPARISON.CONTAINS)]
499+
objects = self.storage.list_all(filters=filters, **self.storage_kw)
500+
self.assertEqual(len(objects), 1)
501+
494502
def test_list_all_can_filter_on_array_with_contains_any_and_unsupported_type(self):
495503
self.create_object({"code": "black"})
496504
self.create_object({"fib": [2, 3, 5]})

tests/browser.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ port = %(http_port)s
77
[app:main]
88
use = egg:kinto
99

10+
kinto.project_name = Kinto
11+
1012
#
1113
# Backends.
1214
#

tests/core/resource/test_filter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,10 @@ def test_contains_any_fails_on_a_non_sequence_object_value(self):
339339
values = result["data"]
340340
assert len(values) == 0
341341

342+
def test_contains_returns_400_if_field_is_id_or_last_modified(self):
343+
self.validated["querystring"] = {"contains_id": "foo"}
344+
self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.plural_get)
345+
342346

343347
class SubobjectFilteringTest(BaseTest):
344348
def setUp(self):

0 commit comments

Comments
 (0)