Skip to content

Commit bf16eea

Browse files
committed
Backport pr 2060 to 9.x.x branch
1 parent 3174a72 commit bf16eea

6 files changed

Lines changed: 49 additions & 4 deletions

File tree

‎CONTRIBUTORS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@ this file, one to a line, like so:
4444
- Jon Pentland
4545
- Leonardo J. Caballero G.
4646
- Steve Piercy
47+
- Marcel Liebischer

‎news/2060.bugfix‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix two cases where REST API search and summary serialization could return HTTP 500 (`TypeError`) based on request parameters: a form or query string parameter named `summary_serializer_metadata`, and a `portal_type` value containing the substring "query" when `use_site_search_settings` is used. Also make the summary serializer's per-request metadata cache effective; it was recomputed for every item. @mliebischer

‎src/plone/restapi/search/handler.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def filter_query(self, query):
116116
search_settings = registry.forInterface(ISearchSchema, prefix="plone")
117117

118118
types = query.get("portal_type", [])
119-
if "query" in types:
119+
if isinstance(types, dict) and "query" in types:
120120
types = types["query"]
121121
query["portal_type"] = self.filter_types(types)
122122

‎src/plone/restapi/serializer/summary.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ class DefaultJSONSummarySerializer:
8585
def __init__(self, context, request):
8686
self.context = context
8787
self.request = request
88-
# Cache summary_serializer_metadata on request
89-
metadata = self.request.form.get("summary_serializer_metadata", None)
90-
if not metadata:
88+
# Cache on the request: read via request.get() so the value stored by
89+
# request.set() is reused; isinstance() ignores a same-named parameter.
90+
metadata = self.request.get("summary_serializer_metadata", None)
91+
if not isinstance(metadata, dict):
9192
metadata = merge_serializer_metadata_utilities_data()
9293
self.request.set("summary_serializer_metadata", metadata)
9394

‎src/plone/restapi/tests/test_search.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,17 @@ def test_respect_access_inactive_permission(self):
718718
).json()
719719
self.assertEqual(response["items_total"], 1)
720720

721+
def test_search_use_site_search_settings_with_portal_type_containing_query(self):
722+
# A portal_type string that happens to contain the substring "query"
723+
# must not be mistaken for a {"query": ...} dict, which raised a
724+
# TypeError (HTTP 500) in filter_query.
725+
response = self.api_session.get(
726+
"/@search",
727+
params={"use_site_search_settings": 1, "portal_type": "Subquery"},
728+
)
729+
self.assertEqual(response.status_code, 200)
730+
self.assertEqual(response.json()["items_total"], 0)
731+
721732
def test_search_use_site_search_settings_for_types(self):
722733
response = self.api_session.get(
723734
"/@search", params={"use_site_search_settings": 1}

‎src/plone/restapi/tests/test_serializer_summary.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import Missing
2020
import pytz
2121
import unittest
22+
from unittest import mock
2223

2324

2425
try:
@@ -202,6 +203,36 @@ def test_brain_summary_includes_all_metadata_fields(self):
202203
summary.items(),
203204
)
204205

206+
def test_request_metadata_cache_is_reused(self):
207+
# The per-request metadata cache must take effect, so the metadata is
208+
# only computed once even when many items are serialized in the same
209+
# request (e.g. a search result or folder listing with many items).
210+
from plone.restapi.serializer import summary as summary_module
211+
212+
self.request.other.pop("summary_serializer_metadata", None)
213+
214+
with mock.patch.object(
215+
summary_module,
216+
"merge_serializer_metadata_utilities_data",
217+
wraps=summary_module.merge_serializer_metadata_utilities_data,
218+
) as merge_mock:
219+
getMultiAdapter((self.doc1, self.request), ISerializeToJsonSummary)()
220+
getMultiAdapter((self.doc1, self.request), ISerializeToJsonSummary)()
221+
222+
self.assertEqual(merge_mock.call_count, 1)
223+
224+
def test_request_parameter_does_not_break_serialization(self):
225+
self.request.other.pop("summary_serializer_metadata", None)
226+
self.request.form["summary_serializer_metadata"] = "injected"
227+
try:
228+
summary = getMultiAdapter(
229+
(self.doc1, self.request), ISerializeToJsonSummary
230+
)()
231+
finally:
232+
del self.request.form["summary_serializer_metadata"]
233+
234+
self.assertEqual(summary["@type"], "DXTestDocument")
235+
205236
def test_dx_type_summary(self):
206237
summary = getMultiAdapter((self.doc1, self.request), ISerializeToJsonSummary)()
207238

0 commit comments

Comments
 (0)