Skip to content

fix(content-types): save legacy editor changes with the user's permissions - #38270

Merged
swicken merged 5 commits into
mainfrom
legacy-content-type-editor-saves-as-user
Oct 29, 2026
Merged

swicken merged 5 commits into
mainfrom
legacy-content-type-editor-saves-as-user

Conversation

@oidacra

@oidacra oidacra commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Summary

The legacy content type editor (/ext/structure/edit_structure) saves content types with the
permissions of the logged-in user, applying the same checks as the content type REST endpoints.
Making a content type the default one requires EDIT_PERMISSIONS stored for it. A requested content
type the user may not read, or that does not exist, gets the same fixed answer.

Changes

  • EditStructureAction:
    • Saves through the new StructureFactory.saveStructure(Structure, User), which uses
      APILocator.getContentTypeAPI(user):
      • every save needs permission to add children and to edit content type permissions on the
        site or folder the type is saved on (the destination one for a new type or a move);
      • an existing type also needs EDIT_PERMISSIONS on it.
    • Saves workflow schemes only after the type itself was saved.
    • When the user may not save the type (including the default content type lookup that runs
      first), nothing is changed, the page shows message.insufficient.permissions.to.save, and
      the refusal is written once to the security log.
    • Loading the requested type: when the user may not read it, or no type has the posted inode,
      the page shows dont-have-permissions-msg and returns to the referer or the content type
      list. Only the refusal is logged. A posted inode that matches no type is no longer saved as a
      new type.
    • cmd=SET_DEFAULT: a refusal shows dont-have-permissions-msg and is logged; any other
      failure is logged as an error and shows an-unexpected-error-occurred.
  • StructureFactory:
    • New saveStructure(Structure, User). It sets the saved inode on the structure and sends the
      same save event as saveStructure(Structure).
    • saveStructure(Structure) is unchanged for its callers (jobs, push publishing, upgrade tasks,
      the field editor). It shares a private helper with the new method.
  • ContentTypeAPIImpl.setAsDefault:
    • Requires EDIT_PERMISSIONS in the permissions stored for the type, with the API's
      respectFrontendRoles flag.
    • Its only caller is the legacy editor.

Tests

Risk

  • The legacy editor requires the same permissions as the content type REST endpoints and the
    Angular editor: add-children plus content type edit-permissions on the type's site or folder for
    every save, and EDIT_PERMISSIONS on an existing type.
  • No layout lists the legacy editor on current versions.
  • setAsDefault requires EDIT_PERMISSIONS stored for the type.
  • A request for an inode that matches no content type is answered like a refused one; no screen
    sends such a request.
  • No schema, index or REST contract change.

Fixes dotCMS/private-issues#889

…sions

The legacy content type editor (/ext/structure/edit_structure) now saves
through the requesting user's ContentTypeAPI, with the same checks as the
REST endpoints: EDIT_PERMISSIONS on an existing type, and permission to
add children and edit content type permissions on the destination site
or folder for a new type or a move. Workflow schemes are saved only after
the type itself was saved.

When the user may not save the type, nothing is changed, the page shows
the insufficient-permissions message, and the refusal is written once to
the security log.

Making a content type the default one now requires EDIT_PERMISSIONS on
it, checked as the save checks it; the legacy editor shows a permissions
message when it is refused.

StructureFactory.saveStructure(Structure, User) is added for this. The
existing saveStructure(Structure), used by jobs, push publishing and
upgrade tasks, is unchanged.
@oidacra oidacra added the LTS: Next Patch Shortlisted of issues that will be included in the upcoming LTS patch label Oct 27, 2026
@oidacra
oidacra marked this pull request as ready for review October 27, 2026 14:48
@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Oct 27, 2026
@claude

claude Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @oidacra's task in 2m 33s —— View job


Code Review

Reviewed the diff against origin/main. This enforces the caller's permissions in the legacy content type editor, matching the REST/Angular editors. The implementation is sound.

Verification notes:

  • ContentTypeAPIImpl.setAsDefault now requires PERMISSION_EDIT_PERMISSIONS (was READ). I confirmed the only non-test callers are the legacy editor (EditStructureAction:495) and the deprecated StructureFactory shim. The pre-existing integration tests that call it (HostAPITest, ContentTypeAPIImplTest) all use systemUser, which short-circuits doesUserHavePermission, so they won't regress.
  • StructureFactory: the public no-user saveStructure(Structure) still routes through the system-user typeAPI and re-wraps DotSecurityException into DotHibernateException, so jobs/push-publish/upgrade-task callers keep their privileged, unchanged behavior. The new user-aware overload lets DotSecurityException propagate — correct.
  • save() fires pushSaveUpdateEvent only after a successful save, so a refused save emits no event and performs no write. Good.
  • The permission gate is on the type save itself (contentTypeAPI.save() is reached even for an unchanged save), so the permission-free saveSchemesForStruct at EditStructureAction:338 is correctly gated behind it — a scheme-only edit still must clear the type save first.
  • Load path: a READ denial (DotSecurityException) is logged and shows dont-have-permissions-msg; a non-existent inode (NotFoundInDbException) shows the same message without logging. Both produce the same user-facing answer, so existence isn't leaked. Matches the stated intent.
  • respectFrontendRoles is a Boolean field set from a primitive constructor arg, so the auto-unbox at ContentTypeAPIImpl:666 can't NPE.

New Issues

  • 🟡 Medium: dotcms-integration/src/test/java/com/dotcms/MainSuite2a.java:68 — EditStructureActionSaveTest is inserted into the middle of @SuiteClasses (per commit 430dbf69), but CLAUDE.md / the Integration Tests guide direct new registrations to be appended at the end of the list. Non-blocking, but it diverges from the documented convention. Fix this →

No correctness, security, or error-handling issues found in the changed code.
· legacy-content-type-editor-saves-as-user

…the legacy editor

ContentTypeAPIImpl.setAsDefault checks EDIT_PERMISSIONS in the permissions
stored for the content type, with the API's respectFrontendRoles flag.

The legacy content type editor answers a requested type that the user may
not read, or that does not exist, the same way: the fixed
dont-have-permissions-msg message and a redirect to the referer or the
content type list. Only the refusal is written to the security log. A
posted inode that matches no type is no longer saved as a new type.

When making a type the default one fails for a reason other than
permissions, the failure is logged as an error and the page shows
an-unexpected-error-occurred.

The Javadoc of StructureFactory.saveStructure(Structure, User) now states
that every save needs permission to add children and to edit content type
permissions on the type's site or folder.
…ditor-saves-as-user

# Conflicts:
#	dotcms-integration/src/test/java/com/dotcms/MainSuite2a.java
@oidacra
oidacra added this pull request to the merge queue Oct 28, 2026
@wezell
wezell removed this pull request from the merge queue due to the queue being cleared Oct 28, 2026
@oidacra
oidacra added this pull request to the merge queue Oct 28, 2026
@oidacra
oidacra removed this pull request from the merge queue due to a manual request Oct 28, 2026

@swicken swicken left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. The legacy editor now loads, saves and sets the default content type with the logged-in user's permissions, and a type the user can't read gets the same answer as one that doesn't exist. Internal callers keep the no-user saveStructure overload, and EditFieldAction already checks PUBLISH before its save. setAsDefault now needs EDIT_PERMISSIONS rather than READ, which applies to every caller of ContentTypeAPI, not only the legacy editor; that looks intended. Since erick's approval the only changes are merges from main and the MainSuite2a registration move. CI is green.

@swicken
swicken added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 4492d29 Oct 29, 2026
71 checks passed
@swicken
swicken deleted the legacy-content-type-editor-saves-as-user branch October 29, 2026 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Backend PR changes Java/Maven backend code LTS: Next Patch Shortlisted of issues that will be included in the upcoming LTS patch

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants