Repository navigation
Conversation
saveRolePermission had no admin check at all — only the portlet-access gate. Added getAdminUser() to enforce the same admin requirement used by addNewRole/updateRole/deleteRole/lockRole/unlockRole. saveRoleLayouts already called getAdminUser() but after two DB reads. Moved the call to immediately after the portlet check so the admin gate fires before any data access, consistent with the pattern in updateLayout and deleteLayout. updateLayout and deleteLayout were already correctly ordered — no change. Closes: dotCMS/private-issues#643 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @mbiuki's task in 1m 44s —— View job SDK Compatibility Check
Scope of the diff: all four changed files are server-side Java/integration-test code — None of it touches an SDK-reachable surface:
The only runtime-reachable surface here is the legacy DWR endpoint No PR comment or |
🤖 dotBot Review (Bedrock)Reviewed 5 file(s); 3 candidate(s) → 2 confirmed, 0 uncertain (unverified, kept for review). Confirmed findings
us.deepseek.r1-v1:0 · Run: #28672054880 · tokens: in: 21925 · out: 6849 · total: 28774 · calls: 10 · est. ~$0.067 |
Versions AffectedExploitable range: v21.02 → v26.06.22-03 (current latest)
No patched release has been cut yet — this PR is part of the fix. |
…#642)
Any authenticated backend user could call PUT /api/v1/toolgroups/{id}/_addtouser
to self-assign arbitrary layouts, including the admin Settings layout containing
the roles/users portlets. Combined with the pre-existing DWR addUserToRole check
(getAdminUser()), this broke the full privilege-escalation + RCE chain.
Adds .requiredRoles(Role.CMS_ADMINISTRATOR_ROLE) to the _addtouser InitBuilder so
non-admin callers receive a 403/SecurityException before any layout operation runs.
Also adds regression tests:
- ToolGroupResourceTest: verifies 403 for non-admin, success for admin
- RoleAjaxSecurityTest: verifies addUserToRole blocks a non-admin who already holds
roles-portlet access (post-step-1 state), confirming independent defense-in-depth
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CI Re-run Results ✅All checks passed on the re-run (run #28331839580). The previous failures (QuartzUtils DB deadlock, StoryBlockMap NPE, E2E/Postman timeouts) were confirmed as pre-existing flaky infrastructure issues unrelated to this PR's changes. This PR is clear to merge.
|
|
@mbiuki @sfreudenthaler we should do a pass over all the DWR endpoints to make sure there are no other vulnerabilities. These all predate any of our security hardening efforts. Here is a list of these endpoints. https://github.com/dotCMS/core/blob/main/dotCMS/src/main/webapp/WEB-INF/dwr.xml |
prepare() never adds testLayout to adminUser's role; only the admin success test does via the API call. Wrapping in try-catch prevents a guaranteed DotDataException on teardown when that test is skipped or fails before the association is created. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
ihoffmann-dot
left a comment
There was a problem hiding this comment.
Approved ✅
This hardening change looks good.
What it does
- Restores the admin-only check on
saveRolePermission, which had been dropped by mistake. Users with only roles-portlet access can no longer change role permissions. - Applies the same admin check in
saveRoleLayoutsbefore anything is loaded, so unauthorized callers are rejected right away.
Why I'm comfortable approving
- It matches how the other role-modifying endpoints in
RoleAjaxare already protected. - The change is small and contained, with no side effects on the rest of the class.
- The new
RoleAjaxSecurityTestcovers the three affected paths with a non-admin user who only has roles-portlet access, and it is registered inMainSuite2b, so it runs in CI. - The test cleans up after itself, so it won't interfere with other tests in the suite.
Non-blocking note: the read-only role endpoints (e.g. getRole, getUserRole, getRolePermissions) still use the portlet-only check. That looks intentional and is outside the scope of this PR.
…ission changes saveRoleLayouts, updateLayout, deleteLayout and saveRolePermission were gated by getAdminUser(), which only checks Users portlet access. They now require a CMS Administrator, the same bar addUserToRole and removeUsersFromRole already use, and rejected calls are written to the security log. - RoleAjax: add validateCMSAdmin (Roles portlet + isAdmin + SecurityLogger) and use it in all six role and layout mutators - RoleAjaxSecurityTest: describe behaviour rather than history, rename tests to *_nonAdmin_throwsDotSecurityException, cover updateLayout and deleteLayout, and grant the Users portlet so only the CMS Administrator check can reject the calls - RoleAjaxTest: non-admin rejection and security log entry for the four methods - MainSuite2b: import order Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@oidacra review again |
|
@ihoffmann-dot review again |
swicken
left a comment
There was a problem hiding this comment.
Pulled the branch and ran RoleAjaxTest locally: 7 of 7 green. The new helper runs before any of the four methods read or write anything, the REST counterparts in RoleResource and LayoutResource already apply the same CMS Administrator bar, and the only DWR callers are the two Roles & Tools JSPs, as the description says. A merge-tree against current main is clean; of the four files only MainSuite2b has moved on main, in unrelated lines, so the rebase is straightforward.
Two things in the integration test worth fixing before merge, and a couple of nits, on the lines below. I'll follow up separately on the PR wording and history before this goes in.
swicken
left a comment
There was a problem hiding this comment.
Approving. The administrator check is in the right place and the behaviour change is documented; the two test comments above are suggestions, not blockers. When merging, please squash with a single plain commit message rather than the default concatenation.
…usals at WARN - RoleAjax.validateCMSAdmin: log refused calls at WARN with the remote address - RoleAjaxSecurityTest: target a throwaway role for saveRolePermission and saveRoleLayouts, restore the DWR builder attached before the class ran instead of attaching a system-user context, and add a CMS Administrator case where updateLayout and deleteLayout go through - RoleAjaxTest: share one helper for all six rejection tests and assert the WARN entry names the user and remote address - MainSuite2b: fully qualified class name like its neighbours Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
swicken
left a comment
There was a problem hiding this comment.
Re-reviewed at fbf894f. The rejection tests now target a throwaway role and layout, the class saves the DWR context that was attached before it ran and puts that back afterwards, refusals log at WARN with the remote host, and the new administrator case actually exercises update and delete rather than only asserting that nothing was thrown. The merge from main kept main's additions to MainSuite2b, and the only production change since my last pass is the log line in validateCMSAdmin. Approving again.
Summary
Follow-on to #36344. The
RoleAjaxDWR methods that change role layouts, layouts and role permissions now require a CMS Administrator, the same baraddUserToRole/removeUsersFromRolealready use. Rejected calls are written to the security log.saveRolePermissionsaveRoleLayoutsgetAdminUser()), checked after two DB readsupdateLayoutgetAdminUser())deleteLayoutgetAdminUser())addUserToRole/removeUsersFromRolegetAdminUser()checksPortletAPI.hasUserAdminRights, which is Users portlet access, not CMS Administrator, so it is no longer used as the gate for these four methods.Changes
RoleAjax.java(the only production change)validateCMSAdmin(action): Roles portlet check, thenisAdmin(); a non-admin caller gets aSecurityLoggerentry naming the operation and user, and aDotSecurityException.saveRoleLayouts,updateLayout,deleteLayoutandsaveRolePermission, and replaces the inline copies inaddUserToRole/removeUsersFromRole.Behaviour change
Non-admin users who hold the Roles and Users portlets could previously save role layouts and update or delete layouts, and anyone with the Roles portlet could save role permissions. They now get
DotSecurityException. CMS Administrators are unaffected. The only callers of these four DWR methods are the legacy Roles & Tools screens (view_roles_js_inc.jsp,view_role_permissions_js_inc.jsp).Scope
ToolGroupResourceis intentionally not changed. An earlier revision added.requiredRoles(CMS_ADMINISTRATOR_ROLE)toaddToolGroupToUser; it was reverted becauserequiredRolesruns before the method body and would block non-admin Getting Started onboarding (PUT /api/v1/toolgroups/gettingstarted/_addtouser). Those endpoints are handled by sec: enforce admin check on layout assignment and role grant endpoints #36344.ToolGroupResourcematchesmain.getAdminUser()callers (addNewRole,updateRole,deleteRole,lockRole,unlockRole,addNewLayout) are not changed here.Tests
RoleAjaxSecurityTest(integration,MainSuite2b) runs as a non-admin backend user holding both the Roles and the Users portlets, so the Users portlet check would pass and only the CMS Administrator check can reject the call:test_addUserToRole_nonAdmin_throwsDotSecurityExceptiontest_saveRolePermission_nonAdmin_throwsDotSecurityExceptiontest_saveRoleLayouts_nonAdmin_throwsDotSecurityExceptiontest_updateLayout_nonAdmin_throwsDotSecurityExceptiontest_deleteLayout_nonAdmin_throwsDotSecurityExceptionRoleAjaxTest(unit) adds*_rejectsNonAdminfor the four methods, assertingDotSecurityExceptionand aSecurityLoggerentry. Run locally: 7/7 pass, and the four new tests fail against the previousgetAdminUser()gate. The system-user DWR callers inLayoutAPITest/PermissionAPITestare unaffected: the system user always holds the CMS Administrator role.Test plan
DotSecurityExceptionfromsaveRolePermission,saveRoleLayouts,updateLayoutanddeleteLayoutvia DWR, with an entry in the security log_addtouser/_removefromuserofgettingstarted) still worksLayoutAPITestandPermissionAPITeststill pass in MainSuite 2bReferences
🤖 Generated with Claude Code