Skip to content

Commit 2cab79b

Browse files
Merge commit from fork
* Security: in summary serializer only allow access to metadata that is available in the catalog. * add RESTAPI_EXTRA_NON_METADATA_ATTRIBUTES environment variable, a comma-separated list of extra attribute names appended to the summary serializer `non_metadata_attributes` * add debug logging * remove python-version added by mistake * fix --------- Co-authored-by: Mauro Amico <mauro.amico@gmail.com>
1 parent 5677f2e commit 2cab79b

4 files changed

Lines changed: 49 additions & 7 deletions

File tree

news/+6263b817.bugfix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security: in summary serializer only allow access to metadata that is available in the catalog.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for the `RESTAPI_ADDITIONAL_NON_METADATA_ATTRIBUTES` environment variable, a comma-separated list of extra attribute names appended to the summary serializer's `non_metadata_attributes`.

src/plone/restapi/serializer/summary.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212
from zope.interface import implementer
1313
from zope.interface import Interface
1414

15+
import logging
16+
import os
17+
18+
logger = logging.getLogger(__name__)
19+
20+
ADDITIONAL_NON_METADATA_ATTRIBUTES = {
21+
attr.strip()
22+
for attr in os.environ.get("RESTAPI_ADDITIONAL_NON_METADATA_ATTRIBUTES", "").split(
23+
","
24+
)
25+
if attr.strip()
26+
}
27+
1528

1629
@implementer(IJSONSummarySerializerMetadata)
1730
class JSONSummarySerializerMetadata:
@@ -30,7 +43,7 @@ def non_metadata_attributes(self):
3043
return {
3144
"getPath",
3245
"getURL",
33-
}
46+
} | ADDITIONAL_NON_METADATA_ATTRIBUTES
3447

3548
def blocklisted_attributes(self):
3649
return {
@@ -112,14 +125,26 @@ def metadata_fields(self):
112125
if not isinstance(additional_metadata_fields, list):
113126
additional_metadata_fields = [additional_metadata_fields]
114127
additional_metadata_fields = set(additional_metadata_fields)
115-
128+
if not additional_metadata_fields:
129+
return self.default_metadata_fields
130+
131+
fields_cache = self.request.get("_summary_fields_cache", None)
132+
if fields_cache is None:
133+
catalog = getToolByName(self.context, "portal_catalog")
134+
fields_cache = set(catalog.schema()) | self.non_metadata_attributes
135+
self.request.set("_summary_fields_cache", fields_cache)
116136
if "_all" in additional_metadata_fields:
117-
fields_cache = self.request.get("_summary_fields_cache", None)
118-
if fields_cache is None:
119-
catalog = getToolByName(self.context, "portal_catalog")
120-
fields_cache = set(catalog.schema()) | self.non_metadata_attributes
121-
self.request.set("_summary_fields_cache", fields_cache)
122137
additional_metadata_fields = fields_cache
138+
else:
139+
rejected_metadata_fields = additional_metadata_fields - fields_cache
140+
if rejected_metadata_fields:
141+
logger.debug(
142+
"Rejected metadata_fields %s requested for %s: not available "
143+
"in the catalog schema or non_metadata_attributes.",
144+
sorted(rejected_metadata_fields),
145+
self.context,
146+
)
147+
additional_metadata_fields = additional_metadata_fields & fields_cache
123148

124149
return self.default_metadata_fields | additional_metadata_fields
125150

src/plone/restapi/tests/test_serializer_summary.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from plone.event.interfaces import IEvent
1010
from plone.event.interfaces import IEventRecurrence
1111
from plone.restapi.interfaces import ISerializeToJsonSummary
12+
from plone.restapi.serializer.summary import JSONSummarySerializerMetadata
1213
from plone.restapi.testing import PLONE_RESTAPI_DX_INTEGRATION_TESTING
1314
from plone.restapi.testing import register_static_uuid_utility
1415
from Products.CMFCore.utils import getToolByName
@@ -19,6 +20,7 @@
1920
import Missing
2021
import pytz
2122
import unittest
23+
import unittest.mock as mock
2224

2325
try:
2426
from plone.app.event.adapters import OccurrenceContentListingObject
@@ -201,6 +203,19 @@ def test_brain_summary_includes_all_metadata_fields(self):
201203
summary.items(),
202204
)
203205

206+
def test_extra_non_metadata_attributes_env_var(self):
207+
with mock.patch(
208+
"plone.restapi.serializer.summary.ADDITIONAL_NON_METADATA_ATTRIBUTES",
209+
{"customAttribute", "anotherCustomAttribute"},
210+
):
211+
non_metadata_attributes = (
212+
JSONSummarySerializerMetadata().non_metadata_attributes()
213+
)
214+
self.assertIn("customAttribute", non_metadata_attributes)
215+
self.assertIn("anotherCustomAttribute", non_metadata_attributes)
216+
self.assertIn("getPath", non_metadata_attributes)
217+
self.assertIn("getURL", non_metadata_attributes)
218+
204219
def test_dx_type_summary(self):
205220
summary = getMultiAdapter((self.doc1, self.request), ISerializeToJsonSummary)()
206221

0 commit comments

Comments
 (0)