Skip to content

Commit 88f2631

Browse files
WaVEVtimgraham
authored andcommitted
INTPYTHON-827 Prevent Value strings from being interpreted as expressions
1 parent e180cbe commit 88f2631

File tree

5 files changed

+37
-5
lines changed

5 files changed

+37
-5
lines changed

django_mongodb_backend/expressions/builtins.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ def when(self, compiler, connection):
211211

212212
def value(self, compiler, connection, as_expr=False): # noqa: ARG001
213213
value = self.value
214-
if isinstance(value, (list, int)) and as_expr:
215-
# Wrap lists & numbers in $literal to prevent ambiguity when Value
216-
# appears in $project.
214+
if isinstance(value, (list, int, str)) and as_expr:
215+
# Wrap lists, numbers, and strings in $literal to avoid ambiguity when
216+
# Value is used in aggregate() or update_many()'s $set.
217217
return {"$literal": value}
218218
if isinstance(value, Decimal):
219219
return Decimal128(value)

docs/releases/5.2.x.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Bug fixes
1717

1818
- Prevented ``QuerySet.union()`` queries from duplicating the ``$project``
1919
pipeline.
20+
- Made :class:`~django.db.models.Value` wrap strings in ``$literal`` to
21+
prevent dollar-prefixed strings from being interpreted as expressions.
2022

2123
Performance improvements
2224
------------------------

tests/basic_/test_escaping.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Literals that MongoDB intreprets as expressions are escaped."""
22

3+
from operator import attrgetter
4+
5+
from django.db.models import Value
36
from django.test import TestCase
47

58
from django_mongodb_backend.test import MongoTestCaseMixin
@@ -18,3 +21,17 @@ def test_dollar_prefixed_string(self):
1821
self.assertInsertQuery(
1922
ctx.captured_queries[0]["sql"], "basic__author", [{"name": "$foobar"}]
2023
)
24+
25+
26+
class AnnotationTests(MongoTestCaseMixin, TestCase):
27+
def test_dollar_prefixed_value(self):
28+
"""Value() escapes dollar prefixed strings."""
29+
Author.objects.create(name="Gustavo")
30+
with self.assertNumQueries(1) as ctx:
31+
qs = list(Author.objects.annotate(a_value=Value("$name")))
32+
self.assertQuerySetEqual(qs, ["$name"], attrgetter("a_value"))
33+
self.assertAggregateQuery(
34+
ctx.captured_queries[0]["sql"],
35+
"basic__author",
36+
[{"$project": {"a_value": {"$literal": "$name"}, "_id": 1, "name": 1}}],
37+
)

tests/expressions_/test_value.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def test_int(self):
4141
def test_str(self):
4242
self.assertEqual(Value("foo").as_mql(None, None), "foo")
4343

44+
def test_str_expr(self):
45+
self.assertEqual(Value("$foo").as_mql(None, None, as_expr=True), {"$literal": "$foo"})
46+
4447
def test_uuid(self):
4548
value = uuid.UUID(int=1)
4649
self.assertEqual(Value(value).as_mql(None, None), "00000000000000000000000000000001")

tests/model_fields_/test_embedded_model_array.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,18 @@ def test_nested_array_index_expr(self):
327327
},
328328
{
329329
"$concat": [
330-
{"$ifNull": ["Z", ""]},
331-
{"$ifNull": ["acarias", ""]},
330+
{
331+
"$ifNull": [
332+
{"$literal": "Z"},
333+
{"$literal": ""},
334+
]
335+
},
336+
{
337+
"$ifNull": [
338+
{"$literal": "acarias"},
339+
{"$literal": ""},
340+
]
341+
},
332342
]
333343
},
334344
]

0 commit comments

Comments
 (0)