-
Notifications
You must be signed in to change notification settings - Fork 0
feat(oauth2): support custom URI schemes for Native clients across redirect_uris, allowed_origins, post_logout_redirect_uris #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
smarcet
wants to merge
14
commits into
main
Choose a base branch
from
hotfix/native-app-custom-schemas
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
844328c
feat(oauth2): support custom URI schemes for Native clients across re…
smarcet 20306e3
refactor(oauth2): relocate Native-client scheme deny-list from HttpUt…
smarcet 580f3fb
docs(adr): record ADR-0001 for Native client custom URI scheme support
smarcet 6668873
fix(oauth2): validate redirect_uris scheme/uniqueness on client creat…
smarcet a0e83b1
fix(oauth2): validate custom URI scheme lists on client create()
smarcet bc5d118
fix(oauth2): exact-match redirect_uris, declare scheme predicate on I…
smarcet 15c71ad
fix(oauth2): exact-match post_logout_redirect_uris, closing the CodeR…
smarcet 2d091b2
fix(oauth2): exact-match isOriginAllowed(), closing the last substrin…
smarcet 65e467e
fix(oauth2): serialize Native client custom-scheme create/update behi…
smarcet 889c57c
test(oauth2): document query-string/path-casing gap in URI matching (…
smarcet f75bb28
fix(oauth2): scope Native client custom-scheme lock to payloads that …
smarcet 75d86ac
fix(utils): auto-recover stuck locks - relative TTL and release on an…
smarcet f35330c
test(oauth2): clear stale facade instances in OAuth2LoginStrategyTest…
smarcet 670753d
fix(oauth2): release facade state in tearDown even if Mockery::close(…
smarcet File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Missing null checks after normalization can cause unintended matches.
If
URLUtils::normalizeUrl()fails and returnsnullfor a malformed requested origin,$originWithoutPortevaluates tonull. If a registered origin also causesnormalizeUrl()to fail, the strict equality check ($originWithoutPort === $canonical_allowed_origin) evaluates tonull === nulland incorrectly returnstrue.Add
empty()checks after normalization to ensurenullvalues are safely rejected, consistent with the pattern used inisPostLogoutUriAllowed.🛡️ Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@smarcet Following up on both points raised in this review round for
Client.php:Missing null checks after
normalizeUrl()(this thread,isOriginAllowed()line 880): Investigated by fuzzingURLUtils::normalizeUrl()against a wide range of malformed inputs (invalid UTF-8, malformed IPv6/ports, control characters, bracket/backslash paths, overlong percent-encoding, etc.). Every malformed case already fails earlier, atcanonicalUrl()'s ownfilter_var(FILTER_VALIDATE_URL)/parse_url()guard, which is already checked. Could not find an input wherecanonicalUrl()succeeds but the subsequentnormalizeUrl()call on that output returns null/empty. The gap is real on inspection (the underlyingglenscott/url-normalizerlibrary'smbParseUrl()can diverge from PHP's nativeparse_url()and reset to an empty state - seeNormalizer::setUrl()), and adding the guard would match the pattern already used on the requested side inisPostLogoutUriAllowed(). Leaving open as a low-priority defensive-hardening item rather than a confirmed exploitable bug, since no reproduction was found despite the attempt.Query string / path casing dropped by
canonicalUrl()(outside-diff comment, lines 690-706): Not acting on this.canonicalUrl()never reads the query component and lowercases the path; both were true before this PR's changes and remain true after. The exact-match rewrite in this PR exists to close a prefix/substring bypass (a registered value matching as a substring of a different, longer URI) - that's the property RFC 6749 SS3.1.2.2 exact-matching is protecting here, and it's fully closed. Query-string tolerance is a pre-existing, deliberate choice already documented and tested forisPostLogoutUriAllowed()(testIsPostLogoutUriAllowedNativeClientAcceptsDynamicQueryString- dynamicstate/sessionparams appended by the client), and doesn't reopen that bypass: it doesn't change the destination authority or path, only the client's own trailing query params on their own already-validated endpoint. Path-case-insensitivity is similarly self-referential - it can only make a registered value match a differently-cased variant of itself, never a different client's URI or a different authority/path, so it doesn't reintroduce cross-client or cross-origin risk either. Both behaviors are now additionally covered by regression tests (testIsUriAllowedIgnoresPathCasingDifferences,testIsPostLogoutUriAllowedIgnoresPathCasingDifferences,testIsUriAllowedAcceptsAnyQueryStringNotJustDynamicOnes) so this stays a deliberate, pinned behavior rather than an unreviewed implicit one.