diff --git a/news/1847.feature b/news/1847.feature
new file mode 100644
index 0000000000..c2f91e123e
--- /dev/null
+++ b/news/1847.feature
@@ -0,0 +1 @@
+When a Link content item is linked by UID, resolve its URL as the linked target URL for anonymous users. @cekk
diff --git a/src/plone/restapi/serializer/configure.zcml b/src/plone/restapi/serializer/configure.zcml
index 0e84f64f42..32c63b2d78 100644
--- a/src/plone/restapi/serializer/configure.zcml
+++ b/src/plone/restapi/serializer/configure.zcml
@@ -8,6 +8,8 @@
+
+
diff --git a/src/plone/restapi/serializer/dxcontent.py b/src/plone/restapi/serializer/dxcontent.py
index 1c546d091d..e497bb6b68 100644
--- a/src/plone/restapi/serializer/dxcontent.py
+++ b/src/plone/restapi/serializer/dxcontent.py
@@ -1,6 +1,7 @@
from AccessControl import getSecurityManager
from Acquisition import aq_inner
from Acquisition import aq_parent
+from plone.app.contenttypes.interfaces import ILink
from plone.autoform.interfaces import READ_PERMISSIONS_KEY
from plone.dexterity.interfaces import IDexterityContainer
from plone.dexterity.interfaces import IDexterityContent
@@ -266,3 +267,27 @@ def check_permission(self, permission_name, obj):
sm.checkPermission(permission.title, obj)
)
return self.permission_cache[permission_name]
+
+
+@adapter(ILink, Interface)
+@implementer(IObjectPrimaryFieldTarget)
+class LinkObjectPrimaryFieldTarget:
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ self.permission_cache = {}
+
+ def __call__(self):
+ """
+ If user can edit Link object, do not return remoteUrl
+ """
+ pm = getToolByName(self.context, "portal_membership")
+ if bool(pm.isAnonymousUser()):
+ for schema in iterSchemata(self.context):
+ for name, field in getFields(schema).items():
+ if name == "remoteUrl":
+ serializer = queryMultiAdapter(
+ (field, self.context, self.request), IFieldSerializer
+ )
+ return serializer()
diff --git a/src/plone/restapi/tests/test_dxcontent_serializer.py b/src/plone/restapi/tests/test_dxcontent_serializer.py
index 567aa01e62..1989181472 100644
--- a/src/plone/restapi/tests/test_dxcontent_serializer.py
+++ b/src/plone/restapi/tests/test_dxcontent_serializer.py
@@ -16,11 +16,13 @@
from plone.namedfile.file import NamedFile
from plone.registry.interfaces import IRegistry
from plone.restapi.interfaces import IExpandableElement
+from plone.restapi.interfaces import IObjectPrimaryFieldTarget
from plone.restapi.interfaces import ISerializeToJson
from plone.restapi.testing import PLONE_RESTAPI_DX_INTEGRATION_TESTING
from plone.restapi.tests.test_expansion import ExpandableElementFoo
from plone.restapi.serializer.utils import get_portal_type_title
from plone.uuid.interfaces import IMutableUUID
+from plone.uuid.interfaces import IUUID
from Products.CMFCore.utils import getToolByName
from zope.component import getGlobalSiteManager
from zope.component import getMultiAdapter
@@ -756,3 +758,38 @@ def test_primary_field_target_with_edit_permissions(self):
serializer = getMultiAdapter((self.portal.doc1, self.request), ISerializeToJson)
data = serializer()
self.assertNotIn("targetUrl", data)
+
+ def test_primary_field_target_for_link_objects_for_auth_return_none(self):
+ self.portal.invokeFactory(
+ "Document",
+ id="linked",
+ )
+ self.portal.invokeFactory(
+ "Link",
+ id="link",
+ remoteUrl=f"../resolveuid/{IUUID(self.portal.linked)}",
+ )
+ wftool = getToolByName(self.portal, "portal_workflow")
+ wftool.doActionFor(self.portal.linked, "publish")
+ adapter = getMultiAdapter(
+ (self.portal.link, self.request), IObjectPrimaryFieldTarget
+ )
+ self.assertEqual(adapter(), None)
+
+ def test_primary_field_target_for_link_objects_for_anonymous(self):
+ self.portal.invokeFactory(
+ "Document",
+ id="linked",
+ )
+ self.portal.invokeFactory(
+ "Link",
+ id="link",
+ remoteUrl=f"../resolveuid/{IUUID(self.portal.linked)}",
+ )
+ wftool = getToolByName(self.portal, "portal_workflow")
+ wftool.doActionFor(self.portal.linked, "publish")
+ logout()
+ adapter = getMultiAdapter(
+ (self.portal.link, self.request), IObjectPrimaryFieldTarget
+ )
+ self.assertEqual(adapter(), self.portal.linked.absolute_url())