Skip to content

Read query parameters through Route::queryParam() - #14

Merged
mastacontrola merged 1 commit into
mainfrom
fix-routed-query-params
Aug 18, 2026
Merged

Read query parameters through Route::queryParam()#14
mastacontrola merged 1 commit into
mainfrom
fix-routed-query-params

Conversation

@mastacontrola

Copy link
Copy Markdown
Member

Companion to FOGProject/fogproject#1163.

The bug

/ext/oidc/start and /ext/oidc/callback are reached by an internal rewrite to
api/index.php. On nginx that rewrite handed the router an empty query
string, so:

filter_input(INPUT_GET, 'provider')   // null, on a URL that clearly has ?provider=3

start() therefore refused a configured, enabled provider with "Unknown
identity provider"
— the plugin could not sign anybody in on any nginx install.
callback() reads state, code and error the same way, so the flow could not
have completed either.

The fix

All four reads go through Route::queryParam(), which #1163 makes public. It
returns $_GET when it is populated and re-parses REQUEST_URI when it is not —
so it is correct on Apache (which carried QSA all along), on a re-installed
nginx server (which now gets $is_args$args), and on an nginx server still
carrying the old vhost. A handler cannot tell which of the three it is running
under, which is exactly why it should not be reading INPUT_GET directly.

Verification — end to end against a real Keycloak

quay.io/keycloak/keycloak:26.0, realm fog, client fog-web with PKCE, group
fog-admins mapped to role Administrator and user group Test Group:

Step Result
click "Sign in with Test IdP" → Keycloak, with state, nonce, code_challenge_method=S256
sign in as valer → back to the FOG dashboard, signed in
database users row 115 with uAuthSource='oidc'; oidcIdentity binding the Keycloak subject GUID to it; oidcUserGrant ×2; roleUserAssoc → Administrator; userGroupMembers → Test Group
remove fog-admins at the IdP, sign in again all grants gone — oidcUserGrant, roleUserAssoc and userGroupMembers empty for that user; the account survives and the dashboard says "No role assigned: this account has no role, so it has no access to any management page."
re-add the group, sign in again grants restored, still exactly one identity row and one user — no duplication

That third row is the one that mattered: grants are recomputed from the claim
on every sign-in, not accumulated.

Test

tests/oidc-flow-safety.test.php gains a gate — no filter_input(INPUT_GET)
anywhere in the flow, and all four parameters read through Route::queryParam().
It strips comments first: the file's own docblock names the wrong call so a reader
knows what not to write, and a gate that reads its own documentation as a
violation is a gate nobody can document. Four mutations verified, all caught.

🤖 Generated with Claude Code

https://claude.ai/code/session_017aBSWrDArXHTpKWkkN27LR

Both flow entry points are reached by an internal rewrite to
api/index.php. On nginx that rewrite handed the router an EMPTY query
string, so filter_input(INPUT_GET, 'provider') returned null and start()
refused a configured, enabled provider as "Unknown identity provider" --
the plugin could not sign anybody in on any nginx install. callback()
reads state, code and error the same way, so the flow could not have
completed either.

fogproject#1163 gives the installer's vhost $is_args$args, which fixes
it at the source, and makes Route::queryParam() public. That method
recovers the value from REQUEST_URI when $_GET is empty, which is what
carries every server that has not re-run the installer -- an existing
vhost is not rewritten by anything. Apache carried QSA all along and
never had the problem, and this code cannot tell which it is running
under, so it uses the method that is right for both.

tests/oidc-flow-safety.test.php gains a gate: no filter_input(INPUT_GET)
anywhere in the flow, and all four parameters read through
Route::queryParam(). It strips comments first, because this file's own
docblock names the wrong call so a reader knows what not to write, and a
gate that reads its own documentation as a violation is a gate nobody
can document. Four mutations verified, all caught.

Verified end to end against a real Keycloak: sign-in provisions the
user, records the identity, and grants the role and user group from the
groups claim; removing the group at the provider and signing in again
revokes both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aBSWrDArXHTpKWkkN27LR
@mastacontrola
mastacontrola merged commit 02dd5c9 into main Aug 18, 2026
2 checks passed
@mastacontrola
mastacontrola deleted the fix-routed-query-params branch August 18, 2026 00:06
mastacontrola added a commit to FOGProject/fogproject that referenced this pull request Aug 18, 2026
Carries FOGProject/fog-plugins#14: the OIDC flow reads its query
parameters through Route::queryParam(), which #1163 made public. Without
this bump the fix cannot reach a server -- fetch-plugins.sh downloads
whatever this constant names.


Claude-Session: https://claude.ai/code/session_017aBSWrDArXHTpKWkkN27LR

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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