Skip to content

[2.x] fix: Avoid loading target user's groups in editCredentials check - #4729

Merged
imorland merged 1 commit into
2.xfrom
im/editcredentials-skip-admin-check
Jun 13, 2026
Merged

[2.x] fix: Avoid loading target user's groups in editCredentials check#4729
imorland merged 1 commit into
2.xfrom
im/editcredentials-skip-admin-check

Conversation

@imorland

Copy link
Copy Markdown
Member

Fixes #4724.

Problem

UserPolicy::editCredentials() evaluated $user->isAdmin() — which reads $user->groupsbefore checking whether the actor can edit credentials at all:

if ($user->isAdmin() && ! $actor->isAdmin()) {
    return $this->deny();
}
if ($actor->hasPermission('user.editCredentials')) {
    return $this->allow();
}
return null;

UserResource's email-field visibility calls editCredentials for every serialized user. On render paths that serialize many users, $user->isAdmin() lazy-loads each one's groups — one group_user query per user. #4696 fixed this for post authors on the JSON:API posts endpoint by eager-loading user.groups, but it does not cover other serialized users — notably the likers included by default via flarum-likes' likes relationship, whose groups are not eager-loaded for post-stream posts.

Fix

Check the actor's user.editCredentials permission first and return no opinion when they lack it. Guests and normal users — the vast majority of serialized actors — never reach $user->isAdmin(), so the target user's groups are never loaded. $user->isAdmin() is only consulted when the actor can actually edit credentials, where it is needed to stop a non-admin from editing an admin's credentials.

This eliminates the work for the common case rather than batching it, and the visibility outcome is unchanged in every case:

actor has editCredentials target is admin actor is admin before after
no deny/null → not allowed null → not allowed
yes no allow allow
yes yes no deny deny
yes yes yes allow allow

Tests

  • New regression test in flarum-likes (ListPostsTest::likers_groups_are_not_loaded_individually): asserts likers' groups are batch-loaded, not one query per liker. Fails without this change (loads them individually), passes with it.
  • The existing user API + policy suite (114 tests) remains green, covering the credential-protection behaviour the table above preserves.

UserPolicy::editCredentials evaluated $user->isAdmin() (which reads
$user->groups) before checking whether the actor can edit credentials at
all. On render paths that serialize many users — post authors, and likers
via flarum-likes' default `likes` include — UserResource's email-field
visibility runs this check per serialized user, lazy-loading each one's
groups: an N+1 that #4696 did not cover.

Check the actor's `user.editCredentials` permission first and return no
opinion when absent (guests and normal users — the common case), so the
target user's groups are never touched. $user->isAdmin() is only consulted
when the actor can actually edit credentials, where it is needed to protect
admins. The visibility outcome is unchanged in every case.

Adds a flarum-likes regression test asserting likers' groups are not
loaded one query per liker.

Fixes #4724.
@imorland imorland changed the title Avoid loading target user's groups in editCredentials check [2.x] fix: Avoid loading target user's groups in editCredentials check Jun 13, 2026
@imorland
imorland marked this pull request as ready for review June 13, 2026 18:58
@imorland
imorland requested a review from a team as a code owner June 13, 2026 18:58
@imorland imorland added this to the 2.0.0-rc.4 milestone Jun 13, 2026
@imorland
imorland merged commit fe5f778 into 2.x Jun 13, 2026
25 checks passed
@imorland
imorland deleted the im/editcredentials-skip-admin-check branch June 13, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[2.x] N+1 group_user queries when serializing post authors on the forum HTML render path

1 participant