Skip to content

BUGFIX: Do not create inline editors for nodes without edit permission - #4175

Open
ru3fu5z wants to merge 1 commit into
neos:9.0from
ru3fu5z:bugfix/no-inline-editor-without-edit-permission
Open

BUGFIX: Do not create inline editors for nodes without edit permission#4175
ru3fu5z wants to merge 1 commit into
neos:9.0from
ru3fu5z:bugfix/no-inline-editor-without-edit-permission

Conversation

@ru3fu5z

@ru3fu5z ru3fu5z commented Aug 22, 2026

Copy link
Copy Markdown

Resolves #4171

What I did

Inline editors were created for every inline editable property, regardless of whether the user is allowed to edit the node. Editors could type into content protected by an EditNodePrivilege, and the change was only rejected once it was persisted — with a raw AccessDenied exception from the Content Repository. This is the behaviour described in the note of neos/neos-development-collection#5298.

The node policy already carries the required information (policy.canEdit, computed server side by ContentRepositoryAuthorizationService::getNodePermissions()), but it was never consulted when creating the inline editors — and it was not even available at that point in time.

How I did it

Two changes, both in neos-ui-guest-frame:

  1. initializePropertyDomNode.js now respects policy.canEdit. The decision moved into a small pure function isPropertyInlineEditable() so it can be unit tested. A missing canEdit is deliberately treated as "editable", so nothing is locked down if the policy is unavailable.

  2. initializeGuestFrame.js loads the node policies together with the node data (all([call(loadNodeData, …), call(loadNodePolicies, …)])) and merges them in a single store update. Previously the policies were fetched lazily by the CR/Policies saga after the node data had been merged, while the content DOM nodes are initialized immediately afterwards — and initializeContentDomNode reads the store at call time. Whether the policy was there was therefore a race.

The second part is what makes the first one reliable. Gating on canEdit alone is flaky: measured on the same page over three identical reloads, a gate-only build initialized 1, 5 and 1 editors — i.e. forbidden editors leak in depending on when the policy request returns. With the policies loaded up front the result is deterministic.

Because both requests now run concurrently, this costs no extra latency: in the network timeline the guest frame's flow-query and get-additional-node-metadata start simultaneously. The merged policies also keep the CR/Policies saga from requesting them a second time (its filter skips nodes that already have a policy).

No security logic was added on the client side — the decision stays entirely server side, as requested in #4079. The removed NodePolicyService is not revived and no new endpoint is introduced.

Regarding the ongoing security concept work: I asked in neos/neos-development-collection#5650 (comment) whether a UX fix against the current privilege API is welcome now; no objection in two weeks, so here it is — happy to rework if the concept lands on a different model.

How to verify it

Reproduction on the Neos demo site (a small command controller is needed to apply the subtree tag, since there is no CLI for it yet — see neos/neos-development-collection#5931):

  1. ./flow site:importAll --package-key Neos.Demo
  2. Tag a subtree, e.g. the "Blog" document, via TagSubtree::create(...)
  3. Policy.yaml — note the site-wide target: for nodes that no privilege target matches at all, Flow defaults to "granted", so without it the restricted role could edit everything and nothing would reproduce. Note also LivePublisher: without it the personal workspace is reported read-only and the UI disables editing altogether, which hides the node-level problem behind the workspace-level one.
    privilegeTargets:
      'Neos\Neos\Security\Authorization\Privilege\EditNodePrivilege':
        'Neos.Demo:EditAllNodes':
          matcher: 'demosite'
        'Neos.Demo:EditBlogNodes':
          matcher: 'blog'
    roles:
      'Neos.Neos:Editor':
        privileges:
          - privilegeTarget: 'Neos.Demo:EditAllNodes'
            permission: GRANT
          - privilegeTarget: 'Neos.Demo:EditBlogNodes'
            permission: GRANT
      'Neos.Demo:BlogEditor':
        parentRoles: ['Neos.Neos:AbstractEditor', 'Neos.Neos:LivePublisher']
        privileges:
          - privilegeTarget: 'Neos.Demo:EditBlogNodes'
            permission: GRANT
  4. Log in with a user that only has Neos.Demo:BlogEditor and open a document outside the tagged subtree
  5. Before: clicking into any text starts an inline editor; saving fails with Command "…SetNodeProperties" was denied: No edit permissions for node "…"
    After: the text is not editable; content inside the tagged subtree still is

Measured on the demo site with the setup above, comparing every property against what get-additional-node-metadata reports for its node:

page 9.1.6 as released with this PR server verdict mismatches
Features (not permitted) 16 of 16 editors 0 of 16 all 16 canEdit: false 0
Blog posting (permitted) 6 of 6 editors 5 of 6 true, 1× false (element outside the blog subtree) 0

And in a real 8.3→9.1 customer migration (74 inline editable properties on one page, editor role scoped to a single tagged element):

Build inline editors initialized
9.1.6 as released 54 of 74 (53 of them forbidden for that role)
gate only, policies still lazy 1 / 5 / 1 over three identical reloads (flaky)
this PR 1 / 1 / 1 — exactly the one node the role may edit

Before/after GIFs recorded on the demo site follow in a comment.

Side note, unrelated to this PR: the deny message names the wrong privilege type — it says No privilege of type "…\ReadNodePrivilege" matched while evaluating an Edit target ("Neos.Demo:EditAllNodes": ABSTAIN). Happy to file that separately.

Tests: 7 new Jest cases in initializePropertyDomNode.spec.js; full suite green (151 suites / 826 tests / 52 snapshots), ESLint clean, production build succeeds.

Known limitations (deliberately out of scope)

Content of nodes that the user has no edit permission for (via an
`EditNodePrivilege`) was still initialized with an inline editor. Typing was
possible, but persisting the change failed with an `AccessDenied` exception from
the Content Repository.

The node policy already carries that information, but it was neither respected
when creating the inline editors nor available in time: The policies are loaded
lazily after the node data arrived, while the inline editors are initialized
right after the guest frame merged the node data. They are now loaded together
with the node data, so the guest frame can rely on them.
@github-actions github-actions Bot added Bug Label to mark the change as bugfix 9.0 labels Aug 22, 2026
@ru3fu5z

ru3fu5z commented Aug 22, 2026

Copy link
Copy Markdown
Author
neos-ui-edit-permission-before neos-ui-edit-permission-after

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

9.0 Bug Label to mark the change as bugfix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant