Skip to content

feat(router): per-route and group REST permission callbacks - #17

Merged
abdul-kaioum merged 3 commits into
mainfrom
feat/route-permission-callback
Oct 27, 2026
Merged

abdul-kaioum merged 3 commits into
mainfrom
feat/route-permission-callback

Conversation

@abdul-kaioum

@abdul-kaioum abdul-kaioum commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Why

APIRouter registers every REST route with permission_callback => '__return_true', so middleware is the only gate on privileged routes. WordPress.org plugin reviewers reject that pattern, and WordPress can't answer 401/403 before the callback runs.

What

  • RouteBase::permission(callable) sets a permission callback for a whole group (Route::group(...)->permission(...)).
  • RouteRegister::permission(callable) overrides it for a single route.
  • APIRouter registers $route->getPermissionCallback(): the route's own, then the group's, then '__return_true'. Routes that don't opt in behave exactly as before.

Group inheritance fix

Permission inside groups exposed a pre-existing RouteBase bug that affected prefix and middleware too:

  • Route::permission(...)->get(...) (or ->middleware(...)) declared inside a group was dropped: the route registered against the group instead of its own RouteBase.
  • Nested groups ignored their parent, and closing one reset the current group to null, so later routes in the outer group became ungrouped. A throwing group callback leaked its group.

Each RouteBase now records the group it was declared in and resolves prefix, middleware, permission, noAuth and ignoreToken through that parent when read; group() restores the enclosing group in a finally. Behaviour change: nested prefixes join (admin/inner) instead of the inner one replacing the outer, and a prefix set on a route inside a group is no longer lost.

Tests

TransportRegistrationTest covers the public default, group propagation to every method, and per-route override. RouteDefinitionTest covers attributes on routes inside a group, nested inheritance with sibling restore, and a throwing group callback. composer test (268), php-cs-fixer and composer compat pass.

Needed by the PaymentForm plugin's WordPress.org submission; it will move from ^2.4 to the release that carries this.

🤖 Generated with Claude Code

APIRouter registered every REST route with
permission_callback => '__return_true', leaving middleware as the only
gate. WordPress.org reviewers reject that for privileged routes, and
WordPress cannot answer 401/403 before the callback runs.

RouteBase::permission() sets a callback for a whole group and
RouteRegister::permission() overrides it per route. Routes without one
keep '__return_true', so existing consumers are unchanged.

Assisted-By: AI
Route attributes declared inside a group (permission, middleware) were
dropped, nested groups ignored their parent and reset the outer group on
close, and a throwing group callback leaked its state.

Assisted-By: AI
Each RouteBase now records the group it was declared in and resolves
prefix, middleware, permission, noAuth and ignoreToken through that
parent at read time. Routes register against their own RouteBase, so
attributes set inside a group are kept, and group() restores the
enclosing group in a finally block.

Nested prefixes now join (admin/inner) instead of the inner one
replacing the outer.

Assisted-By: AI
@abdul-kaioum
abdul-kaioum merged commit bc923a9 into main Oct 27, 2026
0 of 6 checks passed
@abdul-kaioum
abdul-kaioum deleted the feat/route-permission-callback branch October 27, 2026 08:43
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.

2 participants