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"""