diff --git a/news/1634.bugfix b/news/1634.bugfix new file mode 100644 index 000000000..b0d7788b8 --- /dev/null +++ b/news/1634.bugfix @@ -0,0 +1,2 @@ +Do not expose private metadata via relationfield serializer. +[maethu] diff --git a/src/plone/restapi/serializer/relationfield.py b/src/plone/restapi/serializer/relationfield.py index 28483ce42..50b1234e7 100644 --- a/src/plone/restapi/serializer/relationfield.py +++ b/src/plone/restapi/serializer/relationfield.py @@ -1,3 +1,4 @@ +from plone import api from plone.dexterity.interfaces import IDexterityContent from plone.restapi.interfaces import IFieldSerializer from plone.restapi.interfaces import IJsonCompatible @@ -17,7 +18,12 @@ @adapter(IRelationValue) @implementer(IJsonCompatible) def relationvalue_converter(value): - if value.to_object: + has_permission = api.user.has_permission( + "Access Contents Information", + obj=value.__parent__ + ) + + if value.to_object and has_permission: request = getRequest() request.form["metadata_fields"] = ["UID"] summary = getMultiAdapter((value.to_object, request), ISerializeToJsonSummary)() @@ -33,4 +39,9 @@ class RelationChoiceFieldSerializer(DefaultFieldSerializer): @adapter(IRelationList, IDexterityContent, Interface) @implementer(IFieldSerializer) class RelationListFieldSerializer(DefaultFieldSerializer): - pass + def __call__(self): + value = self.get_value() + if value: + return [item for item in json_compatible(value) if item] + else: + return super().__call__() diff --git a/src/plone/restapi/tests/test_content_get.py b/src/plone/restapi/tests/test_content_get.py index 34cd70d13..10db402ab 100644 --- a/src/plone/restapi/tests/test_content_get.py +++ b/src/plone/restapi/tests/test_content_get.py @@ -8,6 +8,7 @@ from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING from Products.CMFCore.utils import getToolByName from z3c.relationfield import RelationValue +from z3c.relationfield.event import updateRelations from zope.component import getUtility from zope.intid.interfaces import IIntIds @@ -18,7 +19,6 @@ class TestContentGet(unittest.TestCase): - layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): @@ -137,6 +137,30 @@ def test_get_content_includes_related_items(self): response.json()["relatedItems"], ) + def test_get_content_includes_related_items_filtered_by_view_permission(self): + intids = getUtility(IIntIds) + self.portal.folder1.doc1.relatedItems = [ + RelationValue(intids.getId(self.portal.folder1.folder2.doc2)), + ] + updateRelations(self.portal.folder1.doc1, None) + # Remove view permission + self.portal.folder1.folder1.doc1.manage_permission( + "Access contents information", roles=[], acquire=False + ) + transaction.commit() + + response = requests.get( + self.portal.folder1.doc1.absolute_url(), + headers={"Accept": "application/json"}, + auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(0, len(response.json()["relatedItems"])) + self.assertEqual( + [], + response.json()["relatedItems"], + ) + def test_get_content_related_items_without_workflow(self): intids = getUtility(IIntIds)