fix(permissions): enforce contentlet-level WRITE check on Copy and Edit (#34215) - #36510
Conversation
…d-edit (#34215) Users with only View permission on a contentlet instance could still trigger "Copy and Edit" because PageResourceHelper.copyContent() lacked an instance-level permission check before calling contentletAPI.copyContentlet(). Refs: #34215 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @gortiz-dotcms's task in 12m 32s —— View job 🔍 dotCMS Backend Review[🟡 Medium]
if (!permissionAPI.doesUserHavePermission(currentContentlet, PermissionAPI.PERMISSION_WRITE, user, pageMode.respectAnonPerms)) {
throw new DotSecurityException(...);
}💡 Optional: before the [🟡 Medium]
if (!permissionAPI.doesUserHavePermission(HTMLPageAsset.class.cast(page), PermissionAPI.PERMISSION_WRITE, user, pageMode.respectAnonPerms)) {
throw new DotSecurityException(String.format("User '%s' does not have WRITE permission on Page '%s'",
user.getUserId(), page.getIdentifier()));
}💡 Extract a private helper, e.g. Next steps
|
🔍 dotCMS Backend Review[🟡 Medium]
for (final MultiTree multiTree : multiTrees) {
this.copyContentlet(new CopyContentletForm.Builder()...build(), user, pageMode, language);
// -> copyContent() -> doesUserHavePermission(currentContentlet, WRITE, ...) each time
}💡 This is a pre-existing per-item-cost loop pattern (each iteration already does Next steps
|
A user with READ-only access on a page could call _deepcopy and have the page node copied before the child-contentlet WRITE checks caused a rollback. Added an explicit WRITE permission check on the page itself in copyPage(), mirroring the check added in copyContent() for contentlet instances. Refs: #34215 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Tick the box to add this pull request to the merge queue (same as
|
|
@gortiz-dotcms is it possible to include a test here? |
…test (#34215) Replace @test(expected=...) with try/catch to ensure DotSecurityException is thrown specifically by copyContentlet() and copyPage(), not by setup code. Also assert the exception message contains the permissionable identifier. Refs: #34215 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem
When a user only has View permission on a contentlet instance, they could still use the Copy and Edit button in the Page Edit screen to create a copy of that contentlet. Additionally, a user with Read-only access on a page could call the
_deepcopyendpoint and receive a fully editable duplicate of that page — same class of bug, different object.Both code paths called
contentletAPI.copyContentlet()directly, which only enforces READ, not WRITE.Root Cause
Copy and Edit (contentlet):
PageResourceHelper.copyContent()fetched the source contentlet and immediately invokedcontentletAPI.copyContentlet()without verifying the user had WRITE permission on the specific contentlet instance. Permission was only evaluated at the content type level.Deep copy (page):
PageResourceHelper.copyPage()calledcontentletAPI.copyContentlet(page, ...)without a WRITE check on the page itself. A READ-only user could create a page copy before the child-contentlet WRITE checks caused a rollback — wrong failure mode, wrong place.Fix
Added explicit
permissionAPI.doesUserHavePermission(..., PERMISSION_WRITE, ...)checks in both code paths before any copy is attempted. Both throwDotSecurityException(→ 403) if the user lacks WRITE on the instance.Changes
dotCMS/src/main/java/com/dotcms/rest/api/v1/page/PageResourceHelper.javacopyContent()— added instance-level WRITE check before copying a contentlet (Copy and Edit)copyPage()— added WRITE check on the page before deep-copying itTest Plan
PUT /page/{pageId}/_deepcopyshould return 403./mvnw verify -pl :dotcms-integration -Dcoreit.test.skip=false -Dit.test=PageResourceTestFixes: #34215
This PR fixes: #34215