From b5124fa366641876dd44fa526f02caa45771ca5b Mon Sep 17 00:00:00 2001 From: Mathias Leimgruber Date: Mon, 1 May 2023 13:57:31 -0400 Subject: [PATCH 1/2] Do not expose private metadata via relationfield serializer. --- news/1634.bugfix | 2 ++ src/plone/restapi/serializer/relationfield.py | 11 ++++++-- src/plone/restapi/tests/test_content_get.py | 26 ++++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 news/1634.bugfix 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..c54145f1e 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,8 @@ @adapter(IRelationValue) @implementer(IJsonCompatible) def relationvalue_converter(value): - if value.to_object: + mtool = api.portal.get_tool("portal_membership") + if value.to_object and mtool.checkPermission("View", value.to_object): request = getRequest() request.form["metadata_fields"] = ["UID"] summary = getMultiAdapter((value.to_object, request), ISerializeToJsonSummary)() @@ -33,4 +35,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..fa0e3171f 100644 --- a/src/plone/restapi/tests/test_content_get.py +++ b/src/plone/restapi/tests/test_content_get.py @@ -18,7 +18,6 @@ class TestContentGet(unittest.TestCase): - layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING def setUp(self): @@ -137,6 +136,31 @@ 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)), + ] + + # Remove view permission + self.portal.folder1.folder2.doc2.manage_permission( + "View", roles=[], acquire=False + ) + self.portal.folder1.folder2.doc2.reindexObjectSecurity() + 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) From bc3b71eacc494a124386f232f199344c2412126c Mon Sep 17 00:00:00 2001 From: Mathias Leimgruber Date: Wed, 3 May 2023 14:19:46 -0400 Subject: [PATCH 2/2] Check for Access contents information on parent object instead of view permission on the current object. --- src/plone/restapi/serializer/relationfield.py | 8 ++++++-- src/plone/restapi/tests/test_content_get.py | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/plone/restapi/serializer/relationfield.py b/src/plone/restapi/serializer/relationfield.py index c54145f1e..50b1234e7 100644 --- a/src/plone/restapi/serializer/relationfield.py +++ b/src/plone/restapi/serializer/relationfield.py @@ -18,8 +18,12 @@ @adapter(IRelationValue) @implementer(IJsonCompatible) def relationvalue_converter(value): - mtool = api.portal.get_tool("portal_membership") - if value.to_object and mtool.checkPermission("View", 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)() diff --git a/src/plone/restapi/tests/test_content_get.py b/src/plone/restapi/tests/test_content_get.py index fa0e3171f..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 @@ -141,12 +142,11 @@ def test_get_content_includes_related_items_filtered_by_view_permission(self): 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.folder2.doc2.manage_permission( - "View", roles=[], acquire=False + self.portal.folder1.folder1.doc1.manage_permission( + "Access contents information", roles=[], acquire=False ) - self.portal.folder1.folder2.doc2.reindexObjectSecurity() transaction.commit() response = requests.get(