[6.x] Add guard param to user tags - #15382
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011p6KWCwGqfnEv1ntSxnYUD
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011p6KWCwGqfnEv1ntSxnYUD
jasonvarga
left a comment
There was a problem hiding this comment.
Warning — ?guard= also applies to the CP logout route (src/Http/Controllers/User/LoginController.php:109)
LoginController@logout backs two routes:
routes/web.php:48 → statamic.logout (the front-end route this PR targets)
routes/cp.php:145 → statamic.cp.logout
The new query param is read unconditionally, so /cp/logout?guard= now logs out that other guard and redirects to route('statamic.site') as though the CP logout succeeded — while the CP session stays alive. On the multi-guard setups this PR is aimed at, a crafted logout link leaves a user believing they're signed out of the CP when they aren't. Nothing in the PR's stated scope calls for the CP route to be guard-aware.
Suggested fix: only honour the param on the front-end route — e.g. split the CP logout into its own controller method, or gate on request()->routeIs('statamic.logout').
Warning — guard validation is too loose and has three unhandled-input paths (src/Http/Controllers/User/LoginController.php:111-114)
if ($guard = request()->get('guard')) {
abort_unless(array_key_exists($guard, config('auth.guards')), 404);
}
Auth::guard($guard)->logout();
The check confirms a guard is configured, not that it can be logged out of, and the truthiness guard lets input past it:
Non-stateful guards. ?guard=api (or sanctum/any token-driver guard) passes array_key_exists, but TokenGuard has no logout() → BadMethodCallException → 500.
Array input. ?guard[]=x is truthy, so array_key_exists(['x'], …) throws TypeError: Illegal offset type → 500.
?guard=0. The string "0" is falsy, so the abort_unless never runs; Auth::guard('0') then throws InvalidArgumentException → 500.
All three are 500s reachable from unauthenticated query input on a public route. Suggested fix: read a string (request()->string('guard')->toString()), use !== '' rather than truthiness, and require the guard to resolve to a StatefulGuard rather than merely existing in config.
Warning — no test coverage for the invalid-guard edges or the CP route
LogoutTest covers the happy path and one unknown-guard 404. Missing: the CP logout route with a guard param, a non-session guard, and array/"0" input — i.e. exactly the paths above.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FqBuDquzfGFkbgyQGDCMfp
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FqBuDquzfGFkbgyQGDCMfp
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FqBuDquzfGFkbgyQGDCMfp
|
Thanks for the review! Addressed:
|
|
Might be introducing a security issue. Marking as a draft for a moment so we don't merge prematurely. |
This pull request implements a
guardparameter on the user tags, so a template can read a user from a guard other than the one configured instatamic.users.guards.web.Sites running two user populations (say, website customers on Laravel's
webguard and Statamic users on astatamicguard) can't express that with the config alone, because the front-end guard config sets one default guard for the whole request. An admin toolbar rendered on the front-end for the logged-in CP user therefore had no way of checking permissions or building a logout link for that user.The parameter is accepted by the tags that read the current user:
{{ user }},{{ user:can }},{{ user:cant }},{{ user:is }},{{ user:isnt }},{{ user:in }},{{ user:not_in }},{{ user:logout }}and{{ user:logout_url }}. When it's omitted, the tags behave exactly as before.{{ user:can guard="statamic" do="edit pages entries" }} <a href="{{ user:logout_url guard="statamic" redirect="{url}" }}">Log out</a> {{ /user:can }}This PR also lets the logout route accept a
guardquery parameter, which is what{{ user:logout_url }}appends. Unknown guards return a 404 rather than being passed through to the auth manager.The form tags (login, register, profile, password and so on) are unchanged. They post to controllers that rely on the configured guard, and nobody has asked for those to be guard-aware.
Closes #10490