diff --git a/Products/CMFPlone/browser/configure.zcml b/Products/CMFPlone/browser/configure.zcml
index 8dbe1e4320..8ddf4fdc82 100644
--- a/Products/CMFPlone/browser/configure.zcml
+++ b/Products/CMFPlone/browser/configure.zcml
@@ -316,14 +316,14 @@
name="recyclebin"
for="plone.base.interfaces.siteroot.IPloneSiteRoot"
class=".recyclebin.RecycleBinView"
- permission="cmf.ManagePortal"
+ permission="Products.CMFPlone.ManageRecycleBin"
/>
diff --git a/Products/CMFPlone/browser/recyclebin.py b/Products/CMFPlone/browser/recyclebin.py
index f702ddb861..c7c3a260bf 100644
--- a/Products/CMFPlone/browser/recyclebin.py
+++ b/Products/CMFPlone/browser/recyclebin.py
@@ -318,6 +318,10 @@ def get_items(self):
filter_type = self.get_filter_type()
search_query = self.get_search_query().lower()
+ # Check if the user has full access to the recycle bin
+ # (includes permissions check and Manager/Site Administrator roles)
+ has_full_access = self.recycle_bin.check_permission(check_roles=True)
+
# Create a list of all items that are children of a parent in the recycle bin
child_items_to_exclude = []
for item in items:
@@ -334,6 +338,11 @@ def get_items(self):
for item in items:
if item.get("id") not in child_items_to_exclude:
+ # If user doesn't have full access, show only items they can restore
+ if not has_full_access:
+ if not self.recycle_bin.check_permission():
+ continue
+
# Apply type filtering
if filter_type and item.get("type") != filter_type:
continue
@@ -411,6 +420,19 @@ def update(self):
)
return
+ # Check if the user has permission to access this item
+ if not self.recycle_bin.check_permission():
+ logger.debug(f"User does not have permission to view item {self.item_id}")
+ message = translate(
+ _("You don't have permission to access this item in the recycle bin."),
+ context=self.request,
+ )
+ IStatusMessage(self.request).addStatusMessage(message, type="error")
+ self.request.response.redirect(
+ f"{self.context.absolute_url()}/@@recyclebin"
+ )
+ return
+
# Handle restoration of children
if "restore.child" in self.request.form:
self._handle_child_restoration()
diff --git a/Products/CMFPlone/controlpanel/browser/configure.zcml b/Products/CMFPlone/controlpanel/browser/configure.zcml
index 1a7dc5a9c8..ccbc3d46b3 100644
--- a/Products/CMFPlone/controlpanel/browser/configure.zcml
+++ b/Products/CMFPlone/controlpanel/browser/configure.zcml
@@ -354,7 +354,7 @@
name="plone-recyclebin"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class="Products.CMFPlone.controlpanel.browser.recyclebin.RecyclebinControlPanelView"
- permission="cmf.ManagePortal"
+ permission="Products.CMFPlone.ManageRecycleBin"
/>
diff --git a/Products/CMFPlone/controlpanel/permissions.zcml b/Products/CMFPlone/controlpanel/permissions.zcml
index 7f57dd164f..f8786d6bf3 100644
--- a/Products/CMFPlone/controlpanel/permissions.zcml
+++ b/Products/CMFPlone/controlpanel/permissions.zcml
@@ -89,4 +89,9 @@
title="Manage Context Aliases"
/>
+
+
diff --git a/Products/CMFPlone/profiles/default/actions.xml b/Products/CMFPlone/profiles/default/actions.xml
index c857d46f0c..aded4c340a 100644
--- a/Products/CMFPlone/profiles/default/actions.xml
+++ b/Products/CMFPlone/profiles/default/actions.xml
@@ -508,7 +508,7 @@
string:plone-delete
portal/@@recyclebin-enabled|nothing
-
+
True
diff --git a/Products/CMFPlone/profiles/default/controlpanel.xml b/Products/CMFPlone/profiles/default/controlpanel.xml
index b137266cdf..07e994cadc 100644
--- a/Products/CMFPlone/profiles/default/controlpanel.xml
+++ b/Products/CMFPlone/profiles/default/controlpanel.xml
@@ -362,6 +362,6 @@
url_expr="string:${portal_url}/@@plone-recyclebin"
visible="True"
>
- Manage portal
+ Manage recycle bin
diff --git a/Products/CMFPlone/profiles/default/rolemap.xml b/Products/CMFPlone/profiles/default/rolemap.xml
index 1336b14e06..8571ceb103 100644
--- a/Products/CMFPlone/profiles/default/rolemap.xml
+++ b/Products/CMFPlone/profiles/default/rolemap.xml
@@ -378,5 +378,11 @@
+
+
+
+
diff --git a/Products/CMFPlone/recyclebin.py b/Products/CMFPlone/recyclebin.py
index 90d81863d0..79b938dbda 100644
--- a/Products/CMFPlone/recyclebin.py
+++ b/Products/CMFPlone/recyclebin.py
@@ -125,6 +125,9 @@ def get_items_sorted_by_date(self, reverse=True):
class RecycleBin:
"""Stores deleted content items"""
+ # Permission for managing recycle bin
+ MANAGE_RECYCLEBIN = "Manage recycle bin"
+
def __init__(self):
"""Initialize the recycle bin utility
@@ -166,6 +169,15 @@ def is_enabled(self):
except (KeyError, AttributeError):
return False
+ def check_permission(self):
+ """Check if the current user has permission to manage the recycle bin
+
+ Returns:
+ Boolean indicating permission
+ """
+ context = self._get_context()
+ return getSecurityManager().checkPermission(self.MANAGE_RECYCLEBIN, context)
+
def _get_item_title(self, obj, item_type=None):
"""Helper method to get a meaningful title for an item"""
if hasattr(obj, "objectIds") or item_type == "Collection":
diff --git a/Products/CMFPlone/tests/test_recyclebin.py b/Products/CMFPlone/tests/test_recyclebin.py
index bc85796c1a..f9b6b8bf2f 100644
--- a/Products/CMFPlone/tests/test_recyclebin.py
+++ b/Products/CMFPlone/tests/test_recyclebin.py
@@ -89,6 +89,19 @@ def test_recyclebin_settings(self):
self.assertEqual(settings.retention_period, 30)
self.assertEqual(settings.maximum_size, 100)
+ def test_recyclebin_permission(self):
+ """Test permission checks for the recycle bin"""
+ # As Manager role, should have access
+ self.assertTrue(self.recyclebin.check_permission())
+
+ self.portal.acl_users._doAddUser("testuser", "password", ["Member"], [])
+
+ # Log in as the test user using plone.app.testing login function
+ login(self.portal, "testuser")
+
+ # Check permission - should be False for a regular member
+ self.assertFalse(self.recyclebin.check_permission())
+
class RecycleBinContentTests(RecycleBinTestCase):
"""Tests for deleting and restoring basic content items"""