Read query parameters through Route::queryParam() - #14
Merged
Conversation
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Companion to FOGProject/fogproject#1163.
The bug
/ext/oidc/startand/ext/oidc/callbackare reached by an internal rewrite toapi/index.php. On nginx that rewrite handed the router an empty querystring, so:
start()therefore refused a configured, enabled provider with "Unknownidentity provider" — the plugin could not sign anybody in on any nginx install.
callback()readsstate,codeanderrorthe same way, so the flow could nothave completed either.
The fix
All four reads go through
Route::queryParam(), which #1163 makes public. Itreturns
$_GETwhen it is populated and re-parsesREQUEST_URIwhen it is not —so it is correct on Apache (which carried
QSAall along), on a re-installednginx server (which now gets
$is_args$args), and on an nginx server stillcarrying 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_GETdirectly.Verification — end to end against a real Keycloak
quay.io/keycloak/keycloak:26.0, realmfog, clientfog-webwith PKCE, groupfog-adminsmapped to role Administrator and user group Test Group:state,nonce,code_challenge_method=S256valerusersrow 115 withuAuthSource='oidc';oidcIdentitybinding the Keycloak subject GUID to it;oidcUserGrant×2;roleUserAssoc→ Administrator;userGroupMembers→ Test Groupfog-adminsat the IdP, sign in againoidcUserGrant,roleUserAssocanduserGroupMembersempty 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."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.phpgains a gate — nofilter_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