Skip to content

Comments: Make REST comment moderation comment-type aware (Trac #35214, Stage 3)#56

Open
adamsilverstein wants to merge 2 commits into
feature/comment-type-cap-enforcementfrom
feature/comment-type-moderation-rest
Open

Comments: Make REST comment moderation comment-type aware (Trac #35214, Stage 3)#56
adamsilverstein wants to merge 2 commits into
feature/comment-type-cap-enforcementfrom
feature/comment-type-moderation-rest

Conversation

@adamsilverstein

Copy link
Copy Markdown
Owner

Summary

Stage 3 of capability enforcement (follow-up to PR #55): start rerouting real moderation call sites through the new moderate_comment meta capability, beginning with the REST API - the cleanest, most self-contained, most testable surface.

WP_REST_Comments_Controller::check_edit_permission() (the gate for REST update and delete) short-circuited on the global moderate_comments primitive:

if ( current_user_can( 'moderate_comments' ) ) {
    return true;
}
return current_user_can( 'edit_comment', $comment->comment_ID );

That had two problems for a comment type with an independent capability model: a moderator of that type (holding e.g. moderate_reviews) could not act on its comments, while any global moderator could act on every type regardless of its model.

What changed

One-line semantic change in two spots: route the moderator shortcut through the per-comment moderate_comment meta cap (added in #55) instead of the global primitive.

  • check_edit_permission() - the update/delete gate.
  • check_read_permission() - the orphaned-comment branch.

For the default capability model moderate_comment resolves to moderate_comments, so built-in comment / pingback / trackback / note are completely unchanged. A type with its own capability_type is gated by its own moderation primitive.

Why this is the whole REST surface (mostly)

Most per-comment REST gates already call current_user_can( 'edit_comment', $id ), which #55 made type-aware - so they needed no change. The remaining bare moderate_comments checks in the controller are cross-cutting / field-level (collection edit context, setting author / author_ip), where per-single-comment semantics don't apply; those are intentionally left as the global primitive.

Testing

New tests/phpunit/tests/rest-api/rest-comment-type-moderation.php dispatches real POST (update) and DELETE requests:

  • A review moderator (moderate_reviews) and a review editor (edit_others_reviews) can update/delete review comments.
  • A global moderate_comments moderator gets 403 on review comments (independence proven at the REST layer) - and a review moderator gets 403 on default comments (the inverse).
  • Default comments, and the built-in pingback type, still update/delete fine for a global moderator (back-compat).

Full --group comment --group capabilities --group restapi passes (the only failure is a pre-existing environmental oEmbed flake unrelated to comments). PHPCS + PHPStan clean.

Remaining Stage 3 families (future PRs)

Same pattern, one focused PR each so every surface gets its own security review: admin (wp-admin/comment.php is already edit_comment-gated; list-table bulk/ajax-actions.php moderation fallback at line 1000), and XML-RPC (wp.editComment status change). These reach the same map_meta_cap() foundation.

Stacking

Based on feature/comment-type-cap-enforcement (#55). Retarget to trunk as the stack lands behind #12311.

See #35214.

`WP_REST_Comments_Controller::check_edit_permission()` short-circuited on the
global `moderate_comments` primitive, so a moderator of a custom comment type
could not edit or delete comments of that type via REST, while a global
moderator could act on every type regardless of its capability model.

Route the moderator shortcut through the `moderate_comment` meta capability
instead. For comment types using the default capability model this resolves
to `moderate_comments` (behavior unchanged), while a type with its own
capabilities is gated by its own moderation primitive. Apply the same change
to the orphaned-comment branch of `check_read_permission()`.

This consumes the meta capabilities added in the previous commit; it makes no
change for the built-in comment, pingback, trackback, or note types.

See #35214.
Dispatch real update and delete requests through the comments controller to
prove `check_edit_permission()` honors per-type capabilities: a moderator (or
editor) of an independent `review` type can edit and delete review comments,
a global `moderate_comments` moderator cannot touch them, and the default and
built-in ping types behave exactly as before.

See #35214.
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props adamsilverstein.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a6a9835a-5476-4f92-90ec-f9d913150bd2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/comment-type-moderation-rest

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant